【问题标题】:How can I refactor this IQueryable<T> Repository Method?如何重构此 IQueryable<T> 存储库方法?
【发布时间】:2010-08-31 00:26:12
【问题描述】:

我正在开发一个 .NET 4 应用程序、C#、Entity Framework 4、SQL Server 2008。

我的数据库中有 7 个表,每个表代表一个特定级别的位置(国家、州、城市、社区等)。

现在在我的存储库中,我试图定义一个只有一个 Find() 方法的接口契约。为此,我创建了一个名为“Location”的抽象类,POCO 位置都继承自该抽象类。

这是我目前的方法:

public IQueryable<Location> Find()
{
   return AllCountries()
             .Union(AllStates())
             .Union(AllCounties())
             .Union(AllCities())
             .Union(AllNeigbourhoods())
             .Union(AllZipCodes())
             .Union(AllStreets());
}

那些内联方法(例如 AllStates)是私有的 IQueryable 方法,例如:

private IQueryable<Location> AllCountries()
{
   var db = new MyCustomDataContext();
   return db.Countries;
}

这一切都很好,但我不喜欢 Find() 方法中代码的外观。

基本上,我想要一个 Repository 方法,它返回所有国家/城市/州等(作为 IQuerable&lt;Location&gt;)。

这样,我的服务层可以这样做:

var countries = repository.Find(somePredicate).OfType<Country>().ToList();

或者这个:

var countries = repository.Find(somePredicate).OfType<City>().ToList();

所以我只需要声明一个 Find 方法。您可以将 Location 类视为我的“聚合根”。

不使用抽象类,这就是我的存储库合约的样子:

IQueryable<City> FindCities();
IQueryable<State> FindStates();
IQueryable<Country> FindCountries();
 ....

糟糕!

这是我的存储库合同目前的样子(我想保持这种方式):

IQueryable<Location> Find();

那么,有什么比拥有所有这些工会更好的想法呢? IQueryable&lt;T&gt; 扩展方法,可以动态链接多个 IQueryable 的?

请记住,我还有一个执行过滤/收集投影(延迟执行)的服务层。存储库需要返回“查询”,而不是具体的集合。

感谢您的帮助。

【问题讨论】:

  • 我不会说你的设计是“错误的”......让我们说“我不明白”。话虽如此,您似乎想要执行以下搜索:特定城市的所有街道;涉及特定街道的所有邮政编码;特定县的所有城市和社区;特定社区的所有街道;等等。那么接近吗?
  • @Neil T - 现场。我希望能够根据谓词搜索任何“位置”(城市/国家/州)。我不想拥有一个包含 IQueryable FindCities()、IQueryable FindStates() 等的存储库。我想要一个能够返回任何位置类型的集合的 Find() 方法。
  • Location也是实体类吗?如果是这样,为什么不能从存储库中检索所有 Location 实例?
  • @Jacob。不,位置不是实体(实体数据模型中不存在)。它是一个仅用于存储库/服务层抽象的抽象类。所有 POCO 都继承自它,这就是允许使用 IQueryable 的原因。

标签: .net linq extension-methods repository-pattern iqueryable


【解决方案1】:

我假设相同的逻辑实体不会存在于两个单独的表中(例如,“城市”不是“州”)。在这种情况下,你会更适合使用Concat 而不是Union

一个简短的帮助方法将使调用看起来更好(警告:未经测试):

// (Defined in the static class MyHelpers)
// Concatenate all sequences into one.
public IQueryable<T> ConcatAll<T>(this IQueryable<T> first,
    params IQueryable<T>[] others)
{
  var ret = first;
  foreach (var other in others)
  {
    ret = ret.Concat(other);
  }

  return ret;
}

...

public IQueryable<Location> Find() 
{
  return MyHelpers.ConcatAll(
    AllCountries(),
    AllStates(),
    AllCounties(),
    AllCities(),
    AllNeigbourhoods(),
    AllZipCodes(),
    AllStreets());

  // OR:

  return AllCountries().ConcatAll(
    AllStates(),
    AllCounties(),
    AllCities(),
    AllNeigbourhoods(),
    AllZipCodes(),
    AllStreets());
} 

【讨论】:

  • @是的,这就是我想要的。但是,我现在重新考虑我的设计(由于@Jacob 的回答)。话虽这么说,这可能是基于我原来的问题的正确答案。谢谢。
【解决方案2】:

实体框架允许您将表映射到使用继承的数据模型。如果您的数据库中有一个包含所有公共字段的 Location 表,并且每个子位置类(例如 City)具有该 Location 表的外键,那么当您检索 Location 对象时从存储库中,您还应该收到继承类的实例。

如果Location 中没有公共字段,那么拥有联合集合似乎没有什么好处。

【讨论】:

  • 同意(部分)。最初我创建了一个位置表,并进行了继承。但问题是,Location 对象中的“公共字段”是基于业务逻辑创建的自定义属性。即 - URL slugs,格式化地址等。所以我只会创建表以满足 EDM 要求 - 应该是相反的方式(IMO)。不过感谢答案 - 将重新考虑我的设计。
  • 让我这样问。可以说我没有位置表。我怎样才能从存储库中返回一个集合中的城市、州、街道的混合包?即 var heapsOfStuff = repository.Find().Where(s => Name.Contains("new york"))。 “名称”可以是所有类型共享的抽象成员。明白我的意思了吗?我希望用户界面能够运行 - “让我找到所有名称中包含“纽约”的位置”。这些位置可以是任何东西(街道、城市等)。我该怎么做?
  • 如果没有基本的Location 实体,我会选择你接受的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-23
  • 1970-01-01
  • 2012-10-15
  • 2020-12-22
  • 2016-09-11
  • 1970-01-01
相关资源
最近更新 更多