为什么 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