Skip to content

Commit

Permalink
Fix to #30358 - Duplicate table alias in generated select query (An i…
Browse files Browse the repository at this point in the history
…tem with the same key has already been added)

We were not updating usedAliases list as we uniquify tabke aliases after combining two sources because of JOIN. If the resulting query also needs owned type expanded (when owned type is mapped to a separate table - this expansion happens in translation rather than nav expansion), additional table generated could have incorrect alias.
Fix is to update the usedAliases list as we make changes to aliases, so that when new tables are added we generate new aliases correctly.

Fixes #30358
  • Loading branch information
maumar committed Mar 14, 2023
1 parent 6aadd9a commit 57a8325
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,22 @@ public AliasUniquifier(HashSet<string> usedAliases)
{
for (var i = 0; i < innerSelectExpression._tableReferences.Count; i++)
{
var newAlias = GenerateUniqueAlias(_usedAliases, innerSelectExpression._tableReferences[i].Alias);
var currentAlias = innerSelectExpression._tableReferences[i].Alias;
var 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]
""");
}
}

0 comments on commit 57a8325

Please sign in to comment.