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()上的额外约束,但(据我所知(这是不可能的。

有什么建议吗?

LINQ where子句基于(casted)接口

您可以为继承IOther的类编写一个特定的控制器,但它并不能完全解决问题。

在下面的表达式中(RepositoryIQueryable<T>T继承自IOther(,C#编译器考虑从TIOther的隐式转换,以调用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;}
}