请考虑以下课程:
public class Foo
{
public int Id { get; set; }
public string Type { get; set; }
public int BarId { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
}
和以下DbContext:
public class TestDbContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=.;Database=ConditionalJoinEFCoreTest;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Foo>().HasData(new Foo { Id = 1, BarId = 1, Type = "Bar" });
modelBuilder.Entity<Foo>().HasData(new Foo { Id = 2, BarId = 2, Type = "Bar" });
modelBuilder.Entity<Foo>().HasData(new Foo { Id = 3, BarId = 1, Type = "Not Bar" });
modelBuilder.Entity<Foo>().HasData(new Foo { Id = 4, BarId = 2, Type = "Not Bar" });
modelBuilder.Entity<Bar>().HasData(new Bar { Id = 1, Name = "Bar 1" });
modelBuilder.Entity<Bar>().HasData(new Bar { Id = 2, Name = "Bar 2" });
}
}
现在让我们查询数据:
using (var ctx = new TestDbContext())
{
var joinResult = ctx.Foos.GroupJoin(
ctx.Bars,
foo => new { Key = foo.BarId, PropName = foo.Type },
bar => new { Key = bar.Id, PropName = "Bar" },
(foo, bars) => new
{
Foo = foo,
Bars = bars
})
.SelectMany(
x => x.Bars.DefaultIfEmpty(),
(foo, bar) => new
{
Foo = foo.Foo,
Bar = bar.Name
});
var result = joinResult.GroupBy(x => x.Foo.Id).Select(x => new
{
Id = x.Key,
Name = x.Max(r => r.Bar)
}).ToList();
}
此查询将按预期产生以下SQL:
SELECT [foo].[Id], [foo].[BarId], [foo].[Type], [bar].[Name] AS [Bar]
FROM [Foos] AS [foo]
LEFT JOIN [Bars] AS [bar] ON ([foo].[BarId] = [bar].[Id]) AND ([foo].[Type] = N'Bar')
ORDER BY [foo].[Id]
但是,如果我们定义类型:
public class ConditionalJoin
{
public int Key { get; set; }
public string PropName { get; set; }
}
...然后修改LINQ查询:
using (var ctx = new TestDbContext())
{
var joinResult = ctx.Foos.GroupJoin(
ctx.Bars,
foo => new ConditionalJoin { Key = foo.BarId, PropName = foo.Type }, // <-- changed
bar => new ConditionalJoin { Key = bar.Id, PropName = "Bar" }, // <-- changed
(foo, bars) => new
{
Foo = foo,
Bars = bars
})
.SelectMany(
x => x.Bars.DefaultIfEmpty(),
(foo, bar) => new
{
Foo = foo.Foo,
Bar = bar.Name
});
var result = joinResult.GroupBy(x => x.Foo.Id).Select(x => new
{
Id = x.Key,
Name = x.Max(r => r.Bar)
}).ToList();
}
然后生成的SQL如下:
SELECT [foo0].[Id], [foo0].[BarId], [foo0].[Type]
FROM [Foos] AS [foo0]
SELECT [bar0].[Id], [bar0].[Name]
FROM [Bars] AS [bar0]
为什么会这样?
答案 0 :(得分:3)
正如我所怀疑的那样,问题不在于匿名类型与具体类型,而是唯一的C#编译器功能,它发出特殊的Expression.New
调用,而不是这种语法Expression.MemberInit
的常规调用,并且可以完成仅用于匿名类型。与Selection in GroupBy query with NHibernate with dynamic anonymous object中的问题完全相同,解决方案也是如此-使用参数生成类构造函数,并使用将参数映射到类成员的方法生成NewExpression
。
以下是有关静态类的概念证明:
public class ConditionalJoin
{
public ConditionalJoin(int key, string property)
{
Key = key;
Property = property;
}
public int Key { get; }
public string Property { get; }
public static Expression<Func<T, ConditionalJoin>> Select<T>(Expression<Func<T, int>> key, Expression<Func<T, string>> property)
{
var parameter = key.Parameters[0];
var body = Expression.New(
typeof(ConditionalJoin).GetConstructor(new[] { typeof(int), typeof(string) }),
new[] { key.Body, Expression.Invoke(property, parameter) },
new [] { typeof(ConditionalJoin).GetProperty("Key"), typeof(ConditionalJoin).GetProperty("Property") });
return Expression.Lambda<Func<T, ConditionalJoin>>(body, parameter);
}
}
及其用法:
var joinResult = ctx.Foos.GroupJoin(
ctx.Bars,
ConditionalJoin.Select<Foo>(foo => foo.BarId, foo => foo.Type),
ConditionalJoin.Select<Bar>(bar => bar.Id, bar => "Bar"),
// the rest...
当然,如果您希望查询在被评估的客户端(例如LINQ to Objects)的情况下正常工作,则该类必须更正实现GetHashCode
和Equals
。
话虽如此,实际上EF Core支持另一种更简单的替代解决方案-在匿名/具体类型上使用Tuple
(而不是ValueTuple
-表达式树中仍然不支持这些)。因此,以下内容也可以在EF Core中正常运行(类似Tuple
类型的情况):
var joinResult = ctx.Foos.GroupJoin(
ctx.Bars,
foo => new Tuple<int, string>(foo.BarId, foo.Type),
bar => new Tuple<int, string>(bar.Id, "Bar"),
// the rest...