Mic*_*tum 8 .net repository-pattern linq-to-sql
有一个关于IRepository及其用途的问题,它有一个看似很好的答案.
我的问题是:我如何干净地处理彼此相关的实体,而不是IRepository然后只是一个没有实际目的的层?
假设我有这些业务对象:
public class Region {
public Guid InternalId {get; set;}
public string Name {get; set;}
public ICollection<Location> Locations {get; set;}
public Location DefaultLocation {get; set;}
}
public class Location {
public Guid InternalId {get; set;}
public string Name {get; set;}
public Guid RegionId {get; set;}
}
Run Code Online (Sandbox Code Playgroud)
有规则:
那么我的RegionRepository会是什么样子?
public class RegionRepository : IRepository<Region>
{
// Linq To Sql, injected through constructor
private Func<DataContext> _l2sfactory;
public ICollection<Region> GetAll(){
using(var db = _l2sfactory()) {
return db.GetTable<DbRegion>()
.Select(dbr => MapDbObject(dbr))
.ToList();
}
}
private Region MapDbObject(DbRegion dbRegion) {
if(dbRegion == null) return null;
return new Region {
InternalId = dbRegion.ID,
Name = dbRegion.Name,
// Locations is EntitySet<DbLocation>
Locations = dbRegion.Locations.Select(loc => MapLoc(loc)).ToList(),
// DefaultLocation is EntityRef<DbLocation>
DefaultLocation = MapLoc(dbRegion.DefaultLocation)
}
}
private Location MapLoc(DbLocation dbLocation) {
// Where should this come from?
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,RegionRepository也需要获取位置.在我的示例中,我使用Linq To Sql EntitySet/EntiryRef,但现在Region需要处理将Locations映射到Business Objects(因为我有两组对象,业务和L2S对象).
我应该重构这样的事情:
public class RegionRepository : IRepository<Region>
{
private IRepository<Location> _locationRepo;
// snip
private Region MapDbObject(DbRegion dbRegion) {
if(dbRegion == null) return null;
return new Region {
InternalId = dbRegion.ID,
Name = dbRegion.Name,
// Now, LocationRepo needs to concern itself with Regions...
Locations = _locationRepo.GetAllForRegion(dbRegion.ID),
// DefaultLocation is a uniqueidentifier
DefaultLocation = _locationRepo.Get(dbRegion.DefaultLocationId)
}
}
Run Code Online (Sandbox Code Playgroud)
现在我很好地将我的数据层分成了原子库,每个只处理一种类型.我启动了Profiler和......哎呀,选择N + 1.因为每个Region都调用位置服务.我们只有十几个地区和40个左右的位置,所以自然优化是使用DataLoadOptions.问题是RegionRepository不知道LocationRepository是否使用相同的DataContext.毕竟我们在这里注入工厂,所以LocationRepository可能会自己开发它.即使它没有 - 我正在调用提供业务对象的服务方法,因此无论如何都不能使用DataLoadOptions.
啊,我忽略了一些事情.IRepository应该有这样的方法:
public IQueryable<T> Query()
Run Code Online (Sandbox Code Playgroud)
所以现在我会这样做
return new Region {
InternalId = dbRegion.ID,
Name = dbRegion.Name,
// Now, LocationRepo needs to concern itself with Regions...
Locations = _locationRepo.Query()
.Select(loc => loc.RegionId == dbRegion.ID)
.ToList(),
// DefaultLocation is a uniqueidentifier
DefaultLocation = _locationRepo.Get(dbRegion.DefaultLocationId)
}
Run Code Online (Sandbox Code Playgroud)
看起来很好.首先.在第二次检查时,我有单独的业务和L2S对象,所以我仍然没有看到这如何避免SELECT N + 1,因为Query不能只返回GetTable<DbLocation>.
问题似乎是有两组不同的对象.但是如果我使用所有System.Data.LINQ属性([Table],[Column]等)来装饰Business Objects,那就会打破抽象并破坏IRepository的目的.因为也许我想也能够使用其他ORM,此时我现在必须用其他属性来装饰我的业务实体(同样,如果业务实体在一个单独的.Business程序集中,它的消费者现在需要引用所有ORM以及要解析的属性 - 哎呀!).
对我来说,似乎IRepository应该是IService,上面的类应该是这样的:
public class RegionService : IRegionService {
private Func<DataContext> _l2sfactory;
public void Create(Region newRegion) {
// Responsibility 1: Business Validation
// This could of course move into the Region class as
// a bool IsValid(), but that doesn't change the fact that
// the service concerns itself with validation
if(newRegion.Locations == null || newRegion.Locations.Count == 0){
throw new Exception("...");
}
if(newRegion.DefaultLocation == null){
newRegion.DefaultLocation = newRegion.Locations.First();
}
// Responsibility 2: Data Insertion, incl. Foreign Keys
using(var db = _l2sfactory()){
var dbRegion = new DbRegion {
...
}
// Use EntitySet to insert Locations as well
foreach(var location in newRegion.Locations){
var dbLocation = new DbLocation {
}
dbRegion.Locations.Add(dbLocation);
}
// Insert Region AND all Locations
db.InsertOnSubmit(dbRegion);
db.SubmitChanges();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这也解决了鸡蛋问题:
在没有EntitySet的情况下执行此操作几乎是不可能的,因此除非您牺牲数据库上的数据完整性并将其移动到业务逻辑中,否则无法对Region提供程序中的Locations负责.
我看到这个帖子如何被视为不是一个真实的问题,主观和议论,所以请允许我提出一个客观的问题:
我想我真正的问题是:
我对这一切有几个想法: 1. AFAIK 存储库模式的发明比 ORM 早一点。回到纯 SQL 查询的时代,实现存储库是一个好主意,并从实际使用的数据库中购买这个抽象代码。2.我可以说现在完全不需要Repository,但不幸的是,根据我的经验,我不能说任何ORM可以真正将您从所有数据库细节中抽象出来。例如,我无法一次创建 ORM 映射并将其与 ORM 声称支持的任何其他数据库服务器一起使用(特别是我正在谈论 Microsoft EF)。因此,如果您确实希望能够使用不同的数据库服务器,那么您可能仍然需要使用存储库。3.另一个问题很简单:代码重复。当然,有些查询经常被称为您的代码。如果您只保留 ORM 作为存储库,那么您将重复这些查询,因此最好对 ORM 容器进行一定程度的抽象,以保存那些常用的查询。