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

标签 c# asp.net-mvc linq entity-framework linq-to-entities

我想过滤实现接口(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 但并没有完全解决问题。

在下面的表达式中( RepositoryIQueryable<T> 并且 T 继承自 IOther ),C# 编译器考虑从 T 进行隐式转换。至IOther调用 Other属性。

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

所以你得到相同的 NotSupportedException关于强制转换和 LINQ to 实体。

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

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

通用查询表达式是:

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

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

QueryExpression.ToString() : (存储库,其他) =>存储库.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/

相关文章:

c# - 如何确定对象的类型是否实现了 IEnumerable<X>,其中 X 使用反射从 Base 派生

c# - 在C#中实现VB With语句

c# - 在 C# 中维护 "clean"using 指令列表有什么好处?

c# - 我如何模拟 IQueryable<T>

c# - ASP.NET MVC 模型 - 使用脚手架将模型上创建的连接传递到 Controller 和 View

C# 关于 IEnumerable<T>.Aggregate

c# - ASP.NET 脚本 "scheduling"问题

asp.net-mvc - 优化性能或属性路由和路由表

c# - ASP.NET MVC3、Razor - 在 Javascript 中使用 ViewBag 或 URL 变量

c# - 排序时ASP.net gridview数据源为空