LINQ where子句基于(casted)接口
本文关键字:casted 接口 where 子句 LINQ | 更新日期: 2023-09-27 18:08:13
我想过滤一个实现接口的实体列表。
型号:
public interface IEntity
{
int Id {get; set;}
}
public interface IOther
{
int Other {get; set;}
}
public class MyEntity : IEntity, IOther
{
public int Id {get; set;}
public int Other {get; set;}
}
控制器:
public abstract class GenericApiController<T> : ApiController
where T : IEntity
{
public HttpResponseMessage Get(int other)
{
var query = Repository.AsQueryable()
.Cast<IOther>()
.Where(x => x.Other == other);
return Ok(query.ToList());
}
}
然而,我得到了一个">LINQ to Entities仅支持强制转换EDM基元或枚举类型"异常。
一种解决方案是在GenericApiController上设置where T : IOther
,但遗憾的是,我不能这样做,因为不是每个IEntity都实现IOther。
我正在研究是否有可能做以下事情:
public abstract class GenericApiController<T> : ApiController
where T : IEntity
{
public HttpResponseMessage Get(int other)
where T : IOther
{
var query = Repository.AsQueryable()
.Where(x => x.Other == other);
return Ok(query.ToList());
}
}
注意Get()
上的额外约束,但(据我所知(这是不可能的。
有什么建议吗?
您可以为继承IOther
的类编写一个特定的控制器,但它并不能完全解决问题。
在下面的表达式中(Repository
是IQueryable<T>
,T
继承自IOther
(,C#编译器考虑从T
到IOther
的隐式转换,以调用Other
属性。
var query = Repository.Where(x => x.Other == other);
因此,您得到了关于强制转换和LINQ到实体的相同NotSupportedException
。
解决方案是在运行时使用反射构建查询。
这是为了限制编译器在表达式级别的工作,并在运行时执行从表达式到函数的转换。
通用查询表达式为:
Expression<Func<IQueryable<T>, int, IQueryable<T>>> QueryExpression =
(repository, other) => repository.Where(x => x.Other == other);
使用调试控制台,您可以看到编译器是如何添加隐式转换器的:
QueryExpression.ToString((:(repository,other(=>repository。其中(x=>(转换(x(。其他==其他((
这个表达式中有两件事需要改变:
- 消除转换器的使用
- 调用
T
声明的Other
属性,而不是IOther
声明的属性
为此,我们使用ExpressionVisitor
。
public abstract class GenericApiControllerForIOther<T> : ApiController
where T : IOther
{
public HttpResponseMessage Get(int other)
{
var query = QueryFunction(Repository, other);
return Ok(query.ToList());
}
// the generic query expression
static Expression<Func<IQueryable<T>, int, IQueryable<T>>> QueryExpression =
(repository, other) => repository.Where(x => x.Other == other);
// the function built from the generci expression
static Func<IQueryable<T>, int, IQueryable<T>> queryFunction = null;
static Func<IQueryable<T>, int, IQueryable<T>> QueryFunction
{
get
{
if (queryFunction == null)
{
// rebuild a new lambda expression without reference to the IOther type
TypeReplacer replacer = new TypeReplacer(typeof(IOther), typeof(T));
Expression newExp = replacer.Visit(QueryExpression.Body);
Expression<Func<IQueryable<T>, int, IQueryable<T>>> newLambdaExp = Expression.Lambda<Func<IQueryable<T>, int, IQueryable<T>>>(newExp, QueryExpression.Parameters);
// newLambdaExp.ToString(): (repository, other) => repository.Where(x => (x.Other == other))
// convert the expression to a function
queryFunction = newLambdaExp.Compile();
}
return queryFunction;
}
}
class TypeReplacer : ExpressionVisitor
{
public TypeReplacer(Type oldType, Type newType)
{
OldType = oldType;
NewType = newType;
}
Type OldType;
Type NewType;
protected override Expression VisitMember(MemberExpression node)
{
// replace IOther.Property by T.Property
MemberInfo memberInfo = node.Member;
if (memberInfo.DeclaringType == OldType)
{
MemberInfo newMemberInfo = NewType.GetMember(memberInfo.Name).First();
return Expression.MakeMemberAccess(Visit(node.Expression), newMemberInfo);
}
return base.VisitMember(node);
}
protected override Expression VisitUnary(UnaryExpression node)
{
// remove the Convert operator
if (node.NodeType == ExpressionType.Convert
&& node.Type == OldType
&& node.Operand.Type == NewType)
return node.Operand;
return base.VisitUnary(node);
}
}
}
您是否尝试在IOther中实现IEnumerable接口?
public interface IOther : IEnumerable
{
int Other {get; set;}
}