哪个更好?在存储库或域级服务中(通过IQueryable或其他)具有复杂的搜索逻辑?

Byr*_*ahl 10 c# search domain-driven-design repository

我需要能够通过多个搜索字段搜索客户帐户.现在,我的搜索逻辑在我的存储库中.搜索逻辑包括一些感觉更像它属于域层的过滤,但这意味着使用类似IQueryable的东西,我也不确定我是否喜欢它.

例如,现在我有一个搜索类,其中包含用户可以搜索的所有字段:

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; }
}
Run Code Online (Sandbox Code Playgroud)

然后,我有一个域级服务,只是将搜索类传递给存储库.我不喜欢它:

public class AccountsService : IAccountsService
{
    private readonly IAccountRepository _accountRepository;

    public AccountsService(IAccountRepository accountRepository)
    {
        _accountRepository = accountRepository;            
    }

    public IEnumerable<Account> Search(AccountSearch accountSearch)
    {
        return _accountRepository.Search(accountSearch);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我在我的存储库实现中拥有所有过滤逻辑:

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
}
Run Code Online (Sandbox Code Playgroud)

不确定我是否应该对此感觉良好,或者我是否应该找到另一种方法来实现这样的搜索.在这个情况下,你会怎么做?

Der*_*eer 6

您可以使用许多技术,其中最好的技术取决于您的特定情况.

不是仅仅根据位置(例如,在服务中或在域中)讨论搜索逻辑,而是在规范位置和执行位置之间进行区分可能更有帮助.根据规范位置,我的意思是在您指定要在哪些字段中搜索哪些字段.通过执行位置,我的意思是立即执行或延期执行.

如果您有几种互斥的搜索类型(即在方案A中您希望按CustomerId进行搜索,而在方案B中您希望按CustomerName进行搜索),则可以通过为每个搜索创建一个具有专用方法的特定于域的存储库来完成此操作.类型,或.Net中您可能使用LINQ表达式.例如:

特定领域的搜索方法:

_customers.WithName("Willie Nelson")
Run Code Online (Sandbox Code Playgroud)

在实现IQueryable的存储库上的LINQ查询:

_customers.Where(c => c.Name.Equals("Willie Nelson")
Run Code Online (Sandbox Code Playgroud)

前者允许更具表现力的域,而后者提供更大的使用灵活性,同时略微缩短开发时间(可能以牺牲可读性为代价).

对于更复杂的搜索条件需求,您可以使用您描述的传递搜索条件集合(强类型或其他方式)的技术,或者您可以使用规范模式.规范模式的优点是它提供了更具表现力,领域丰富的查询语言.一个示例用法可能是:

_customers.MeetingCriteria(
        Criteria.LivingOutsideUnitedStates.And(Criteria.OlderThan(55)))
Run Code Online (Sandbox Code Playgroud)

通过规范模式提供的组合也可以通过.Net的LINQ API提供,尽管对指定意图揭示代码的控制较少.

关于执行时间,可以编写存储库以通过返回IQueryable来提供延迟执行,或者允许传入LINQ表达式以由存储库方法进行评估.例如:

延迟查询:

var customer =  (from c in _customers.Query()
                     where c.Name == "Willie Nelson"
                     select c).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

由Query()方法执行:

var customer =
   _customers.Query(q => from c in q
                           where c.Name == "Willie Nelson"
                           select c).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

返回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();
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,您可能会问的存储库在哪里?我们这里不需要一个.我们的ICustomerQuery可以注入一个IQueryable,可以随意实现(也许是IoC注册只返回NHibernate的以下内容:

 _container.Resolve<ISession>().Linq<Customer>()
Run Code Online (Sandbox Code Playgroud)


Dan*_*mov 4

为什么不IQueryable从存储库本身公开?这将允许从请求代码运行任何 LINQ 查询。

public class AccountRepository : IAccountRepository 
{
    AccountContext context = new AccountContext ();

    public IQueryable<Account> GetItems ()
    {
        return context.Accounts;
    } 
}
Run Code Online (Sandbox Code Playgroud)

您可以AccountSearch根据自己的逻辑负责构建查询:

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 IQueryable<Account> BuildQuery (IQueryable<Account> source)
    {
        var query = source.Where (a =>
            a.Amount == Amount);

        // you can use more twisted logic here, like applying where clauses conditionally
        if (!string.IsNullOrEmpty (Address))
            query = query.Where (a =>
               a.Address == Address);

        // ...

        return query;     
    }
}
Run Code Online (Sandbox Code Playgroud)

然后从客户端代码中使用它:

var filter = GetSearchFields (); // e.g. read from UI
var allItems = repository.GetItems ();

var results = filter.BuildQuery (allItems).ToList ();
Run Code Online (Sandbox Code Playgroud)

这只是可能的方法之一,但我喜欢它,因为它允许搜索过滤器类中的复杂逻辑。例如,您可能在 UI 中有一个具有不同搜索类型的单选按钮,这些搜索类型又按不同字段进行搜索。这一切都可以在AccountSearch使用该模式时体现出来。您也可以将一些搜索字段设置为可选,正如我在本示例中所做的那样Address。毕竟,您有责任从客户端代码实际构建AccountSearch最适合它的查询,因为它最了解搜索条件及其含义。