【问题标题】:Filtering with EF Core 2.1 inheritance使用 EF Core 2.1 继承进行过滤
【发布时间】:2019-04-02 07:41:24
【问题描述】:

当使用继承的对象时,我正在尝试找到一种方法来过滤我在 EF Core 2.1 中的结果。

我有一个基础模型和几个继承的类(但我只包括了一个):

public class Like {
    public int Id { get; set; }
    public LikeType LikeType { get; set; }
}

public class DocumentLike : Like {
    [ForeignKey(nameof(Document))]
    public int DocumentId { get; set; }
    public virtual Document Document { get; set; }
}

LikeType 是一个枚举,它被定义为 dbcontext 中的鉴别器。每个Document 都有一个布尔属性.IsCurrent

要从数据库中获取所有项目,我使用如下查询:

IQueryable<Like> query = _context.Set<Like>()
    .Include(x => x.Owner)
    .Include(x => (x as DocumentLike).Document.DocumentType)
    .Include(x => (x as ProductLike).Product)
    .Include(x => (x as TrainingLike).Training)

这很好用,并且返回包含子对象的所有对象,没有任何错误。我要做的是从数据库中获取链接文档具有.IsCurrent == true 的所有项目。我尝试将以下内容添加到上面的查询中,但都导致异常:

.Where(x => (x as DocumentLike).Document.IsCurrent == true)

还有:

.Where(x => x.LikeType == LikeType.Document ? (x as DocumentLike).Document.IsCurrent == true : true) 

执行查询时抛出的异常:

NullReferenceException: Object reference not set to an instance of an object.
    lambda_method(Closure , TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<Like, ApplicationUser>, Organisation>, Training>, Product>, Platform>, NewsItem>, Event>, Document>, DocumentType>, Course>, CourseType>, ApplicationUser> )
    System.Linq.Utilities+<>c__DisplayClass1_0<TSource>.<CombinePredicates>b__0(TSource x)
    System.Linq.Enumerable+WhereSelectEnumerableIterator<TSource, TResult>.MoveNext()
    Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities<TOut, TIn>(IEnumerable<TOut> results, QueryContext queryContext, IList<EntityTrackingInfo> entityTrackingInfos, IList<Func<TIn, object>> entityAccessors)+MoveNext()
    Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider+ExceptionInterceptor<T>+EnumeratorExceptionInterceptor.MoveNext()
    System.Collections.Generic.List<T>.AddEnumerable(IEnumerable<T> enumerable)
    System.Linq.Enumerable.ToList<TSource>(IEnumerable<TSource> source)

有没有办法做到这一点?

更新: 澄清一下:我希望得到一个从数据库中返回所有Like-objects 的查询,无论它们的(子)类型如何。如果子类型是DocumentLike,我只想要链接到具有.IsCurrent == true 的文档的对象。

【问题讨论】:

  • 如果您只想加载一种类型,请不要添加所有这些包含。使用_context.Set&lt;Like&gt;().OfType&lt;DocumentLike&gt;() 仅加载此类型的实例,但首先阅读client evaluation。如果 EF Core 不支持该运算符,它可以将所有内容加载到内存中,然后尝试过滤数据。确保在配置中添加 .ConfigureWarnings(warnings =&gt; warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); 以防止这种情况发生
  • UPDATE 本质上是询问如何过滤特定类型的孩子
  • 嗯,它实际上询问了如何根据仅在继承类中可用的属性来过滤对象。
  • 条件应该是x is not DocumentLike or it is and Document.IsCurrent is true。你可以试试.Where(x =&gt; !(x is DocumentLike) || ((DocumentLike)x).Document.IsCurrent == true)。如果IsCurrent 不可为空,则不需要与true 进行比较。不过我还没有尝试过,无论如何我都会使用配置警告来确保 EF 不会尝试在客户端对此进行评估
  • 谢谢,这似乎可以解决问题!

标签: c# .net-core entity-framework-core ef-core-2.1


【解决方案1】:

诀窍是稍微编辑谓词,如下所示:

.Where(x =&gt; !(x is DocumentLike) || ((DocumentLike)x).Document.IsCurrent == true)

感谢Panagiotis Kanavos 的建议。

【讨论】:

  • 谢谢,as 不起作用,但铸造可以做到。这很奇怪
【解决方案2】:

我在使用.OfType&lt;&gt;() 的多层层次结构中遇到了类似的问题,这会导致“过早”(在我看来)访问数据库以获取所有数据,因此它可以在内存中进行过滤,这是不可取的!

