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

C# 哪个更好?在存储库或域级服务中是否有复杂的搜索逻辑(通过IQueryable或其他)?,c#,search,domain-driven-design,repository,C#,Search,Domain Driven Design,Repository,我需要能够通过多个搜索字段搜索客户帐户。现在,我的存储库中有我的搜索逻辑。搜索逻辑包括一些感觉更像是属于域层的过滤,但这意味着使用IQueryable之类的东西,我也不确定我是否喜欢 例如,现在我有一个搜索类,它包含用户可以搜索的所有字段: public class AccountSearch { public decimal Amount { get; set; } public string CustomerId { get; set; } public string

我需要能够通过多个搜索字段搜索客户帐户。现在,我的存储库中有我的搜索逻辑。搜索逻辑包括一些感觉更像是属于域层的过滤,但这意味着使用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; }
}
然后,我有一个域级别的服务,它只是将搜索类传递给存储库。我不喜欢它:

public class AccountsService : IAccountsService
{
    private readonly IAccountRepository _accountRepository;

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

    public IEnumerable<Account> Search(AccountSearch accountSearch)
    {
        return _accountRepository.Search(accountSearch);
    }
}
公共类帐户服务:IAccountsService
{
私有只读IAccountRepository\u accountRepository;
公共帐户服务(IAccountRepository accountRepository)
{
_accountRepository=accountRepository;
}
公共IEnumerable搜索(AccountSearch AccountSearch)
{
返回_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
}
公共类AccountRepository:IAccountRepository
{
私有AccountDataContext_dataContext;
公共AccountRepository(AccountDataContext entityFrameworkDataContext)
{
_dataContext=entityFrameworkDataContext;
}
公共IEnumerable搜索(AccountSearch AccountSearch)
{
//My datacontext包含数据库实体,而不是域实体。
//此方法必须查询数据上下文,然后映射数据库
//实体到域实体。
return\u dataContext.Accounts
.其中(他们的搜索标准)
.选择(MappedAccounts);
} 
//在此处实现表达式:
//1.TheyMeetSearchCriteria根据给定的条件筛选帐户
//2.MappeAccounts从数据库映射到域实体
}

我不确定我是否应该对此感到满意,或者我是否应该找到另一种方法来实现这样的搜索。在这种情况下,您会怎么做?

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

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

    public IQueryable<Account> GetItems ()
    {
        return context.Accounts;
    } 
}
然后从客户端代码中使用它:

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

var results = filter.BuildQuery (allItems).ToList ();

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

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

与其仅仅根据位置(例如,在服务或域中)讨论搜索逻辑,还不如区分规范位置和执行位置。通过规范位置,我的意思是在什么层中指定要搜索的字段。所谓执行地点,我指的是立即执行或延迟执行

如果您有几种互斥的搜索类型(即,在场景A中,您希望按CustomerId搜索,在场景B中,您希望按CustomerName搜索),则可以通过为每种搜索类型创建具有专用方法的特定于域的存储库来实现,或者在.Net中,您可以使用LINQ表达式。例如:

特定于域的搜索方法:

_customers.WithName("Willie Nelson")
实现IQueryable的存储库上的LINQ查询:

_customers.Where(c => c.Name.Equals("Willie Nelson")
前者允许更具表现力的领域,而后者提供了更大的使用灵活性,开发时间略有减少(可能以牺牲可读性为代价)

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

_customers.MeetingCriteria(
        Criteria.LivingOutsideUnitedStates.And(Criteria.OlderThan(55)))
通过规范模式提供的组合也可以通过.Net的LINQAPI提供,尽管对指定意图揭示代码的控制较少

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

延迟查询:

var customer =  (from c in _customers.Query()
                     where c.Name == "Willie Nelson"
                     select c).FirstOrDefault();
由Query()方法执行:

前者返回IQueryable的Query()方法的优点是更容易测试,因为Query()可以很容易地存根以提供通过调用代码操作的集合,而后者的优点是更具确定性

======编辑====

受盖隆方法的启发,我决定用类似的技巧修改我的答案。他的方法有点像反向规范模式,规范执行实际的查询。这本质上使它本身成为一个查询,所以我们就这样称呼它:

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>()
\u container.Resolve().Linq()

我可以问一下
AccountsService
在这里有什么意义吗?它似乎只是毫无目的地包装
IAccountRepository
。正确。像往常一样,为了这个问题,我删除了其他方法。你的问题与我的问题相同,只是我希望搜索逻辑是正确的
public class SomeClass
{
    // Get the ICustomerQuery through DI
    public SomeClass(ICustomerQuery customerQuery)
    {
        _customerQuery = customerQuery;
    }

    public void SomeServiceMethod()
    {
        _customerQuery()
            .WhereLivingOutSideUnitedStates()
            .WhereAgeGreaterThan(55)
            .Select();
    }
}
 _container.Resolve<ISession>().Linq<Customer>()