【问题标题】:Cast a IQueryable type to interface in Linq to Entities将 IQueryable 类型转换为 Linq to Entities 中的接口
【发布时间】:2016-03-10 18:05:24
【问题描述】:

我的泛型类中有以下方法:

// This is the class declaration
public abstract class BaseService<TEntity, TKey> : IBaseService<TEntity, TKey> where TEntity : class, IEntity<TKey>

// The Method
public IQueryable<TEntity> GetActive()
{
    if (typeof(IActivable).IsAssignableFrom(typeof(TEntity)))
    {
        return this.repository.Get().Cast<IActivable>()
            .Where(q => q.Active)
            .Cast<TEntity>();
    }
    else
    {
        return this.Get();
    }
}

这是界面:

public interface IActivable
{
    bool Active { get; set; }
}

基本上,TEntity 是一个实体 (POCO) 类,如果它们具有 Active 属性,则可以实现 IActivable。我希望该方法返回所有具有Active 值的记录为真。但是,我有这个错误:

无法将类型“WebTest.Models.Entities.Product”转换为类型 'Data.IActivable'。 LINQ to Entities 仅支持转换 EDM 原始类型或枚举类型。

我明白为什么会出现这个错误。但是关于 SO 的文章对我的情况没有任何有效的解决方案。是否可以通过Cast 或任何其他方式实现?注意:我不想转换成IEnumerable,我想保留IQueryable

【问题讨论】:

    标签: c# entity-framework linq


    【解决方案1】:

    EF 表达式解析器无需强制转换即可工作,但是如果没有强制转换,您将无法编译 C# 代码(C# 会抱怨它不知道 TEntity 具有 Active 属性)。解决方案是:为 c# 编译器强制转换,而不为 EF 表达式解析器强制转换。

    因此,如果您确定(您在if 中检查它,所以您是)该对象实现了IActivable,您可以使用强制转换创建表达式(用于编译),然后在运行时删除强制转换(这是不必要的)对于 EF。对于您的特殊情况:

    public IQueryable<TEntity> GetActive()
    {
      if (typeof(IActivable).IsAssignableFrom(typeof(TEntity)))
      {
        Expression<Func<TEntity, bool>> getActive = x => ((IActivable)x).Active;
        getActive = (Expression<Func<TEntity, bool>>)RemoveCastsVisitor.Visit(getActive);
        return this.repository.Get().Where(getActive);
      }
      else
      {
        return this.Get();
      }
    }
    

    表达式visitor是这样实现的:

    internal class RemoveCastsVisitor : ExpressionVisitor
    {
      private static readonly ExpressionVisitor Default = new RemoveCastsVisitor();
    
      private RemoveCastsVisitor()
      {
      }
    
      public new static Expression Visit(Expression node)
      {
        return Default.Visit(node);
      }
    
      protected override Expression VisitUnary(UnaryExpression node)
      {
        if (node.NodeType == ExpressionType.Convert
            && node.Type.IsAssignableFrom(node.Operand.Type))
        {
          return base.Visit(node.Operand);
        }
        return base.VisitUnary(node);
      }
    }
    

    它只是检查是否需要强制转换:如果实际值已经实现了它要强制转换的类型,它只会从表达式中删除转换,EF 会正确选择它。

    【讨论】:

    • 不知道可以绕过强制转换,很好的解决方案!
    • 很棒的东西,虽然命名一个接口 IActivable 真的让我感到恶心!请将其更改为 IActivatable :)
    【解决方案2】:

    诀窍是将整个 IQueryable 转换为 IQueryable 而不是第一次转换:

    if (typeof(IActivable).IsAssignableFrom(typeof(TEntity)))
    {
        return ((IQueryable<IActivable>)(this.repository.Get()))
            .Where(q => q.Active)
            .Cast<TEntity>();
    }
    

    【讨论】:

    • 漂亮干净的解决方案!
    • 简单、干净、有效。应该是接受的答案恕我直言。
    【解决方案3】:

    目前我有一个替代方法是使用扩展方法。但是缺点是我的 IBaseService 不能声明 GetActive 方法,因为具体的类实际上并没有实现它。

    public static class BaseServiceExtension
    {
    
        public static IQueryable<TEntity> GetActive<TEntity, TKey>(this IBaseService<TEntity, TKey> service) 
            where TEntity : class, IEntity<TKey>, IActivable
        {
            return service.Get().Where(q => q.Active);
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2016-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-02
      • 2013-02-28
      相关资源
      最近更新 更多