这说明了我的层次结构:

public abstract class BaseSetting {}
public abstract class AccountSetting : BaseSetting {}
public abstract class UserSetting : BaseSetting {}

public class AccountSettingA : AccountSetting {}
public class AccountSettingB : AccountSetting {}
public class UserSettingA : UserSetting {}
public class UserSettingB : UserSetting {}

这是DbContext的设置:

public class DataContext : DbContext
{
  public virtual DbSet<BaseSetting> Settings { get; set; }

  protected override void OnModelCreating(ModelBuilder builder)
  {
    base.OnModelCreating(builder);

    builder.Entity<BaseSetting>(e =>
    {
        e.ToTable("Settings");
        e.HasDiscriminator<string>("Type");
    });
  }
}

然后我会尝试获取单个帐户的所有设置,如下所示:

AccountSetting[] settings = context.Settings
    .OfType<AccountSetting>()
    .Where(s => s.Account.Id == accountId)
    .ToArray();

这会产生类似这样的 SQL 查询:

SELECT *
FROM [Settings] AS [s0]
WHERE [s0].[Type] IN (N'AccountSettingA',N'AccountSettingB',N'UserSettingA',N'UserSettingB')

之前是在查询的.Where(s =&gt; s.Account.Id == accountId) 位中抛出NullReferenceException,因为Account 为空。这可能会通过在查询中添加.Include(...) 以将Account 拉到其中来“修复”,但这只会增加我们从数据库中获取的过多数据量。 (应该注意的是,如果您根据@PanagiotisKanavos 对原始问题的评论将上下文配置为在尝试评估客户端时抛出错误,那么您将在此处获得QueryClientEvaluationWarning)。

解决方案(至少对我而言)是将其添加到我的 DbContext 中的 OnModelCreating 方法中:

typeof(BaseSetting).Assembly.GetTypes()
  .Where(t => t != typeof(BaseSetting) && typeof(BaseSetting).IsAssignableFrom(t))
  .Each(s => builder.Entity(s).HasBaseType(s.BaseType));

这将遍历我所有不同的设置类(继承自BaseSetting)并告诉实体框架它们的基本类型是它们的Type.BaseType。我原以为 EF 可以自己解决这个问题,但是在这样做之后,我得到了这样的 SQL(并且没有 QueryClientEvaluationWarning 异常!):

SELECT *
FROM [Settings] as [a]
INNER JOIN [Accounts] AS [a.Account] ON [a].[AccountId] = [a.Account].[Id]
WHERE ([a].[Type] IN (N'AccountSettingA',N'AccountSettingB',N'UserSettingA',N'UserSettingB')
AND ([a.Account].[Id] = @__accountId)

这显然只返回我感兴趣的帐户的帐户设置,而不是所有帐户设置和所有用户设置 像以前一样。

【讨论】:

    【解决方案3】:

    您可以使用 Enumerable.OfType 来过滤类型。欲了解更多信息,您可以查看https://docs.microsoft.com/de-de/dotnet/api/system.linq.enumerable.oftype?redirectedfrom=MSDN&view=netcore-2.1

    对于你的情况,你可以简单地过滤你的结果

    var documentLikes = query.OfType<DocumentLike>();
    

    【讨论】:

    • 使用Enumerable.OfType 必须从数据库中加载所有内容,以便在内存中过滤结果。不幸的是,EF Core如果不能生成对应的SQL语句,往往会在内存中加载数据
    • 你确定吗?因为我可以看到还有 IQueryable.OfType 方法返回 IQuerayable。 docs.microsoft.com/en-us/dotnet/api/…
    • 当您发布关于 IEnumerable 时,这是 IQueryable
    • IQueryable 正在实现 IEnumerable;所以在 OfType 可以进行所需的过滤之前,我没有看到您的数据点被加载到内存中。只有在 IEnumerable 或 IQueryable 之上调用 ToList() 或 ToArray() 或显式枚举列表时,数据才会加载到内存中。
    • 顺便说一句,您发布的链接中已经解释了 - OfType 链接指向 Queryable 静态类的 OfType 方法
    猜你喜欢
    • 1970-01-01
    • 2019-02-11
    • 2019-02-22
    • 1970-01-01
    • 2018-11-18
    • 2022-11-17
    • 2021-03-06
    • 1970-01-01
    • 2020-10-19
    相关资源
    最近更新 更多