【问题标题】:Is it possible to convert Expression<Func<T, bool>> to Expression<Func<MyType, bool>>?是否可以将 Expression<Func<T, bool>> 转换为 Expression<Func<MyType, bool>>?
【发布时间】:2023-03-31 17:55:02
【问题描述】:

我有一个包含一些通用方法的存储库类。一个是

public IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate) where T : class
{
    return GetDbSet<T>().Where(predicate);
}

对于单元测试,我有一个TestRepository,它使用内存中的对象而不是数据库。 TestRepository 覆盖 FindAll 方法,我想控制返回的内容。所以我希望能够做这样的事情:

public override IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate)
{
    return MyEntities.Where(predicate).Cast<T>();
}

MyEntities.Where() 只接受Expression&lt;Func&lt;MyEntity, bool&gt;&gt;

如何将泛型表达式转换/转换为强类型表达式?

【问题讨论】:

  • 您是否考虑过模拟您的存储库?
  • @JakubKonecki:这不正是他在这里所做的吗?
  • @Jon - 不,他不是在嘲笑。
  • 了解Covariance & Contravariance 可能会有所帮助。
  • @SaeedNamati:或者它可能不会。与本案无关。

标签: c# unit-testing lambda


【解决方案1】:

你可以做这样的事情。不确定这是否是一个好主意,但它确实有效。基本上,您的重载可以将类型参数T 与您的实体类进行比较。如果谓词具有正确的类型,则可以强制转换。否则,您将没有任何回报。

public class MyEntity { public int x; }

MyEntity[] MyEntitiesList = Enumerable.Range(1,5).Select(y => new MyEntity() { x = y }).ToArray();

public IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate)
{
     if (typeof(T) == typeof(MyEntity))
     {
         return (IEnumerable<T>)MyEntitiesList.Where((predicate as Expression<Func<MyEntity, bool>>).Compile());
     }
     return new T[0];
}

用法:

var res = FindAll((MyEntity y) => y.x % 2 == 0).ToList();
Console.WriteLine(res.Count);

【讨论】:

    【解决方案2】:

    看起来你的存储库实现有一个重大缺陷,即调用者不知道他只能传递某些类型的参数(例如,看起来我可以做到FindAll&lt;int&gt;(v =&gt; v &gt; 0),但实际上,只有底层实现与MyEntity一起使用)。换句话说,它试图过于“聪明”,它不直观且容易出错。

    解决此问题的一种方法是引入接口/基类:

    public interface IRepository<T>
    {
      IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate);
    }
    
    // A base class that can carry helper functionality.
    public abstract class Repository<T> : IRepository<T>
    {
      private readonly IEnumerable<T> _entities;
    
      protected Repository(IEnumerable<T> entities)
      {
        _entities = entities;
      }
    
      public IEnumerable<T> FindAll(Expression<Func<T, bool>> predicate)
      {
        return _entities.Where(predicate);
      }
    }
    
    // Concrete implementation
    public class MyEntityRepository : Repository<MyEntity>
    {
      public MyEntityRepository() : base(new MyDbContext().MyEntities) { }
    }
    

    在上面的例子中,我提到MyDbContext 只是为了演示(所以如果你使用Entity Framework,它看起来会更熟悉一些)。

    现在您可以实例化MyEntityRepository 并在整个应用程序中使用它。如果您使用某种 IoC,您可以稍微修改代码:

    public interface IMyEntityRepository : IRepository<MyEntity>
    {
      // ...
    }
    
    public class MyEntityRepository : Repository<MyEntity>, IMyEntityRepository
    {
      // ...
    }
    

    现在您可以轻松地在您的应用程序中注入和模拟IMyEntityRepository

    希望这会有所帮助。

    更新

    事实证明,由于给定的实现仅用于测试目的,您可以尝试通过以下方式生成所需类型的表达式:

    return MyEntities.Where(Expression.Lambda<Func<MyEntity, bool>>(predicate.Body, 
      predicate.Parameters)).Cast<T>();
    

    如果需要显式转换,您还可以对 lambda 参数应用一些转换。

    【讨论】:

    • 实现的存储库仅用于单元测试。我有一个完全通用的存储库,用于查询数据库。当不运行测试时,所有类型都被接受。
    • 我认为这不是我要走的路,因为我不想为每种不同的实体类型建立一个存储库。
    • @NicklasJepsen 你指的是最后一段Expression.Lambda吗?我留下了第一部分,以防遇到类似问题的人可能想考虑使用我提供的方法(顺便说一下,我们在当前项目中广泛使用它,并且已证明它非常灵活和可维护)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多