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.
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With