能否将Expression<Func<T, bool>>转换为Expression<Func<MyType, bool>>?

7

我有一个存储库类,其中包含一些通用方法。其中之一是

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<Func<MyEntity, bool>>

我该如何将通用表达式转换为强类型表达式?


你有没有考虑过模拟你的仓库呢? - Jakub Konecki
@Jon - 不是在模拟,而是在桩测试。 - Jakub Konecki
@SaeedNeamati:或许也可能不会。与此案件无关。 - Jon
@Grundy:很抱歉,我没有看到你的评论。我会尝试一些这些评论中的解决方案,并回报结果。 - Nicklas Møller Jepsen
@Jon:你在提到的帖子中尝试的解决方案实际上对我有效。 - Nicklas Møller Jepsen
显示剩余15条评论
2个回答

2
你可以这样做。不确定是否是一个好主意,但它能够工作。基本上,你的重载可以将类型参数 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);

0

看起来你的存储库实现在某种意义上存在一个重大缺陷,即调用者不知道他只能传递某些类型的参数(例如,看起来我可以执行FindAll<int>(v => v > 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参数进行一些转换。


实现的存储库仅用于单元测试。我有一个完全通用的存储库,用于查询数据库。在不运行测试时,接受所有类型。 - Nicklas Møller Jepsen
我认为这对我来说不是正确的方式,因为我不想为每个不同的实体类型都有一个存储库。 - Nicklas Møller Jepsen
@NicklasJepsen 你是指最后一段关于 Expression.Lambda 的部分吗?我保留了第一部分,以防有类似问题的人想考虑使用我提供的方法(顺便说一下,在我们当前的项目中广泛使用它,已经证明它非常灵活和易于维护)。 - volpav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接