gpt4 book ai didi

c# - 基于(转换)接口(interface)的 LINQ where 子句

转载 作者:太空宇宙 更新时间:2023-11-03 10:44:06 25 4
gpt4 key购买 nike

我想过滤一个实现接口(interface)的实体列表。

型号:

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;}
}

Controller :

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 的类编写特定的 Controller 但并没有完全解决问题。

在下面的表达式中(Repository 是一个IQueryable<T> 并且T 继承自IOther),C# 编译器考虑从T 隐式转换。至 IOther调用电话 Other属性(property)。

var query = Repository.Where(x => x.Other == other);

所以你得到相同的NotSupportedException关于 cast 和 LINQ to entities。

解决方案是在运行时使用反射构建查询

这是为了限制编译器在表达式级别的工作,并在运行时执行从表达式到函数的转换。

通用查询表达式是:

Expression<Func<IQueryable<T>, int, IQueryable<T>>> QueryExpression =
(repository, other) => repository.Where(x => x.Other == other);

使用调试控制台,您可以看到编译器如何添加隐式转换器:

QueryExpression.ToString() : (repository, other) => repository.Where(x => (Convert(x).Other == other))

这个表达式有两处需要改变:

  • 消除转换器的使用
  • 调用Other T 声明的属性(property)而不是 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);
}
}
}

关于c# - 基于(转换)接口(interface)的 LINQ where 子句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24146965/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com