C# 如何在存储库中强制转换通用T<;T>;在LINQ to SQL筛选器中有条件地访问接口属性?
我有一个通用存储库C# 如何在存储库中强制转换通用T<;T>;在LINQ to SQL筛选器中有条件地访问接口属性?,c#,entity-framework,linq,generics,C#,Entity Framework,Linq,Generics,我有一个通用存储库存储库,其中T:class由域模型T使用 用作T的某些类具有其他重要属性,可使用接口使用(例如,“我有属性”abc,IAbc是我的关联接口) 我试图实现的是通过接口转换在我的存储库中的特定方法中公开这些附加字段,并将它们用于过滤、条件决策等 // Note: The repository shown below only has a generic constraint on class. // It can not have a constraint on IAbc, sin
存储库,其中T:class
由域模型T
使用
用作T
的某些类具有其他重要属性,可使用接口使用(例如,“我有属性”abc,IAbc
是我的关联接口)
我试图实现的是通过接口转换在我的存储库中的特定方法中公开这些附加字段,并将它们用于过滤、条件决策等
// 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
}
//注意:下面显示的存储库仅对类具有通用约束。
//它不能对IAbc有约束,因为不是所有类都使用
//存储库还实现了IAbc。
公共类MyRepository,其中T:class
{
//为简洁而缩写
公共IQueryable GetSomething()
{
//我在这里要做的是:
//如果泛型T实现IAbc,我想使用T的“abc”属性
//在我的Where过滤器中,如果T
//实施IAbc。
//然而,由于不是所有人都能实施IAbc,所以我不能将其作为
//对整个存储库的约束。
//我实现这一点的方法是(1)进行可分配性检查
//和(2)在Where谓词中强制转换为IAbc。强制转换不是
//尽管如此,请参见下面的错误。
if(typeof(IAbc).IsAssignableFrom(typeof(T))
{
返回DbSet(),其中(x=>((IAbc)x.Abc==“hey”);
}
//为简洁而缩写
}
但是,当我尝试执行此操作时,会出现以下异常:
无法将类型“T”强制转换为类型“IAbc”。LINQ to Entities仅支持强制转换EDM基元或枚举类型
谢谢您的帮助。您可以使用as
执行以下操作:
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
}
Linq to Entities将无法将您的强制转换表达式转换为在中有意义的内容,例如t-SQL。不过,您可以这样做:
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");
}
}
公共类MyRepository,其中T:class
{
//为简洁而缩写
公共IQueryable GetSomething()
{
if(typeof(IAbc).IsAssignableFrom(typeof(T))
{
返回GetSomeAbc();
}
}
私有IQueryable GetSomeAbc(),其中V:IAbc
{
返回DbSet(),其中(x=>x.Abc==“嘿”);
}
}
我看到的一种可能的方法是在一个单独的非泛型类中创建静态约束泛型方法,并使用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");
}
}
示例用法:
if (typeof(IAbc).IsAssignableFrom(typeof(T))
{
return DbSet<T>().Where(x => ((IAbc)x).Abc == "hey").ReduceCasts();
}
if(typeof(IAbc).IsAssignableFrom(typeof(T))
{
返回DbSet();
}
潜在有用的阅读(?)不完全确定它是否适用(有限的.net/linq经验),但有各种可能的答案可能会给出一个想法。谢谢@CollinD-不幸的是,我的情况有点不同(我认为)因为这个问题是使用扩展方法,而不是我正在做的。尽管如此,还是要感谢它!谢谢,Ivan!这会创建相同的复合表达式和SQL,就好像T首先受到IAbc的约束一样,还是我应该期望它在任何方面都有所不同?你是说第二种方法?是的,它基本上会导致约束调用。我已经测试过了,EF很高兴:)我喜欢reducecast()
方法
public class MyRepository<T> where T : class
{
public IQueryable<T> GetSomething()
{
if (typeof(IAbc).IsAssignableFrom(typeof(T)))
{
return MyRepository.GetSomething((dynamic)DbSet<T>());
}
// ...
}
}
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);
}
}
}
if (typeof(IAbc).IsAssignableFrom(typeof(T))
{
return DbSet<T>().Where(x => ((IAbc)x).Abc == "hey").ReduceCasts();
}