简体   繁体   中英

Getting errors when using Equals on generic parameters with EF

I have the following two generic classes:

 public abstract class Entity<T> : IEntity<T>, IAuditableEntity,ISoftDeletable where T : struct
    {
        public T Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public string CreatedBy { get; set; }
        public DateTime UpdatedDate { get; set; }
        public string UpdatedBy { get; set; }
        public bool Deleted { get; set; }
    }


    public abstract class Repository<TEntity,TKey> : IRepository<TEntity, TKey> where TEntity :Entity<TKey> where TKey : struct           
    {
        protected DbContext Context;
        protected readonly IDbSet<TEntity> Set;

        protected Repository(DbContext context)
        {
            Context = context;
            Set = Context.Set<TEntity>();
        }

        public TEntity Get(TKey key)
        {               
            var output = Set.FirstOrDefault(o => o.Id.Equals(key));
            return output ;
        }
    }

My problem is the Get method. I keep getting this error

Unable to create a constant value of type 'System.Object'. Only primitive types or enumeration types are supported in this context."

i tried to use == but it wont compile and says

cannot apply == to operands TKey and TKey

Why isnt this working? my TKey is supposed to be a primitive data type, isnt the limiter where TKey:struct the correct one?

Why is the compiler using Equals(object) when there is Equals(int), which is what this key is?

The == comparison fails because that operator can only be used for predefined value types or for reference types, and your TKey is neither.

You say in your comment that TKey is an int . That may be true in your case, but a user of your class(es) could define an Entity<List<string>.Enumerator> since List<T>.Enumerator is a struct . The mind boggles at what "equals" means in that case.

My point is that the compiler has no way to know, at compile time , how to do anything other than use object.Equals .

I wonder why you're constraining the key type to be a struct anyway. The most common types of ID that I've seen are int and string . By using a struct you're already ruling out string ...

Do you really need the flexibility to support keys that are not integers? If not, your classes could be much simpler.


Update : you could constrain your TKey such that it implements IComparable , since all the numeric types implement that. That would then let you use CompareTo within the implementation of your Get method.

However, since the lambda you pass to FirstOrDefault will actually be executed on the database side, I'm not sure what will happen, ie whether EF will be able to translate that to a SQL expression correctly.

public abstract class Entity<T> : IEntity<T>, IAuditableEntity,ISoftDeletable where T : struct, IComparable

public abstract class Repository<TEntity,TKey> : IRepository<TEntity, TKey> where TEntity :Entity<TKey> where TKey : struct, IComparable

You could simply use the IDbSet.Find method, which takes one or more object -typed key values.

public TEntity Get(TKey key)
{               
    return Set.Find(key);
}

This has the additional advantage that it's more efficient if the entity already exists in the DbContext 's local cache (since it avoids an unnecessary trip to the database).

However, I suspect you're going to want to do more than just retrieve individual items, so you'll probably run into the same problem later.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM