如何将存储库<T>中的通用 T 转换为接口以在 LINQ to SQL 过滤器中有条件地访问接口属性?

Ale*_*lex 5 c# linq generics entity-framework

我有一个Repository<T> where T: class由域模型使用的通用存储库T

一些用作 as 的类T具有额外的重要属性,可使用接口使用(例如“我有属性“abc”,并且IAbc是我的关联接口)。

public interface IAbc
{
   string Abc { get; }
}

public class MyClass: IAbc
{
   public string Abc { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我想要实现的是通过我内部特定方法中的接口转换来公开这些附加字段,Repository<T>并将它们用于过滤、条件决策等。

// 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
}
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试执行此操作时,出现以下异常:

无法将类型“T”转换为“IAbc”类型。LINQ to Entities 仅支持转换 EDM 原语或枚举类型。

感谢您的帮助。

Iva*_*oev 4

我看到的一种可能的方法是在单独的非泛型类中创建静态约束泛型方法,并使用 DLR 动态调度来调用它们。

例如,助手:

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

和用法:

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

更新:这是解决 EF 转换问题的更好(更简单)的方法。使用 C# 强制转换运算符(如示例中所示),然后调用自定义扩展方法,使用简单的方法删除不必要的强制转换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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

if (typeof(IAbc).IsAssignableFrom(typeof(T))
{
    return DbSet<T>().Where(x => ((IAbc)x).Abc == "hey").ReduceCasts();
}
Run Code Online (Sandbox Code Playgroud)