基于Linq的泛型替代Predicate<T>?

9

我有一个名为 ICatalog 的接口,如下所示,每个 ICatalog 都有一个名称和一个方法,该方法将根据 Predicate<Item> 函数返回项目。

public interface ICatalog
{
     string Name { get; }
     IEnumerable<Item> GetItems(Predicate<Item> predicate);
}

一种特定的目录实现可以链接到各种格式的目录,例如XML或SQL数据库。
使用XML目录时,我会将整个XML文件反序列化到内存中,因此测试每个项目与谓词函数不会增加太多开销,因为它已经在内存中了。
但是,对于SQL实现,我不想将整个数据库内容检索到内存中,然后使用谓词函数过滤项目。相反,我希望找到一种方法将谓词传递给SQL服务器,或以某种方式将其转换为SQL查询。
这似乎是可以用Linq解决的问题,但我对它还不太熟悉。我的接口应该返回IQueryable吗?我现在不关心如何实际实现ICatalog的SQL版本。我只想确保我的接口将来可以支持它。
2个回答

7

Rob已经指出了如何实现这一点(虽然更经典的LINQ方法可能需要使用Expression<Func<Item,bool>>,并且可能返回IQueryable<IFamily>)。

好消息是,如果您想在LINQ-to-Objects中使用谓词(用于xml场景),那么您只需使用:

Predicate<Item> func = predicate.Compile();

或者(对于另一种签名):
Func<Item,bool> func = predicate.Compile();

如果您有一个委托 (func) 来测试您的对象,则这是有用的。

然而,问题在于这对单元测试来说是一场噩梦 - 您只能进行集成测试。

问题在于您无法使用 LINQ-to-Objects 可靠地模拟涉及复杂数据存储的任何内容;例如,在您的单元测试中,以下内容将正常工作,但在真实数据库中不会起作用:

var foo = GetItems(x => SomeMagicFunction(x.Name));

static bool SomeMagicFunction(string name) { return name.Length > 3; } // why not

问题在于只有部分操作可以转换为TSQL。对于IQueryable<T>,你会遇到同样的问题——例如,EF和LINQ-to-SQL在查询上支持不同的操作;即使是First()的行为也不同(EF要求你先显式地对其进行排序,而LINQ-to-SQL则不需要)。
因此,总结一下:
  • 它可以工作
  • 但请仔细考虑是否要这样做;更经典的黑盒存储库/服务接口可能更易于测试

谢谢你的回答,马克。听起来比我想要的更复杂。你所说的“更经典的黑盒子存储库/服务接口”是什么意思?你能提供一个例子吗? - Eric Anastas

5

您不需要费力地创建一个IQueryable实现

如果您将GetItems方法声明为:

IEnumerable<IFamily> GetItems(Expression<Predicate<Item>> predicate);

然后你的实现类可以检查表达式以确定正在请求什么。

不过,请先阅读IQueryable文章,因为它解释了如何构建表达式树访问器,这是您构建简单版本所需的。


FYI:我搞砸了原始示例,它应该同时使用IEnumerable<Item>和Predicate<Item>,而不是IEnumerable<IFamily>。 - Eric Anastas

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