使用多个具体实现处理存储库模式中的查询?

Eoi*_*ell 10 linq generics repository

这更像是学术上的好奇心,但我想弄清楚如何最好地完成以下内容.

想象一下你有一个Person物体的情况

public class Person {
    public string Name {get;set;}
    public int Age {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

和一个从一些持久性存储中检索它们的存储库合同......

public class IPersonRepository {
    public IEnumerable<Person> Search(*** SOME_METHOD_SIGNATURE ***);
}
Run Code Online (Sandbox Code Playgroud)

您的消费者应用程序实际上并不关心具体实现.它只是从Unity/Ninject中获取正确的具体实现并开始查询.

IPersonRespository repo = GetConcreteImplementationFromConfig();
repo.Search( ... );
Run Code Online (Sandbox Code Playgroud)

我想知道的是,你在这里使用的方法签名将是什么,无论实现如何,它都具有灵活性和可扩展性.

选项1.

public IEnumerable<Person> Search(Expression<Func<Person, bool>> expression);
Run Code Online (Sandbox Code Playgroud)

这很好,因为如果你使用LINQ Capable(例如EntityFramework)数据上下文,你可以直接将表达式传递给你的上下文.如果你的实现必须使用手工制作的存储过程/ sql/ADO.NET等,这个选项似乎就会失败...

选项2.

public IEnumerable<Person> Search(PersonSearch parameters);

public class PersonSearch {
    public int? Age {get;set;}
    public string FullName {get;set;}
    public string PartialName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这个看起来最灵活(在某种意义上它可以与Linq,Plain Old SQL一起使用).

但它只是"编写自己的查询语言"的臭味,因为您需要考虑消费者可能想要做的每一个可能的查询.例如,年龄> = 18 &&年龄<= 65 &&名字喜欢'%John%'

选项3.

还有其他选择吗?

sma*_*man 6

  1. 您的声明"如果您实现必须使用手工制作的存储过程/ sql/ADO.NET等","选项1似乎会掉下来"是不正确的.通过使用ExpressionVisitor实现解释查询表达式,完全可以生成与选项2中的模型类似的模型.

  2. 如果要从LINQ 方法返回IEnumerable<T>而不是a IQueryable<T>,则Search应该合并一些机制来支持(1)分页,以及(2)排序.

  3. 我真的不喜欢选项#2.它没有明确说明您要搜索的内容.例如,如果年龄为空,这是否意味着您正在搜索具有空年龄的用户,或者您是否忽略年龄参数?如果同时指定了Name和PartialName怎么办?随着LINQ方法,你可以做的东西一样StartsWith,Contains,Equals,等.

  4. 存储库模式实现应该抽象出数据访问逻辑,并公开面向业务的接口.通过使用通用接口(Expression<Func<Person,bool>>)直接访问存储库,您将失去一点点,因为接口不会向调用者传达意图.有几种方法可以做得更好.

    • 实现基于LINQ表达式的规范模式可创建更强类型的查询.因此,不使用查询成人,而是Search(person => person.Age.HasValue && person.Age.Value > 18)使用规范语法,例如Search(new PersonIsAdultSpecification());规范包装底层LINQ表达式,但公开面向业务的接口.
      • 就个人而言,我喜欢这种方法,但Ayende称之为"在厄运中建筑",因为它很容易导致过度工程化.他的另一个建议是在扩展方法中包含这样的特定查询.我认为这可能同样可行,但我更喜欢使用强类型对象.
      • 最简单的方法是实际声明您将在IPersonRepository界面上执行的实际查询.所以,接口实际上会声明一个SearchForAdults()方法.

通常,每当您查询数据库时,您都应该刻意尝试获取某些数据.每当您查询存储库时,您都应该刻意尝试获取满足特定业务约束的业务对象.为什么不使这个业务约束更明确?为任何服务定义良好的接口取决于该服务的使用者.没有通用的魔术子弹存储库接口,但您可以在特定应用程序的上下文中创建更好或更差的接口.我们的想法是记住您首先使用存储库模式的原因,那就是简化逻辑并创建更直观的访问点.