Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/7.0] Fix to #30358 - Duplicate table alias in generated select query (An item with the same key has already been added) #30486

Merged
merged 1 commit into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,9 @@ public int GetHashCode((ColumnExpression Column, ValueComparer Comparer) obj)

private sealed class AliasUniquifier : ExpressionVisitor
{
private static readonly bool UseOldBehavior30358
= AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue30358", out var enabled30358) && enabled30358;

private readonly HashSet<string> _usedAliases;
private readonly List<SelectExpression> _visitedSelectExpressions = new();

Expand All @@ -401,7 +404,30 @@ public AliasUniquifier(HashSet<string> usedAliases)
{
for (var i = 0; i < innerSelectExpression._tableReferences.Count; i++)
{
var newAlias = GenerateUniqueAlias(_usedAliases, innerSelectExpression._tableReferences[i].Alias);
string newAlias;
if (UseOldBehavior30358)
{
newAlias = GenerateUniqueAlias(_usedAliases, innerSelectExpression._tableReferences[i].Alias);
}
else
{
var currentAlias = innerSelectExpression._tableReferences[i].Alias;
newAlias = GenerateUniqueAlias(_usedAliases, currentAlias);

if (newAlias != currentAlias)
{
// we keep the old alias in the list (even though it's not actually being used anymore)
// to disambiguate the APPLY case, e.g. something like this:
// SELECT * FROM EntityOne as e
// OUTER APPLY (
// SELECT * FROM EntityTwo as e1
// LEFT JOIN EntityThree as e ON (...) -- reuse alias e, since we use e1 after uniqification
// WHERE e.Foo == e1.Bar -- ambiguity! e could refer to EntityOne or EntityThree
// ) as t
innerSelectExpression._usedAliases.Add(newAlias);
}
}

innerSelectExpression._tableReferences[i].Alias = newAlias;
UnwrapJoinExpression(innerSelectExpression._tables[i]).Alias = newAlias;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,91 @@ public class Rut
public int? Value { get; set; }
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Join_selects_with_duplicating_aliases_and_owned_expansion_uniquifies_correctly(bool async)
{
var contextFactory = await InitializeAsync<MyContext30358>(seed: c => c.Seed());
using var context = contextFactory.CreateContext();

var query = from monarch in context.Monarchs
join magus in context.Magi.Where(x => x.Name.Contains("Bayaz")) on monarch.RulerOf equals magus.Affiliation
select new { monarch, magus };

var result = async ? await query.ToListAsync() : query.ToList();

Assert.Single(result);
Assert.Equal("The Union", result[0].monarch.RulerOf);
Assert.Equal("The Divider", result[0].magus.ToolUsed.Name);
}

protected class MyContext30358 : DbContext
{
public DbSet<Monarch30358> Monarchs { get; set; }
public DbSet<Magus30358> Magi { get; set; }

public MyContext30358(DbContextOptions options)
: base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Magus30358>().OwnsOne(x => x.ToolUsed, x => x.ToTable("MagicTools"));
}

public void Seed()
{
Add(new Monarch30358
{
Name = "His August Majesty Guslav the Fifth",
RulerOf = "The Union",
});

Add(new Monarch30358
{
Name = "Emperor Uthman-ul-Dosht",
RulerOf = "The Gurkish Empire",
});

Add(new Magus30358
{
Name = "Bayaz, the First of the Magi",
Affiliation = "The Union",
ToolUsed = new MagicTool30358 { Name = "The Divider" }
});

Add(new Magus30358
{
Name = "The Prophet Khalul",
Affiliation = "The Gurkish Empire",
ToolUsed = new MagicTool30358 { Name = "The Hundred Words" }
});

SaveChanges();
}
}

public class Monarch30358
{
public int Id { get; set; }
public string Name { get; set; }
public string RulerOf { get; set; }
}

public class Magus30358
{
public int Id { get; set; }
public string Name { get; set; }
public string Affiliation { get; set; }
public MagicTool30358 ToolUsed { get; set; }
}

public class MagicTool30358
{
public string Name { get; set; }
}

protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
=> base.AddOptions(builder).ConfigureWarnings(
c => c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,23 @@ public override async Task Owned_entity_with_all_null_properties_property_access
"""
SELECT [r].[Rot_ApartmentNo]
FROM [RotRutCases] AS [r]
""");
}

public override async Task Join_selects_with_duplicating_aliases_and_owned_expansion_uniquifies_correctly(bool async)
{
await base.Join_selects_with_duplicating_aliases_and_owned_expansion_uniquifies_correctly(async);

AssertSql(
"""
SELECT [m].[Id], [m].[Name], [m].[RulerOf], [t].[Id], [t].[Affiliation], [t].[Name], [t].[Magus30358Id], [t].[Name0]
FROM [Monarchs] AS [m]
INNER JOIN (
SELECT [m0].[Id], [m0].[Affiliation], [m0].[Name], [m1].[Magus30358Id], [m1].[Name] AS [Name0]
FROM [Magi] AS [m0]
LEFT JOIN [MagicTools] AS [m1] ON [m0].[Id] = [m1].[Magus30358Id]
WHERE [m0].[Name] LIKE N'%Bayaz%'
) AS [t] ON [m].[RulerOf] = [t].[Affiliation]
""");
}
}