为什么 EntityFramework 6 不支持显式地按照鉴别器进行过滤?

3
以下是一个小的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();
    }
}

简而言之,我们有一个具有显式配置的鉴别器(在这种情况下为“TypeId”的TPH模型)。然后,我们尝试使用该TypeId查询特定的子类型,因为在我们假设的示例中使用“is”运算符也会返回SubAAs,而不仅仅是SubAs。
我们可以修改上面的内容,例如“Where(x => x is SubA &&!(x is SubAA))”,但这显然会在添加SubAB时立即出现问题。通过构建精确过滤器-LINQ-to-Entities辅助方法来自动化这一点显然非常慢,因为该方法必须进行相当数量的反射。更不用说上述生成的SQL非常可怕,因为EF / My SQL Provider无法正确优化它。
现在尝试执行上述操作会导致在查询被实体化时抛出NotSupportedException,这基本上说明由于TypeId不是实体的成员,我不能将其用于过滤。
我去寻找绕过此问题的方法,但我能找到的最好的东西是自动生成“Where(x => x is SubA &&!(x is SubAA))”版本的代码片段,这可能是我解决这个问题的方法。
所以我的问题是:为什么EntityFramework不支持这个?

2
因为特权精英想要统治我们生活的方方面面,这只是他们微妙控制我们每一个清醒时刻的方面之一。 - Liam
认真地说,找出为什么会如何帮助你的情况?很可能答案是因为它根本不会。那会帮助你的问题吗?很可能不会。更好的问题不是如何解决这个问题吗? - Liam
1
@Liam,实际上,我认为特权精英们正在开发C# 7.0,你得把这个归咎于那些被遗忘和错失的人。 - Jodrell
1
我们停止使用EF6并转向Dapper,因为EF不够可靠或快速。最终,自己编写SQL更容易些。 - Jodrell
1
@Liam 不,我已经提到我可以使用非常丑陋和糟糕的 (x is Thing) && !(x is OtherThing) 级联来解决这个问题,所以那可能是我最终会做的事情。我真正感兴趣的是,为什么这样做会导致异常,是否有任何设计上的原因。 - default0
1个回答

1
这个解决方案完全按照您的要求工作,不需要更改任何内容^^“勿改良已成之体”:)
您可以使用枚举而不是整数,这将使您的代码更具类型安全性!
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

我已经在属性中添加了关键字protected。请投票,如果这解决了问题,请将其标记为答案 :-) - Bassam Alugili
它仍然相对容易被破坏 - 只需从其中一个子类中覆盖它,或者意外选择一个重复的子类 :) 我自己解决这个问题的方法与你的类似,但基本上是不可破坏的(虽然需要在上下文中进行配置 - 但我可以轻松自动化)。我将您的答案标记为有用,因为它确实有用,但它没有回答“为什么EntityFramework不支持此功能?”这就是为什么我不会将其标记为接受答案的原因。希望您能理解。 - default0
1
如果您想将TypeId用作TPH的鉴别器,则不能将其用于其他任何内容,因为您不能将其作为属性-->在EF中出现映射错误。这是EF中的设计决策! - Bassam Alugili
1
实体类型在继承中是不可变的,因此如果您更改任何类中的TypeId,则会更改实体的类型,但.NET不支持鸭子类型,因此您无法更改现有实例的类型。即使属性设置器(TypeId)是受保护的,您也可以在实体类型内部更改它。你必须要有一个setter, 因为EF需要它来填充数据。如果您对此答案仍然不满意,那我放弃了。 - Bassam Alugili
1
由于没有其他更令人满意的解释,而你的解释已经足够接近(尽管我仍然需要对手动鉴别器配置进行一些测试,因为我敢打赌你可以用它做很多愚蠢的事情),所以我将把你的答案标记为被接受的。非常感谢! - default0
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接