c#实体框架:如何在模型对象上组合.find和.include

本文关键字:对象 组合 find include 模型 框架 实体 | 更新日期: 2023-09-27 18:08:03

我正在做mvcmusicstore练习教程。在为专辑管理器创建脚手架(添加删除编辑)时,我注意到一些事情。

我想写优雅的代码,所以我正在寻找一个干净的方式来写这个。

仅供参考,我正在使商店更通用:

Albums = Items

genre = Categories

Artist = Brand

这是如何检索索引(由MVC生成):

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

要删除的条目是这样被检索的:

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

第一个返回所有项目,并在项目模型中填充类别和品牌模型。第二个,没有填充品类和品牌。

我怎么能写第二个做查找和填充里面的东西(最好在1行)…理论上-类似于:

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

c#实体框架:如何在模型对象上组合.find和.include

您可以先使用Include(),然后从结果查询中检索单个对象:

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

Dennis的答案是使用IncludeSingleOrDefault。后者往返于数据库。

另一种选择是将FindLoad结合使用,以显式加载相关实体…

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(); 
}

当然,如果该实体已经被上下文加载,那么Find将立即返回,而无需向store发出请求。

没有真正简单的方法来过滤查找。但我已经想出了一个接近的方法来复制功能,但请注意我的解决方案的一些事情。

此解决方案允许您在不知道.net-core

中的主键的情况下进行通用过滤。
  1. Find从根本上是不同的,因为它在查询数据库之前获得跟踪中存在的实体。

  2. 另外,它可以通过对象进行过滤,这样用户就不必知道主键了

  3. 这个解决方案是为EntityFramework Core。

  4. 这需要访问上下文

这里有一些扩展方法可以添加,这将帮助您通过主键进行过滤,所以

    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));
    }

一旦你有了这些扩展方法,你可以这样过滤:

query.FilterByPrimaryKey(this._context, id);

不适合我。但是我是这样解决的。

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

不知道这是不是一个好的解决方案。但是Dennis给出的另一个在.SingleOrDefault(x => x.ItemId = id);

中出现bool错误

在这种情况下,您必须使用DbSet<T>.Local

你不能组合DbSet<T>.Find(object[] params)做你想做的事情,因为如果实体当前没有被上下文附加和跟踪,它将查询数据库。

DbSet<T>.SingleOrDefault<T>DbSet<T>.FirstOrDefault<T>和相关方法的实现也会在调用时立即查询数据库。

假设您有类型MyEntity,属性Id返回int,您可以创建如下方法,或调整它以满足您的特定需求。

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

这种方法的一个缺点,很可能是没有内置方法的原因,可能是由于围绕键值设计API的挑战,或者因为使用DbSet<T>.Local不能保证附加和跟踪的实体具有从数据库填充的相关导航属性。

这个问题真的很老了,但是没有一个人给出简单或正确的答案。

这将适用于实体框架6或实体框架核心

必须将IQueryable类型转换为DbSet

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

return dbSet.Find(id);