简体   繁体   English

C# 实体框架:如何结合 a.Find 和.Include 在 Model Object 上?

[英]C# Entity-Framework: How can I combine a .Find and .Include on a Model Object?

I'm doing the mvcmusicstore practice tutorial.我正在做 mvcmusicstore 练习教程。 I noticed something when creating the scaffold for the album manager (add delete edit).在为相册管理器创建脚手架时我注意到了一些东西(添加删除编辑)。

I want to write code elegantly, so i'm looking for the clean way to write this.我想优雅地编写代码,所以我正在寻找一种干净的方式来编写它。

FYI i'm making the store more generic:仅供参考,我正在使商店更通用:

Albums = Items相册 = 项目

Genres = Categories流派 = 类别

Artist = Brand艺术家=品牌

Here is how the index is retrieved (generated by MVC):以下是索引的检索方式(由 MVC 生成):

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

Here is how the item for delete is retrieved:以下是如何检索要删除的项目:

Item item = db.Items.Find(id);

The first one brings back all the items and populates the category and brand models inside the item model. The second one, doesn't populate the category and brand.第一个带回所有项目并填充项目 model 内的类别和品牌模型。第二个不填充类别和品牌。

How can i write the second one to do the find AND populate whats inside (preferably in 1 line)... theoretically - something like:我怎样才能写第二个来查找和填充里面的内容(最好是在 1 行中)...理论上 - 类似于:

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

You can use Include() first, then retrieve a single object from the resulting query:您可以先使用Include() ,然后从结果查询中检索单个对象:

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .FirstOrDefault(x => x.ItemId == id);

Dennis' answer is using Include and SingleOrDefault .丹尼斯的回答是使用IncludeSingleOrDefault The latter goes round-tripping to database.后者往返于数据库。

An alternative, is to use Find , in combination with Load , for explicit loading of related entities...另一种方法是将FindLoad结合使用,以显式加载相关实体...

Below an MSDN example :MSDN 示例下面:

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

Of course, Find returns immediately without making a request to the store, if that entity is already loaded by the context.当然,如果上下文已经加载了该实体,则Find立即返回而不向商店发出请求。

There's no real easy way to filter with a find.没有真正简单的方法可以通过查找进行过滤。 But I've come up with a close way to replicate the functionality but please take note of a few things for my solution.但是我想出了一种复制功能的接近方法,但请注意我的解决方案的一些注意事项。

This Solutions allows you to filter generically without knowning the primary key in .net-core此解决方案允许您在不知道 .net-core 中的主键的情况下进行一般过滤

  1. Find is fundamentally different because it obtains the the entity if it's present in the tracking before Querying the database. Find 是根本不同的,因为它在查询数据库之前获取实体,如果它存在于跟踪中。

  2. Additionally It can filter by an Object so the user does not have to know the primary key.此外,它可以按对象过滤,因此用户不必知道主键。

  3. This solution is for EntityFramework Core.此解决方案适用于 EntityFramework Core。

  4. This requires access to the context这需要访问上下文

Here are some extension methods to add which will help you filter by primary key so这里有一些要添加的扩展方法,它们将帮助您按主键进行过滤,因此

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

Once you have these extension methods you can filter like so:一旦你有了这些扩展方法,你就可以像这样过滤:

query.FilterByPrimaryKey(this._context, id);

Didnt work for me.没有为我工作。 But I solved it by doing like this.但我通过这样做解决了它。

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

Dont know if thats a ok solution.不知道那是否是一个好的解决方案。 But the other one Dennis gave gave me a bool error in .SingleOrDefault(x => x.ItemId = id);但是另一个丹尼斯给了我一个布尔错误.SingleOrDefault(x => x.ItemId = id);

In this scenario you must use DbSet<T>.Local .在这种情况下,您必须使用DbSet<T>.Local

You cannot combine DbSet<T>.Find(object[] params) to do what you want because it will query the database if the entity is not currently attached and tracked by the context.您不能组合DbSet<T>.Find(object[] params)来执行您想要的操作,因为如果实体当前未被上下文附加和跟踪,它将查询数据库。

Implementations of DbSet<T>.SingleOrDefault<T> , DbSet<T>.FirstOrDefault<T> and related methods will also query the database immediately upon invocation. DbSet<T>.SingleOrDefault<T>DbSet<T>.FirstOrDefault<T>和相关方法的实现也将在调用时立即查询数据库。

Assuming you have type MyEntity with property Id returning int you could create a method like the following, or adapt it to meet your specific need.假设您的MyEntity类型具有返回int的属性Id ,您可以创建如下所示的方法,或对其进行调整以满足您的特定需求。

public MyEntity FindLocalOrRemote(int id)
{
    MyEntity entity = 

        context.MyEntities
               .Local
               .SingleOrDefault(p => p.Id == id) 

        ?? 

        context.MyEntities
               .Include(p => p.PackItems)
               .SingleOrDefault(p => p.PackId == id);
        
    return entity;
}

A drawback of this approach, and quite possibly why there is no built-in method for this, might be due to the challenge of designing an API around key values or because using DbSet<T>.Local there is no guarantee that the attached and tracked entity has the related navigation property populated from the database.这种方法的一个缺点,很可能为什么没有内置方法,可能是由于围绕键值设计 API 的挑战,或者因为使用DbSet<T>.Local不能保证附加的和被跟踪的实体具有从数据库中填充的相关导航属性。

This question is really old, but not a single person gave either a simple or correct answer to the question.这个问题真的很老,但是没有一个人对这个问题给出简单或正确的答案。

This would work with Entity Framework 6 or Entity Framework Core.这适用于 Entity Framework 6 或 Entity Framework Core。

您必须将 IQueryable 转换为 DbSet

var dbSet = (DbSet<Item>) db.Set<Item>().Include("");

return dbSet.Find(id);

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

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