Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
551 views
in Technique[技术] by (71.8m points)

entity framework - EF object comparison with generic types

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...