Skip to content

Commit a6873b3

Browse files
committed
Fix to #30358 - Duplicate table alias in generated select query (An item 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
1 parent 6aadd9a commit a6873b3

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,22 @@ public AliasUniquifier(HashSet<string> usedAliases)
396396
{
397397
for (var i = 0; i < innerSelectExpression._tableReferences.Count; i++)
398398
{
399-
var newAlias = GenerateUniqueAlias(_usedAliases, innerSelectExpression._tableReferences[i].Alias);
399+
var currentAlias = innerSelectExpression._tableReferences[i].Alias;
400+
var newAlias = GenerateUniqueAlias(_usedAliases, currentAlias);
401+
402+
if (newAlias != currentAlias)
403+
{
404+
// we keep the old alias in the list (even though it's not actually being used anymore)
405+
// to disambiguate the APPLY case, e.g. something like this:
406+
// SELECT * FROM EntityOne as e
407+
// OUTER APPLY (
408+
// SELECT * FROM EntityTwo as e1
409+
// LEFT JOIN EntityThree as e ON (...) -- reuse alias e, since we use e1 after uniqification
410+
// WHERE e.Foo == e1.Bar -- ambiguity! e could refer to EntityOne or EntityThree
411+
// ) as t
412+
innerSelectExpression._usedAliases.Add(newAlias);
413+
}
414+
400415
innerSelectExpression._tableReferences[i].Alias = newAlias;
401416
UnwrapJoinExpression(innerSelectExpression._tables[i]).Alias = newAlias;
402417
}

test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,91 @@ public class Rut
369369
public int? Value { get; set; }
370370
}
371371

372+
[ConditionalTheory]
373+
[MemberData(nameof(IsAsyncData))]
374+
public virtual async Task Join_selects_with_duplicating_aliases_and_owned_expansion__uniquifies_correctly(bool async)
375+
{
376+
var contextFactory = await InitializeAsync<MyContext30358>(seed: c => c.Seed());
377+
using var context = contextFactory.CreateContext();
378+
379+
var query = from monarch in context.Monarchs
380+
join magus in context.Magi.Where(x => x.Name.Contains("Bayaz")) on monarch.RulerOf equals magus.Affiliation
381+
select new { monarch, magus };
382+
383+
var result = async ? await query.ToListAsync() : query.ToList();
384+
385+
Assert.Single(result);
386+
Assert.Equal("The Union", result[0].monarch.RulerOf);
387+
Assert.Equal("The Divider", result[0].magus.ToolUsed.Name);
388+
}
389+
390+
protected class MyContext30358 : DbContext
391+
{
392+
public DbSet<Monarch30358> Monarchs { get; set; }
393+
public DbSet<Magus30358> Magi { get; set; }
394+
395+
public MyContext30358(DbContextOptions options)
396+
: base(options)
397+
{
398+
}
399+
400+
protected override void OnModelCreating(ModelBuilder modelBuilder)
401+
{
402+
modelBuilder.Entity<Magus30358>().OwnsOne(x => x.ToolUsed, x => x.ToTable("MagicTools"));
403+
}
404+
405+
public void Seed()
406+
{
407+
Add(new Monarch30358
408+
{
409+
Name = "His August Majesty Guslav the Fifth",
410+
RulerOf = "The Union",
411+
});
412+
413+
Add(new Monarch30358
414+
{
415+
Name = "Emperor Uthman-ul-Dosht",
416+
RulerOf = "The Gurkish Empire",
417+
});
418+
419+
Add(new Magus30358
420+
{
421+
Name = "Bayaz, the First of the Magi",
422+
Affiliation = "The Union",
423+
ToolUsed = new MagicTool30358 { Name = "The Divider" }
424+
});
425+
426+
Add(new Magus30358
427+
{
428+
Name = "The Prophet Khalul",
429+
Affiliation = "The Gurkish Empire",
430+
ToolUsed = new MagicTool30358 { Name = "The Hundred Words" }
431+
});
432+
433+
SaveChanges();
434+
}
435+
}
436+
437+
public class Monarch30358
438+
{
439+
public int Id { get; set; }
440+
public string Name { get; set; }
441+
public string RulerOf { get; set; }
442+
}
443+
444+
public class Magus30358
445+
{
446+
public int Id { get; set; }
447+
public string Name { get; set; }
448+
public string Affiliation { get; set; }
449+
public MagicTool30358 ToolUsed { get; set; }
450+
}
451+
452+
public class MagicTool30358
453+
{
454+
public string Name { get; set; }
455+
}
456+
372457
protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
373458
=> base.AddOptions(builder).ConfigureWarnings(
374459
c => c

test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,23 @@ SELECT [r].[Rot_ApartmentNo]
180180
FROM [RotRutCases] AS [r]
181181
""");
182182
}
183+
184+
public override async Task Join_selects_with_duplicating_aliases_and_owned_expansion__uniquifies_correctly(bool async)
185+
{
186+
await base.Join_selects_with_duplicating_aliases_and_owned_expansion__uniquifies_correctly(async);
187+
188+
AssertSql(
189+
"""
190+
SELECT [m].[Id], [m].[Name], [m].[RulerOf], [t].[Id], [t].[Affiliation], [t].[Name], [t].[Magus30358Id], [t].[Name0]
191+
FROM [Monarchs] AS [m]
192+
INNER JOIN (
193+
SELECT [m0].[Id], [m0].[Affiliation], [m0].[Name], [m1].[Magus30358Id], [m1].[Name] AS [Name0]
194+
FROM [Magi] AS [m0]
195+
LEFT JOIN [MagicTools] AS [m1] ON [m0].[Id] = [m1].[Magus30358Id]
196+
WHERE [m0].[Name] LIKE N'%Bayaz%'
197+
) AS [t] ON [m].[RulerOf] = [t].[Affiliation]
198+
""");
199+
}
200+
201+
183202
}

0 commit comments

Comments
 (0)