【问题标题】:DbSet doesn't have a Find method in EF7DbSet 在 EF7 中没有 Find 方法
【发布时间】:2015-05-15 19:56:55
【问题描述】:

我正在尝试创建一个通用存储库来访问我的数据库。在 EF6 中,我能够这样做以获得特定实体:

protected IDbSet<T> dbset;

public T Get(object id)
{
    return this.dbset.Find(id);
}

EF7 中的 DbSet 缺少 Find 方法。有没有办法实现上面这段代码?

【问题讨论】:

标签: c# entity-framework-core


【解决方案1】:

这是.Find() 作为扩展方法的一个非常粗略、不完整且未经测试的实现。如果不出意外,它应该能让你指向正确的方向。

真正的实现由#797跟踪。

static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues)
    where TEntity : class
{
    var context = ((IAccessor<IServiceProvider>)set).Service.GetService<DbContext>();

    var entityType = context.Model.GetEntityType(typeof(TEntity));
    var key = entityType.GetPrimaryKey();

    var entries = context.ChangeTracker.Entries<TEntity>();

    var i = 0;
    foreach (var property in key.Properties)
    {
        var keyValue = keyValues[i];
        entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValue);
        i++;
    }

    var entry = entries.FirstOrDefault();
    if (entry != null)
    {
        // Return the local object if it exists.
        return entry.Entity;
    }

    // TODO: Build the real LINQ Expression
    // set.Where(x => x.Id == keyValues[0]);
    var parameter = Expression.Parameter(typeof(TEntity), "x");
    var query = set.Where((Expression<Func<TEntity, bool>>)
        Expression.Lambda(
            Expression.Equal(
                Expression.Property(parameter, "Id"),
                Expression.Constant(keyValues[0])),
            parameter));

    // Look in the database
    return query.FirstOrDefault();
}

【讨论】:

  • 嗨 Brice,只是好奇你是否有这个的更新版本。我坚持var entityType = context.Model.GetEntityType(typeof(TEntity));;因为 context 没有 Model 属性。我已将 var context = ((IAccessor&lt;IServiceProvider&gt;)set).Service.GetService&lt;DbContext&gt;(); 更改为 var context = ((IAccessor&lt;IServiceProvider&gt;)set).Service.GetService(typeof(DbContext));
  • @LynnCrumbling 我不认为Model 自从我们把它放在那里之后就没有移动过;它仍然在夜间构建中。要获取GetService 的通用版本,请添加using Microsoft.Framework.DependencyInjection;
  • 非常感谢!我在Microsoft.Data.Entity.Infrastructure 中找到了通用的IAccessor,但这更有意义,因为我需要使用返回的不仅仅是一个对象的GetService()。干杯!
  • 如果你的id字段不是Id,把Expression.Property(parameter, "Id"),改成Expression.Property(parameter, key.Properties[0].Name),
  • @LynnCrumbling 我在Microsoft.Data.Entity.Infrastructure 或其他任何地方都找不到IAccessor 泛型。我错过了一些参考吗?
【解决方案2】:

如果您使用的是EF 7.0.0-rc1-final,您可以在下面找到@bricelam 在上一个答案中提供的代码的小更新。顺便说一句,非常感谢@bricelam - 你的代码对我非常有用。

这是我在“project.config”下的依赖项

"dependencies": {
    "EntityFramework.Commands": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta8",
    "Microsoft.Framework.ConfigurationModel": "1.0.0-beta4",
    "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4",
    "Microsoft.Framework.DependencyInjection": "1.0.0-beta8"
}

以下是DbSet.Find(TEntity)的扩展方法:

using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Microsoft.Data.Entity.Extensions
{
    public static class Extensions
    {
        public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
        {
            var context = ((IInfrastructure<IServiceProvider>)set).GetService<DbContext>();

            var entityType = context.Model.FindEntityType(typeof(TEntity));
            var key = entityType.FindPrimaryKey();

            var entries = context.ChangeTracker.Entries<TEntity>();

            var i = 0;
            foreach (var property in key.Properties)
            {
                entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
                i++;
            }

            var entry = entries.FirstOrDefault();
            if (entry != null)
            {
                // Return the local object if it exists.
                return entry.Entity;
            }

            // TODO: Build the real LINQ Expression
            // set.Where(x => x.Id == keyValues[0]);
            var parameter = Expression.Parameter(typeof(TEntity), "x");
            var query = set.Where((Expression<Func<TEntity, bool>>)
                Expression.Lambda(
                    Expression.Equal(
                        Expression.Property(parameter, "Id"),
                        Expression.Constant(keyValues[0])),
                    parameter));

            // Look in the database
            return query.FirstOrDefault();
        }
    }
}

