Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast a generic T in repository<T> to an interface to access an interface property conditionally in a LINQ to SQL filter?

I have a generic repository Repository<T> where T: class which is used by domain models T.

Some classes used as T have additional important properties, available using interfaces (e.g. "I have property "abc", and IAbc is my associated interface).

public interface IAbc
{
   string Abc { get; }
}

public class MyClass: IAbc
{
   public string Abc { get; set; }
}

What I am trying to achieve is to expose those additional fields via interface casting in specific methods inside my Repository<T> and use them for filtering, conditional decision making and so on.

// Note: The repository shown below only has a generic constraint on class.
// It can not have a constraint on IAbc, since not all classes T using the
// repository also implement IAbc.

public class MyRepository<T> where T: class
{
    // abbreviated for brevity

    public IQueryable<T> GetSomething()
    {
        // What I am trying to do here:
        // If generic T implements IAbc, I want to use T's "abc" property
        // in my Where filter, which should logically be possible if T
        // implements IAbc. 
        // However, since not ALL T implement IAbc, I can't make this a 
        // constraint on the entire repository. 
        // My approach to achieve this is to (1) have an assignability check
        // and (2) cast to IAbc in the Where predicate. The cast is not 
        // working though, see the error below.

        if (typeof(IAbc).IsAssignableFrom(typeof(T))
        {
             return DbSet<T>().Where(x => ((IAbc)x).Abc == "hey");
        }

    // abbreviated for brevity
}

However, when I am trying to do this, I get the following exception:

Unable to cast the type 'T' to type 'IAbc'. LINQ to Entities only supports casting EDM primitive or enumeration types.

Thank you for your help.

like image 885
Alex Avatar asked Dec 04 '16 00:12

Alex


1 Answers

One possible way I see is to create static constrained generic methods in a separate non generic class and use DLR dynamic dispatch to call them.

For instance, helper:

public static class MyRepository
{
    public static IQueryable<T> GetSomething<T>(IQueryable<T> source)
        where T : class, IAbc
    {
        return source.Where(x => x.Abc == "hey");
    } 
}

and usage:

public class MyRepository<T> where T : class
{
    public IQueryable<T> GetSomething()
    {
        if (typeof(IAbc).IsAssignableFrom(typeof(T)))
        {
            return MyRepository.GetSomething((dynamic)DbSet<T>());
        }
        // ...
    }
}

UPDATE: Here is better (easier) way to solve the EF cast issue. Use C# cast operator as in your sample and later call a custom extension method to remove the unnecessary casts using a simple ExpressionVisitor:

public static class QueryableExtensions
{
    public static IQueryable<T> ReduceCasts<T>(this IQueryable<T> source)
    {
        var expression = new CastReducer().Visit(source.Expression);
        if (source.Expression == expression) return source;
        return source.Provider.CreateQuery<T>(expression);
    }

    class CastReducer : ExpressionVisitor
    {
        protected override Expression VisitUnary(UnaryExpression node)
        {
            if (node.NodeType == ExpressionType.Convert &&
                node.Type.IsAssignableFrom(node.Operand.Type))
            {
                // Strip the Convert
                return Visit(node.Operand);
            }
            return base.VisitUnary(node);
        }
    }
}

Sample usage:

if (typeof(IAbc).IsAssignableFrom(typeof(T))
{
    return DbSet<T>().Where(x => ((IAbc)x).Abc == "hey").ReduceCasts();
}
like image 79
Ivan Stoev Avatar answered Nov 02 '22 09:11

Ivan Stoev