16

I have this generic class, which uses Entity Framework 6.x.

public class GenericRepository<TEntity, TId> where TEntity, class, IIdentifyable<TId>
{
    public virtual TEntity GetById(TId id)
    {
        using (var context = new DbContext())
        {
            var dbSet = context.Set<TEntity>();
            var currentItem = dbSet.FirstOrDefault(x => x.Id == id);
            return currentItem;
        }
    }

    public virtual bool Exists(TId id)
    {
        using (var context = new DbContext())
        {
            var dbSet = context.Set<TEntity>();
            var exists = dbSet.Any(x => x.Id == id);
            return exists ;
        }
    }
}

And these interfaces:

public interface IIdentifyable : IIdentifyable<int>
{
}

public interface IIdentifyable<out TId>
{
    TId Id { get; }
}

And entities that looks like this:

public class CustomerEntity : IIdentifyable<int>
{
    public string Name { get; set; }
    public int Id { get;set; }
}

public class ProductEntity : IIdentifyable<Guid>
{
    public string Name { get; set; }
    public Guid Id { get;set; }
}

My issue is that it doesn't compile. I get this error:

Cannot apply operator '==' to operands of type 'TId' and 'TId'

I tried to change it to x => Equals(x.Id, id), but then EF cannot translate it. Any way around it?

I know that I can use Find() instead of FirstOrDefault. But I need this for more than the methods mentioned above. Is there any way I can let EF compare TId with TId? TId is currently only guid and int. I've already seen the questions below, but they don't handle the issue regarding translation to SQL.

Can't operator == be applied to generic types in C#?

How to solve Operator '!=' cannot be applied to operands of type 'T' and 'T'

Community
  • 1
  • 1
smoksnes
  • 10,509
  • 4
  • 49
  • 74

3 Answers3

20

Update: Here is a simple concise way that works with EF.

Add the following constraint to the GenericRepository class

where TId : IEquatable<TId>

and then use Equals method

x => x.Id.Equals(id);

Original answer:

This is a known issue with generics which normally is handled by using EqualityComparer<T>.Default instead of the == operator. However this approach doesn't work with LINQ to Entities.

One way to solve it is to build the predicate dynamically using the Expression class from the System.Linq.Expressions namespace like this:

public class GenericRepository<TEntity, TId> where TEntity: class, IIdentifyable<TId>
{
    protected static Expression<Func<TEntity, bool>> EqualsPredicate(TId id)
    {
        Expression<Func<TEntity, TId>> selector = x => x.Id;
        Expression<Func<TId>> closure = () => id;
        return Expression.Lambda<Func<TEntity, bool>>(
            Expression.Equal(selector.Body, closure.Body),
            selector.Parameters);
    }
}

and use it like this:

dbSet.FirstOrDefault(EqualsPredicate(id));

or

dbSet.Any(EqualsPredicate(id));

etc.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • When you say 'this is a known issue', do you have any reference? – huysentruitw Oct 23 '17 at 17:40
  • 1
    @HANDL Well, it's known to everyone trying it because of the compiler error :) And the reason is because `==` operator is not defined for every type - sort of explained in the [Compiler Error CS0019 documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0019). And the lack of generic operator constraint. But you are right, probably I've used incorrect wording. – Ivan Stoev Oct 23 '17 at 18:09
  • Nothing wrong with your wording, it's just that I've asked a similar question because I couldn't believe this wasn't working. But Eric Lippert already formulated this in an acceptable [answer](https://stackoverflow.com/a/46895752/1300910). – huysentruitw Oct 23 '17 at 18:25
  • 1
    The very concise solution in the 'update' is definitive and should be highlighted over and above the original answer text. It's a very good and simple solution! – Neo Mar 22 '19 at 14:52
-1

This only compiles if you constrain the TId type to be a reference type:

public class GenericRepository<TEntity, TId> 
    where TEntity: class, IIdentifyable<TId> 
    where TId: class

However, this may not be suitable in your case, so you would have to create different classes to support GUID, int or long id values.

Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • 1
    Thank you. This would indeed solve the issue. But since I actually want to use `int` as Id it will be problematic to implement. – smoksnes Oct 19 '16 at 13:54
-1

It's because you can not compare generic type with generic type by == operator. Instead use Equals operator in your code :

 public virtual TEntity GetById(TId id)
{
    using (var context = new DbContext())
    {
        var dbSet = context.Set<TEntity>();
        var currentItem = dbSet.FirstOrDefault(x => x.Id.Equals(id));
        return currentItem;
    }
}
Ali Borjian
  • 941
  • 10
  • 18