【讨论】:

  • 谢谢,这是我需要的:)
  • 建议:你有一个修改后的闭包的捕获,这使得这个中断。您应该将foreach 循环更改为:var i1 = i; entries = entries.Where(e =&gt; e.Property(property.Name).CurrentValue == keyValues[i1]);i++
【解决方案3】:

由于声誉问题无法发表评论,但如果您使用 RC2(或更高版本?),您应该使用

var context = set.GetService<ICurrentDbContext>().Context;

而不是

var context = set.GetService<DbContext>();

【讨论】:

  • 即使这似乎是一个肮脏的黑客,使用了我不应该使用的东西 (ICurrentDbContext),它在它的父级肮脏黑客中运行良好。这只是你应该把答案放在哪里,而不是在评论中。这太棒了。
  • 这为我修复了它,这是我需要的,直到 Find 在 EF7 中正确实施
【解决方案4】:

我采用了之前提供的一些答案并对其进行了调整以解决几个问题:

  • 隐式捕获的闭包
  • 不应将密钥硬编码为“Id”

    public static TEntity Find<TEntity>(this DbSet<TEntity> set, params object[] keyValues) where TEntity : class
    {
        var context = set.GetService<DbContext>();
    
        var entityType = context.Model.FindEntityType(typeof(TEntity));
        var key = entityType.FindPrimaryKey();
    
        var entries = context.ChangeTracker.Entries<TEntity>();
    
        var i = 0;
        foreach (var property in key.Properties)
        {
            var i1 = i;
            entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i1]);
            i++;
        }
    
        var entry = entries.FirstOrDefault();
        if (entry != null)
        {
            // Return the local object if it exists.
            return entry.Entity;
        }
    
        var parameter = Expression.Parameter(typeof(TEntity), "x");
        var query = set.AsQueryable();
        i = 0;
        foreach (var property in key.Properties)
        {
            var i1 = i;
            query = query.Where((Expression<Func<TEntity, bool>>)
             Expression.Lambda(
                 Expression.Equal(
                     Expression.Property(parameter, property.Name),
                     Expression.Constant(keyValues[i1])),
                 parameter));
            i++;
        }
    
        // Look in the database
        return query.FirstOrDefault();
    }
    

