简体   繁体   English

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

[英]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 . 我有一个通用存储库Repository<T> where T: class由域模型T

Some classes used as T have additional important properties, available using interfaces (eg "I have property "abc", and IAbc is my associated interface). 某些用作T类具有其他重要属性,可以使用接口使用(例如,“我具有属性abc”,而IAbc是我的关联接口)。

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. 我要实现的目标是通过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
}

However, when I am trying to do this, I get the following exception: 但是,当我尝试执行此操作时,出现以下异常:

Unable to cast the type 'T' to type 'IAbc'. 无法将类型“ T”强制转换为类型“ IAbc”。 LINQ to Entities only supports casting EDM primitive or enumeration types. LINQ to Entities仅支持强制转换EDM基本类型或枚举类型。

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. 我看到的一种可能的方法是在一个单独的非泛型类中创建静态约束的泛型方法,并使用DLR动态分派来调用它们。

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. 更新:这是解决EF强制转换问题的更好(更简便)的方法。 Use C# cast operator as in your sample and later call a custom extension method to remove the unnecessary casts using a simple ExpressionVisitor : 在示例中使用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);
        }
    }
}

Sample usage: 用法示例:

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

Linq to Entities won't be able to translate your cast expressions to something that makes sense in, say T-SQL. T-SQL说,Linq to Entities将无法将您的转换表达式转换为有意义的东西。 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");
    }
}

You can use as https://msdn.microsoft.com/en-us/library/cscsdfbt.aspx to do things like: 您可以使用as https://msdn.microsoft.com/en-us/library/cscsdfbt.aspx做的事情,如:

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM