简体   繁体   English

使用 EF Core 2.1 继承进行过滤

[英]Filtering with EF Core 2.1 inheritance

I'm trying to find a way to filter my results in EF Core 2.1, when using inherited objects.当使用继承的对象时,我试图找到一种方法来过滤 EF Core 2.1 中的结果。

I've got a base model and several inherited classes (but I've just included one):我有一个基本模型和几个继承的类(但我只包含了一个):

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 is an enum which is defined as the discriminator in the dbcontext. LikeType是一个枚举,它被定义为 dbcontext 中的鉴别器。 Every Document has a boolean property .IsCurrent .每个Document都有一个布尔属性.IsCurrent

To get all items from the database, I'm using a query like:要从数据库中获取所有项目,我正在使用如下查询:

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)

This works beautifully, and returns all objects with the included sub-objects without any error.这工作得很好,并返回包含子对象的所有对象,没有任何错误。 What I'm trying to do, is to get all items from the database for which the linked document has .IsCurrent == true .我想要做的是从数据库中获取链接文档具有.IsCurrent == true所有项目。 I've tried adding the following to the query above, but both result in an exception:我尝试将以下内容添加到上面的查询中,但都导致异常:

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

And:和:

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

The exception, which is thrown when I'm executing the query:执行查询时抛出的异常:

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)

Is there a way to do this?有没有办法做到这一点?

UPDATE: To clarify: I'm looking to get a single query that returns all Like -objects from the database, regardless of their (sub)types.更新:澄清:我希望获得一个查询,该查询从数据库中返回所有Like对象,而不管它们的(子)类型如何。 In case the subtype is DocumentLike , I only want the objects that are linked to a document that has .IsCurrent == true .如果子类型是DocumentLike ,我只想要链接到具有.IsCurrent == true的文档的对象。

The trick was to edit the predicate a bit, like this:诀窍是稍微编辑谓词,如下所示:

.Where(x => !(x is DocumentLike) || ((DocumentLike)x).Document.IsCurrent == true)

Thanks to Panagiotis Kanavos for the suggestion.感谢Panagiotis Kanavos的建议。

I had a similar problem with a multi-layer hierarchy of classes where using .OfType<>() was causing a "premature" (in my opinion) trip to the database to fetch all of the data so it could do the filtering in memory, which is undesirable!我在类的多层层次结构中遇到了类似的问题,其中使用.OfType<>()导致“过早”(在我看来)数据库之旅以获取所有数据,因此它可以在内存中进行过滤,这是不可取的!

This illustrates my hierarchy:这说明了我的层次结构:

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 {}

And this is the set up for the DbContext :这是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");
    });
  }
}

Then I would try and get all the settings for a single account like this:然后我会尝试获取单个帐户的所有设置,如下所示:

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

This results in a SQL query something like this:这会导致 SQL 查询如下所示:

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

just before is throws a NullReferenceException in the .Where(s => s.Account.Id == accountId) bit of the query because Account is null.就在之前,在查询的.Where(s => s.Account.Id == accountId)位中抛出NullReferenceException ,因为Account为空。 This could probably be "fixed" by adding a .Include(...) to the query to pull the Account through too, but that will just add to the excessive amount of data we're getting from the database.这可能可以通过在查询中添加.Include(...)来“修复”以拉出Account ,但这只会增加我们从数据库中获取的过多数据。 (It should be noted that if you configure the context to throw errors when trying to evaluate on the client as per @PanagiotisKanavos's comment on the original question, then you will get a QueryClientEvaluationWarning here instead). (应该注意的是,如果根据QueryClientEvaluationWarning对原始问题的评论,在尝试对客户端进行评估时将上下文配置为抛出错误,那么您将在此处获得QueryClientEvaluationWarning )。

The solution (at least for me) was to add this to the OnModelCreating method in my DbContext :解决方案(至少对我而言)是将其添加到我的DbContextOnModelCreating方法中:

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

This will go through all my different settings classes (that inherit from BaseSetting ) and tell Entity Framework that their base type is their Type.BaseType .这将遍历我所有不同的设置类(从BaseSetting继承)并告诉实体框架它们的基类型是它们的Type.BaseType I would have thought that EF could work this out on it's own, but after doing this I get SQL like this (and no QueryClientEvaluationWarning exceptions!):我原以为 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)

Which obviously only returns the account settings for the account I'm interested in, rather than all the account settings and all of the user settings like it was before.这显然只返回我感兴趣的帐户,而不是所有的帐户设置和所有的用户设置,就像是之前的帐户设置。

You can use Enumerable.OfType to filter types.您可以使用 Enumerable.OfType 来过滤类型。 For more information you can have a look at https://docs.microsoft.com/de-de/dotnet/api/system.linq.enumerable.oftype?redirectedfrom=MSDN&view=netcore-2.1有关更多信息,您可以查看https://docs.microsoft.com/de-de/dotnet/api/system.linq.enumerable.oftype?redirectedfrom=MSDN&view=netcore-2.1

And for your case, you can simply filter your result by对于您的情况,您可以简单地通过以下方式过滤结果

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM