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

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

问题描述

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

  public class EntityBase< TKey>其中TKey:struct,IComparable 
{
public virtual TKey Id {get;组; }
}




  • 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,for例如,我得到以下错误:



    无法将类型System.Int32转换为类型System.Object。



    Repository.Get只是将predicate参数作为过滤器传递给DbSet存储库的Where调用;

    我理解错误 - EF试图翻译成SQL语句,不知道如何处理对象比较。但我不知道如何重写基类和/或LoadEntity()函数,以允许EF与原始类型操作。
    任何想法?

    解决方案

    我认为有一个简单的方法,但它是一个 / strong>。让我再次强调一下 - 它真的是一个黑客。您可以尝试:

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

    上面的代码一般不起作用。在CLR世界中,值将被加框,并通过引用进行比较。即使装箱的值相同,引用也会不同,因此结果将是假的。但是EF查询不是由CLR执行,而是转换为SQL。因此,查询将被翻译成类似: WHERE Id = {EntityId} 这是你需要的。再次,使用这需要了解如何和为什么这个东西工作,可能有点冒险。然而,由于有一个黑客,应该有一个更清洁的解决方案。事实上,干净(这里不容易的解决方案)是手动构建上述表达式。这里是一个例子(对不起,我没有使用你的实体):

      private static TEntity GetEntity< TEntity,TKey> ; Func 其中TKey:struct 
    其中TEntity:BaseEntity< TKey>
    {
    using(var ctx = new Context2())
    {
    var query = Filter(ctx.Set< TEntity>(),property,keyValue);
    return query.First();
    }
    }


    private static IQueryable< TEntity>过滤器< TEntity,TProperty>(IQueryable< TEntity> dbSet,
    Expression< Func< TEntity,TProperty> property,
    TProperty value)
    其中TProperty:struct
    {

    var memberExpression = property.Body as MemberExpression;
    if(memberExpression == null ||!(memberExpression.Member是PropertyInfo))
    {
    throw new ArgumentException(Property expected,property);
    }

    表达式left = property.Body;
    表达式right = Expression.Constant(value,typeof(TProperty));

    表达式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()。其中​​(e => e.Id == idValue).First()(看起来类似于上面的hack),但是你可以使用其他linq运算符(包括对Filter方法的结果调用Filter方法以按多个条件过滤)



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

      public class BaseEntity< TKey>其中TKey:struct 
    {
    public TKey Id {get;组; }
    }

    public class EntityWithIntKey:BaseEntity< int>
    {
    public string Name {get;组; }
    }

    public class EntityWithGuidKey:BaseEntity< Guid>
    {
    public string Name {get;组; }
    }

    public class Context2:DbContext
    {
    public DbSet< EntityWithIntKey> EntitiesWithIntKey {get;组; }

    public DbSet< EntityWithGuidKey> EntitiesWithGuidKey {get;组; }
    }

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


    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 will be provided as Int or Guid in derived classes.

    When i run the code

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

    or

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

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

    Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types.

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

    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?

    解决方案

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

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

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

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

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

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