5

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.

Alex
  • 75,813
  • 86
  • 255
  • 348
  • Potentially useful reading(?) http://stackoverflow.com/questions/18976495/linq-to-entities-only-supports-casting-edm-primitive-or-enumeration-types-with-i Not totally sure if it's applicable (limited .net/linq experience) but there's a variety of potential answers that might give an idea. – CollinD Dec 04 '16 at 00:50
  • Thank you @CollinD - unfortunately my scenario is a bit different (I think) since that question is using an extension method rather than what I am doing. Appreciate it though! – Alex Dec 04 '16 at 01:00

3 Answers3

4

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();
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Thanks, Ivan! Will this create the same composite expression and SQL as if T was constrained with IAbc in the first place, or should I expect it to be different in any way? – Alex Dec 04 '16 at 19:29
  • You mean the second approach? Yes, it will basically result to equivalent of constrained call. I've tested it and EF is just happy :) – Ivan Stoev Dec 04 '16 at 20:57
  • I like the `ReduceCasts()` approach – haim770 Dec 12 '16 at 14:04
0

You can use as https://msdn.microsoft.com/en-us/library/cscsdfbt.aspx to do things like:

var abcX = x as IAbc;
if(abcX != null){
    //you know that x contained an IAbc and abcX now exposes that interface
}
else
{
    //x does not contain an IAbc
}
Danny
  • 541
  • 2
  • 11
0

Linq to Entities won't be able to translate your cast expressions to something that makes sense in, say T-SQL. You may be able to do this instead, though:

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

    public IQueryable<T> GetSomething()
    {
        if (typeof(IAbc).IsAssignableFrom(typeof(T))
        {
             return GetSomeAbc<T>();
        }
    }

    private IQueryable<V> GetSomeAbc<V>() where V : IAbc
    {
        return DbSet<V>().Where(x => x.Abc == "hey");
    }
}
Jacob
  • 77,566
  • 24
  • 149
  • 228