entity-framework-core - Entity Framework - 使用派生类型方法有条件地查询相关实体

标签 entity-framework-core

我有三门课:

public abstract class Conversation
{
    public int Id { get; set; }

    public abstract bool HasUser(string userId);
}

public class PublicConversation : Conversation
{
    public override bool HasUser(string userId)
    {
        return true;
    }
}

public class PrivateConversation : Conversation
{
    public ICollection<User> Users { get; set; }

    public override bool HasUser(string userId)
    {
        return Users.Any(t => t.UserId == userId);
    }
}

在DbContext中,有DbSet:

public DbSet<Conversation> Conversations { get; set; }
public DbSet<PrivateConversation> PrivateConversations { get; set; }
public DbSet<PublicConversation> PublicConversations { get; set; }

public DbSet<User> Users { get; set; }

生成两个表:ConversationsUsers

PrivateConversations and PublicConversations are saved in table Conversations

现在查询发生错误:


// For some reason, I can only query `db.Conversations`, can not access `db.PrivateConversations`
var conversations = db.Conversations
  .Include(t => (t as PrivateConversation).Users)
  .Where(t => t.HasUser("something"))
  .ToList();

对于所有PublicConversation,一切正常。

告诉我,在 PrivateConversation 方法中:HasUser()Users.Any()Users 是空。

enter image description here

我很困惑。如何查询其中与用户的所有私有(private)对话?

其他信息:

我使用的包:

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SQLServer" Version="2.2.6" />

运行时:.NET Core 2.2

<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>

最佳答案

表达式

t.HasUser("something")

无法转换为 SQL,因为它需要 Conversation 对象调用 HasUser 方法。只有不需要物化对象的众所周知的方法才可以转换为SQL。

查询翻译器以不同的方式处理不可翻译的表达式。 EF6 抛出异常。 EF Core 3.0+ 中也是如此。 。但是您似乎正在使用的 EF Core 1.x/2.x 尝试评估此类表达式 client side .

问题是,如果条件使用实体基元属性,它会起作用,但导航属性会失败,因为在客户端评估时,它们尚未加载,即使稍后将使用 Include 加载它们>。您可以通过初始化集合导航属性或添加 null 检查来避免 NRE,但无论哪种情况,结果都不会正确。

这一点,再加上隐藏的低效率,是在 3.0 中删除隐式客户端评估的原因之一。

有两种解决方案:

(1) 保持封装并使用显式客户端评估。显式客户端评估意味着您在查询中的某个点显式插入 AsEnumerable()。在此之前的所有内容都将由 EF Core 执行,之后的所有内容都将由 LINQ to Objects 在 LINQ to Entities (EF Core) 查询的完全具体化结果上执行。

var conversations = db.Conversations
      .Include(t => (t as PrivateConversation).Users)
      .AsEnumerable() // <--
      .Where(t => t.HasUser("something"))
      .ToList(); 

(2) 打破封装并使用可翻译结构重新创建表达式。这样过滤将发生在服务器端:

var conversations = db.Conversations
      .Include(t => (t as PrivateConversation).Users)
      .Where(t => t is PrivateConversation ?
          ((PrivateConversation)t).Users.Any(u => u.Name == "something") :
          true)
      .ToList(); 

即而不是

t.HasUser("something")

你会使用

t is PrivateConversation ?
((PrivateConversation)t).Users.Any(u => u.Name == "something") :
true

或同等内容

!(t is PrivateConversation)
|| ((PrivateConversation)t).Users.Any(u => u.Name == "something")

它从 HasUser 方法的派生类重写中提取内联逻辑。

请注意,EF Core(或任何其他库)无法“查看”类似于 C# 编译器的方法的实现,因为它没有源代码(除非它尝试反编译已编译的代码,这不是一个简单的任务) .

两种解决方案都有优点和缺点。从 OOP 的角度来看,(1) 更好,但效率较低,因为加载的数据(和相关数据)可能比其需要的多得多。 (2) 是相反的 - 性能/内存使用更好,OOP 更糟糕(破坏封装,需要更新,以防相关方法的新派生类实现等),因此请使用更适合您需求的那个。

关于entity-framework-core - Entity Framework - 使用派生类型方法有条件地查询相关实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57728370/

相关文章:

c# - ASP.NET Core 5 Blazor WASM、gRPC、Entity Framework Core 5 : many-to-many results in stack overflow

c# - ASP.NET 5 MVC 6 通用存储库模式

c# - ASP.NET Core Blazor 服务器中的 Entity Framework 上下文生命周期

c# - 在 Entity Core 中使用 Postgres lpad

entity-framework-core - .Net EF Core 2.1 从 IQueryable 参数获取 DbContext

c# - ASP.NET Core 2 +获取数据库上下文实例

c# - 如何使用 EF Core 在 ASP.NET Core 中取消应用迁移

c# - 如何在 EF core 2.1 中使用 FreeText

c# - 在 Entity Framework Core 中包含集合

c# - Entity Framework Core 3.0 查询导致 "SqlException: ' Execution Timeout Expired'"和 tempdb 变满。适用于 EF Core 2.2.6