【讨论】:

    【解决方案5】:

    在 Entity Framework 核心中找到 finally arrives

    【讨论】:

    • 酷!但是我怎样才能将它添加到项目中呢? nuget 似乎没有更新...
    • 测试失败,构建成功后移到stage,然后出现在nuget中,我是guest。
    • @PavelBiryukov 您必须从他们的 MyGet 提要中获取预发布版。
    • @ShaulBehr 如何从他们的 MyGet 源安装预发布版?
    • 我找到了这个链接,但是 PM 命令不适用于我的 Visual Studio dotnet.myget.org/feed/aspnetcore-dev/package/nuget/…
    【解决方案6】:

    所以...上面的 find 方法效果很好,但是如果您的模型中没有名为“Id”的列,那么整个事情将在下一行失败。我不确定为什么 OP 会将硬编码值放入该位置

      Expression.Property(parameter, "Id"),
    

    这是一个修订版,它将为那些正确命名我们的 Id 列的人修复它。 :)

    var keyCompare = key.Properties[0].Name;
    
            // TODO: Build the real LINQ Expression
            // set.Where(x => x.Id == keyValues[0]);
            var parameter = Expression.Parameter(typeof(TEntity), "x");
            var query = set.Where((Expression<Func<TEntity, bool>>)
                Expression.Lambda(
                    Expression.Equal(
                        Expression.Property(parameter, keyCompare),
                        //Expression.Property(parameter, "Id"),
                        Expression.Constant(keyValues[0])),
                    parameter));
    
            // Look in the database
            return query.FirstOrDefault();
        }
    

    如果您在实体对象上设置了多个 Key 并且您正在查找的键不是第一个,这仍然很可能会失败,但这样应该会更好一些。

    【解决方案7】:

    没有足够的声誉可以发表评论,但在控制台应用程序/单独程序集中使用@Roger-Santana 答案时存在错误:

    var i = 0;
    foreach (var property in key.Properties)
    {
        entries = entries.Where(e => e.Property(property.Name).CurrentValue == keyValues[i]);
        i++;
    }
    var entry = entries.FirstOrDefault();
    

    'i' 的值在 foreach 中被捕获,因此当调用 entries.FirstOrDefault() 时,keyValues[i] 具有(至少)keyValues[i++] 的值,在我的情况下它因输出而崩溃的索引错误。 解决方法是通过循环复制“i”的值:

    var i = 0;
    foreach (var property in key.Properties)
    {
       var idx =i;
        entries = entries.Where(e =>  e.Property(property.Name).CurrentValue == keyValues[idx]);
        i++;
    }
    var entry = entries.FirstOrDefault();
    

    【讨论】:

    • 谢谢,谢谢,谢谢。当我遇到这个错误时,我并不期待解决它。立即解决,我不必动脑筋。非常感谢。
    【解决方案8】:

    我用linq;您可以使用 Find 方法代替:

    var record = dbSet.SingleOrDefault(m => m.Id == id)
    

    【讨论】:

    • 这与Find不同,Find可以返回尚未提交到数据库的实体,即在你调用SaveChanges之前。
    【解决方案9】:

    让我贡献一个包含构建表达式的修订。 我承认我实际上并没有测试这个;-)

        public static TEntity Find<TEntity>(this DbSet<TEntity> dbSet, params object[] keyValues) where TEntity : class
        {
            // Find DbContext, entity type, and primary key.
            var context = ((IInfrastructure<IServiceProvider>)dbSet).GetService<DbContext>();
            var entityType = context.Model.FindEntityType(typeof(TEntity));
            var key = entityType.FindPrimaryKey();
    
            // Build the lambda expression for the query: (TEntity entity) => AND( entity.keyProperty[i] == keyValues[i])
            var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
            Expression whereClause = Expression.Constant(true, typeof(bool));
            uint i = 0;
    
            foreach (var keyProperty in key.Properties) {
                var keyMatch = Expression.Equal(
                    Expression.Property(entityParameter, keyProperty.Name),
                    Expression.Constant(keyValues[i++])
                );
    
                whereClause = Expression.And(whereClause, keyMatch);
            }
    
            var lambdaExpression = (Expression<Func<TEntity,bool>>)Expression.Lambda(whereClause, entityParameter);
    
            // Execute against the in-memory entities, which we get from ChangeTracker (but not filtering the state of the entities).
            var entries = context.ChangeTracker.Entries<TEntity>().Select((EntityEntry e) => (TEntity)e.Entity);
            TEntity entity = entries.AsQueryable().Where(lambdaExpression).First(); // First is what triggers the query execution.
    
            // If found in memory then we're done.
            if (entity != null) { return entity; }
    
            // Otherwise execute the query against the database.
            return dbSet.Where(lambdaExpression).First();
        }
    

    【讨论】:

      【解决方案10】:

      这是我使用的。 不是一种查找方法,但就像一个魅力

      var professionalf = from m in _context.Professionals select m;
      professionalf = professionalf.Where(s => s.ProfessionalId == id);
      Professional professional = professionalf.First();
      

      【讨论】:

      • 我确信 OP 知道 FirstFind 在很多方面都完全不同。
      【解决方案11】:

      在我之前的帖子的最后一行中,有人建议将“.First()”更改为“.FirstOrDefault()”。编辑被否决,但我同意。如果找不到密钥,我希望该函数返回 null 。我不希望它抛出异常。在大多数情况下,我想知道密钥是否存在于集合中,处理异常是一种非常缓慢的解决方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-28
        • 2017-12-23
        • 1970-01-01
        • 2017-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-13
        相关资源
        最近更新 更多