public class AccountSearch { public decimal Amount { get; set; } public string CustomerId { get; set; } public string Address { get; set; } public string CustomerName { get; set; } public string City { get; set; } public string PostalCode { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public string State { get; set; } }
然后,我有一个域级服务,简单地将搜索类传递到存储库.我不喜欢
public class AccountsService : IAccountsService { private readonly IAccountRepository _accountRepository; public AccountsService(IAccountRepository accountRepository) { _accountRepository = accountRepository; } public IEnumerable<Account> Search(AccountSearch accountSearch) { return _accountRepository.Search(accountSearch); } }
然后,我的存储库实现中有所有的过滤逻辑:
public class AccountRepository : IAccountRepository { private AccountDataContext _dataContext; public AccountRepository(AccountDataContext entityFrameworkDataContext) { _dataContext = entityFrameworkDataContext; } public IEnumerable<Account> Search(AccountSearch accountSearch) { // My datacontext contains database entities,not domain entities. // This method must query the data context,then map the database // entities to domain entities. return _dataContext.Accounts .Where(TheyMeetSearchCriteria) .Select(MappedAccounts); } // implement expressions here: // 1. TheyMeetSearchCriteria filters the accounts by the given criteria // 2. MappedAccounts maps from database to domain entities }
解决方法
在位置方面(例如在服务或域中)仅仅讨论搜索逻辑,而不是在指定位置和执行位置之间区分起来更有帮助.按照规格位置,我的意思是你指定要搜索哪些字段的层次.通过执行位置,我的意思是立即执行或延迟执行.
如果您有几种相互排斥的搜索类型(例如,您想通过CustomerId搜索的方案A中,以及您想要通过CustomerName进行搜索的方案B),则可以通过为每个搜索创建专门的方法来实现特定于域的存储库键入,或在.Net中,您可以使用LINQ表达式.例如:
域专用搜索方式:
_customers.WithName("Willie Nelson")
对实现IQueryable的存储库进行LINQ查询:
_customers.Where(c => c.Name.Equals("Willie Nelson")
前者允许一个更具表现力的领域,而后者提供更多的灵活性,稍微减少的开发时间(可能以牺牲可读性为代价).
对于更复杂的搜索条件需求,您可以使用您所描述的传递搜索条件集合(强类型或其他方式)的技术,或者您可以使用Specification Pattern.规范模式的优点是它提供了更具表现力,富含域的查询语言.一个示例使用可能是:
_customers.MeetingCriteria( Criteria.LivingOutsideUnitedStates.And(Criteria.OlderThan(55)))
通过规范模式提供的组合也可以通过.Net的LINQ API提供,尽管对指定意图透露代码的控制较少.
关于执行时间,可以编写存储库以通过返回IQueryable来提供延迟执行,或者允许传入LINQ表达式以通过存储库方法进行评估.例如:
延期查询:
var customer = (from c in _customers.Query() where c.Name == "Willie Nelson" select c).FirstOrDefault();
由Query()方法执行:
var customer = _customers.Query(q => from c in q where c.Name == "Willie Nelson" select c).FirstOrDefault();
返回IQueryable的前一个Query()方法具有稍微容易测试的优点,因为可以容易地将Query()容易地扼杀以通过调用代码来提供操作的集合,而后者具有更确定性的优点.
=====编辑====
灵感来自于Gaearon的做法,我决定用类似的技术修改我的答案.他的方法有些是反向规范模式,其中规范执行实际的查询.这本质上使它成为一个查询本身,所以我们来调用它:
public class SomeClass { // Get the ICustomerQuery through DI public SomeClass(ICustomerQuery customerQuery) { _customerQuery = customerQuery; } public void SomeServiceMethod() { _customerQuery() .WhereLivingOutSideUnitedStates() .WhereAgeGreaterThan(55) .Select(); } }
那么,你可能会问:我们不需要这里.我们的ICustomerQuery可以注入一个可以实现的IQueryable,但是您可以实现(也许是一个IoC注册,只返回NHibernate的以下内容:
_container.Resolve<ISession>().Linq<Customer>()