为什么 EntityFramework6 不支持 Discriminator 显式过滤?
Why does EntityFramework 6 not support explicitly filtering by Discriminator?
以下是演示该问题的小型 EF6 程序。
public abstract class Base
{
public int Id { get; set; }
public abstract int TypeId { get; }
}
public class SubA : Base
{
public override int TypeId => 1;
}
public class SubAA : SubA
{
public override int TypeId => 2;
}
public class SubB : Base
{
public override int TypeId => 3;
}
public class SubC : Base
{
public override int TypeId => 4;
}
public class DevartContext : DbContext
{
public virtual DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Base>()
.Map<SubA>(x => x.Requires(nameof(SubA.TypeId)).HasValue(1))
.Map<SubAA>(x => x.Requires(nameof(SubAA.TypeId)).HasValue(2))
.Map<SubB>(x => x.Requires(nameof(SubB.TypeId)).HasValue(3))
.Map<SubC>(x => x.Requires(nameof(SubC.TypeId)).HasValue(4));
}
}
public class Program
{
public static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
}
它的要点是:我们有一个 TPH 模型,它带有一个明确配置的鉴别器(TypeId
在这种情况下)。然后我们尝试使用该 TypeId 查询特定子类型,因为在我们的假设示例中使用 is
运算符也会 return SubAAs,而不仅仅是 SubAs.
我显然可以将上面的内容修改为 Where(x => x is SubA && !(x is SubAA))
之类的东西,但是这显然会在我添加 SubAB 后立即中断,并通过构建一个 exact-filter-linq-to-entities-helper 来自动化它-method 显然非常慢,因为该方法必须进行大量的反射。更不用说上面生成的 SQL 是可怕的,因为 EF/My SQL Provider 没有正确优化它。
现在尝试执行上述操作会导致在具体化查询时抛出 NotSupportedException
,这基本上表明因为 TypeId
不是实体的成员,所以我不能将其用于过滤。
我四处寻找绕过这个问题的方法,但我能找到的最好的东西是一个自动生成 Where(x => x is SubA && !(x is SubAA))
版本来解决问题的片段,这很可能是我将拥有的要解决这个问题。
所以我的问题是:为什么 EntityFramework 不支持这个?
这个灵魂如你所愿地工作,不要改变任何东西^^ "never change a running system" :)
您可以使用枚举而不是整数,这可以为您的代码提供更高的类型安全性!
static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
ctx.Bases.Add(new SubA());
ctx.Bases.Add(new SubAA());
ctx.Bases.Add(new SubB());
ctx.SaveChanges();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
public abstract class Base
{
public int Id { get; set; }
public virtual int TypeId { get; protected set; }
}
public class SubA : Base
{
public override int TypeId { get;protected set; } = 1;
}
public class SubAA : SubA
{
public override int TypeId { get; protected set; } = 2;
}
public class SubB : Base
{
public override int TypeId { get; protected set; } = 3;
}
public class SubC : Base
{
public override int TypeId { get; protected set; } = 4;
}
public class DevartContext : DbContext
{
public DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
}
数据库中的结果:
Id TypeId Discriminator
1 1 SubA
2 2 SubAA
3 3 SubB
以下是演示该问题的小型 EF6 程序。
public abstract class Base
{
public int Id { get; set; }
public abstract int TypeId { get; }
}
public class SubA : Base
{
public override int TypeId => 1;
}
public class SubAA : SubA
{
public override int TypeId => 2;
}
public class SubB : Base
{
public override int TypeId => 3;
}
public class SubC : Base
{
public override int TypeId => 4;
}
public class DevartContext : DbContext
{
public virtual DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Base>()
.Map<SubA>(x => x.Requires(nameof(SubA.TypeId)).HasValue(1))
.Map<SubAA>(x => x.Requires(nameof(SubAA.TypeId)).HasValue(2))
.Map<SubB>(x => x.Requires(nameof(SubB.TypeId)).HasValue(3))
.Map<SubC>(x => x.Requires(nameof(SubC.TypeId)).HasValue(4));
}
}
public class Program
{
public static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
}
它的要点是:我们有一个 TPH 模型,它带有一个明确配置的鉴别器(TypeId
在这种情况下)。然后我们尝试使用该 TypeId 查询特定子类型,因为在我们的假设示例中使用 is
运算符也会 return SubAAs,而不仅仅是 SubAs.
我显然可以将上面的内容修改为 Where(x => x is SubA && !(x is SubAA))
之类的东西,但是这显然会在我添加 SubAB 后立即中断,并通过构建一个 exact-filter-linq-to-entities-helper 来自动化它-method 显然非常慢,因为该方法必须进行大量的反射。更不用说上面生成的 SQL 是可怕的,因为 EF/My SQL Provider 没有正确优化它。
现在尝试执行上述操作会导致在具体化查询时抛出 NotSupportedException
,这基本上表明因为 TypeId
不是实体的成员,所以我不能将其用于过滤。
我四处寻找绕过这个问题的方法,但我能找到的最好的东西是一个自动生成 Where(x => x is SubA && !(x is SubAA))
版本来解决问题的片段,这很可能是我将拥有的要解决这个问题。
所以我的问题是:为什么 EntityFramework 不支持这个?
这个灵魂如你所愿地工作,不要改变任何东西^^ "never change a running system" :)
您可以使用枚举而不是整数,这可以为您的代码提供更高的类型安全性!
static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
ctx.Bases.Add(new SubA());
ctx.Bases.Add(new SubAA());
ctx.Bases.Add(new SubB());
ctx.SaveChanges();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
public abstract class Base
{
public int Id { get; set; }
public virtual int TypeId { get; protected set; }
}
public class SubA : Base
{
public override int TypeId { get;protected set; } = 1;
}
public class SubAA : SubA
{
public override int TypeId { get; protected set; } = 2;
}
public class SubB : Base
{
public override int TypeId { get; protected set; } = 3;
}
public class SubC : Base
{
public override int TypeId { get; protected set; } = 4;
}
public class DevartContext : DbContext
{
public DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
}
数据库中的结果:
Id TypeId Discriminator
1 1 SubA
2 2 SubAA
3 3 SubB