EF 对象与泛型类型的比较 [英] EF object comparison with generic types

查看:19
本文介绍了EF 对象与泛型类型的比较的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下问题:我有一个使用为 Int 或 Guid 实体键设计的 DbContext 的通用 EF 存储库,所以我有一个基本实体类:

I have the following problem: I have a generic EF repository using DbContext designed for Int or Guid entity keys, so i have a base entity class:

public class EntityBase<TKey> where TKey : struct, IComparable
{
    public virtual TKey Id { get; set; }
}

  • TKey 将在派生类中作为 Int 或 Guid 提供.
  • 当我运行代码时

    public virtual void LoadEntity()
    {
        TEntity entity = Repository.Get<TEntity, TKey>(e => object.Equals(e.Id, EntityId));
    }
    

    public virtual void LoadEntity()
    {
        TEntity entity = Repository.Get<TEntity, TKey>(e => e.Id.CompareTo(EntityId) == 0);
    }
    

    其中 Entity 的类型为 TKey 并且在派生类中设置为 int,例如,我收到以下错误:

    where Entity is of type TKey and is set in derived classes as int, for example, I get the following error:

    无法将System.Int32"类型转换为System.Object"类型.LINQ to Entities 仅支持强制转换实体数据模型原始类型.

    Repository.Get 只传递谓词参数作为 DbSet 存储库的 Where 调用的过滤器;

    Repository.Get just pass predicate parameter as a filter for a Where call for DbSet repository;

    我理解错误 - EF 尝试转换为 SQL 语句,但不知道如何处理对象比较.但我不知道如何重写基类和/或 LoadEntity() 函数以允许 EF 使用原始类型进行操作.有什么想法吗?

    I understand the error - EF tries to translate to SQL statement and does not know how to treat the object comparison. But I don't know how to rewrite base class and/or LoadEntity() function to allow EF to operate with primitive types. Any ideas?

    推荐答案

    我认为有一个简单的方法可以解决它,但它是一个hack.让我再次强调 - 这真的是一个hack.你可以试试这个:

    I think there is an easy way around it but it is a hack. Let me stress it again - it really is a hack. You can try this:

    Repository.Get<TEntity, TKey>(e => (object)e.Id == (object)EntityId);
    

    上面的代码一般不应该工作.在 CLR 世界中,这些值将被装箱并通过引用进行比较.即使装箱值相同,引用也会不同,因此结果将为假.但是,EF 查询不是由 CLR 执行而是转换为 SQL.因此,查询将被转换为类似:WHERE Id = {EntityId} 这正是您所需要的.同样,使用它需要了解这些东西如何以及为什么起作用,并且可能有点冒险.但是,由于存在黑客攻击,因此应该有一个更清洁的解决方案.事实上,干净的(这里不是简单的解决方案)是手动构建上述表达式.这是一个示例(抱歉,我没有完全使用您的实体):

    The code above in general should not work. In the CLR world the values would be boxed and will be compared by references. Even if the boxed values were the same the references would be different and therfore the result will be false. However EF queries are not executed by the CLR but translated to SQL. As a result the query will be translated to something like: WHERE Id = {EntityId} which is what you need. Again, using this requires understanding how and why this stuff works and is probably a bit risky. However since there is a hack there should be a cleaner solution. In fact the clean (and not easy solution here) is to build the above expression manually. Here is an example (Sorry I am not using exactly your entities):

        private static TEntity GetEntity<TEntity, TKey>(Expression<Func<TEntity, TKey>> property, TKey keyValue)
            where TKey : struct
            where TEntity : BaseEntity<TKey>
        {
            using (var ctx = new Context2())
            {
                var query = Filter(ctx.Set<TEntity>(), property, keyValue);
                return query.First();
            }
        }
    
    
        private static IQueryable<TEntity> Filter<TEntity, TProperty>(IQueryable<TEntity> dbSet,
                                                                      Expression<Func<TEntity, TProperty>> property,
                                                                      TProperty value)
            where TProperty : struct
        {
    
            var memberExpression = property.Body as MemberExpression;
            if (memberExpression == null || !(memberExpression.Member is PropertyInfo))
            {
                throw new ArgumentException("Property expected", "property");
            }
    
            Expression left = property.Body;
            Expression right = Expression.Constant(value, typeof (TProperty));
    
            Expression searchExpression = Expression.Equal(left, right);
            var lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(left, right),
                                                                new ParameterExpression[] {property.Parameters.Single()});
    
            return dbSet.Where(lambda);
        }
    

    请注意,在 Filter 方法中,我构建了一个可以组合的过滤器表达式.在这个例子中,有效查询看起来像这样 DbSet().Where(e => e.Id == idValue).First() (看起来类似于上面的 hack)但是你可以在这个查询之上使用其他 linq 运算符(包括对Filter方法的结果调用Filter方法进行多条件过滤)

    Note that in the Filter method I build a filter expression that I can compose on. In this example the effective query looks something like this DbSet().Where(e => e.Id == idValue).First() (looks similar to the hack above) but you can use other linq operators on top of this query (including invoking Filter method on the result of the Filter method to filter by multiple criteria)

    我将实体和上下文定义如下:

    I defined the entities and the context as follows:

    public class BaseEntity<TKey> where TKey : struct
    {
        public TKey Id { get; set; }
    }
    
    public class EntityWithIntKey : BaseEntity<int>
    {
        public string Name { get; set; }
    }
    
    public class EntityWithGuidKey : BaseEntity<Guid>
    {
        public string Name { get; set; }
    }
    
    public class Context2 : DbContext
    {
        public DbSet<EntityWithIntKey> EntitiesWithIntKey { get; set; }
    
        public DbSet<EntityWithGuidKey> EntitiesWithGuidKey { get; set; }
    }
    

    您像这样调用 GetEntity 方法:var e2 = GetEntity(e => e.Id, guidKey);

    You invoke the GetEntity method like this: var e2 = GetEntity(e => e.Id, guidKey);

    这篇关于EF 对象与泛型类型的比较的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