![](/img/trans.png)
[英]Separation of Concerns Practice with Repository, UoW Pattern and Entity Framework
[英]Best practice: entity framework: where to do joins if using repository and UoW patterns
我有一个用户存储库和合作伙伴存储库。 我的存储库不返回IQuerables。 用户实体具有partnerID。 我想通过使用使用Linq的存储库,使用partnerID将两个表用户和伙伴表联接在一起。 但是我不确定在哪里进行这些连接。 在合作伙伴和用户上没有外键,因此我无法通过导航属性执行“包含”。
我知道联接不应该放在存储库中。 加入是否应该在UoW中进行? 还是服务? 就我在何处进行这些联接而言,最佳实践是什么?
在我们公司中,我们将保存必须执行的用例的对象(工作单元)与存储数据的概念(存储库)和数据存储方法(使用实体框架存储在数据库中)分开。
通过使用这种分隔,可以将数据库更改为其他任何存储表的数据库。 例如,您可以使用一系列CSV文件,也可以使用Dapper访问数据库。
实体框架和存储库之间分离的另一个优点是,您可以提供不授予您不希望用户访问的项目的接口。 例如,某些用户只能查询数据,其他用户可以添加或更新数据,只有少数用户可以删除对象。
一个非常好的副作用是,我们可以使用Test Lists
而不是真实的数据库表来对使用存储库的代码进行单元测试。
仅当新的用例需要新的数据时,我们才需要更改所有三个。 我们工作单元中不需要新数据的用户不会注意到差异
DbContext中的DbSet代表我们数据库的表。 每个表都至少具有一个ID作为主键,以及一个可为空的DateTime
对象,该对象标记了宣布对象作废的日期。 后台进程会定期删除所有过时的对象。
完成后一部分是为了防止用户A正在更新记录,而用户B正在删除同一记录。 用户只能将记录标记为过时,不能删除它们。
interface IDbItem
{
int Id {get; } // no need to ever change the primary key
DateTime? Obsolete {get; set;}
}
例如客户:
class Customer : IDbItem
{
public int Id {get; set;}
public DateTime? ObsoleteDate {get; set;}
public string Name {get; set;}
... // other properties
}
DbContext保持尽可能简单:它仅表示表以及表之间的关系
存储库隐藏用于存储数据的存储方法。 它可以是一个数据库,也可以是一系列CSV文件,数据可以分为几个数据库。
该存储库通常具有几个接口:
IQueryable<...>
。 该界面的用户可以执行他们想要的每个查询。 他们无法更改数据。 这样做的好处是您可以隐藏不想公开的属性和表。 用户不能意外更改项目。 Obsolete
数据的接口。 由后台进程用来定期删除过时的数据。 就像实体框架具有代表实体的类(表:客户,订单,订单行等)和代表实体集合的类( IDbSet<Customer>
)一样,存储库具有相似的类和接口。 它们大多数都是可重复使用的,而且只有一线
interface IId
{
int Id {get;}
}
interface IRepositoryEntity : IId
{
bool IsObsolete {get;}
void MarkObsolete();
}
每个存储库项目都可以标记为过时。 常见的基类:
class RepositoryEntity<TSource> : IId, IRepositoryEntity
where TSource : IDbItem
{
public TSource DbItem {get; set;}
// Interface IId
public int Id => this.DbItem.Id;
// Interface IRepositoryEntity
public bool IsObsolete => this.DbItem.ObsoleteDate != null;
public void MarkObsolete()
{
this.DbItem.ObsoleteDate = DateTime.UtcNow;
}
}
例如客户:
interface IReadOnlyCustomer : IId
{
string Name {get;}
...
}
interface ICustomer : IRepositoryItem
{
string Name {get; set;}
}
class Customer : RepositoryEntity<Customer>, IReadOnlyCustomer, ICustomer
{
// Interfaces IId and IRepositoryItem implemented by base class
// Interface ICustomer
public string Name {get; set;}
...
// Interface IReadOnlyCustomer
string IReadOnlyCustomer.Name => this.Name;
...
}
您会看到,Repository Customer只需要实现您实际要公开给外部世界的Customer属性。 该存储库不需要表示您的数据库表。
例如,如果您的数据库具有Customer FirstName
, MiddleName
, FamilyName
拆分值,则可以在get Name函数中将它们连接起来。
存储库集合类似于IDbSet<...>
。 有一个Query only
用于Query only
的接口,以及一个用于Query, Update, Mark Obsolete
。 当然,我们也有完全的访问权限,只授予少数幸福的人。
对于ReadOnly,只要具有IQueryable<TEntity> where TEntity : Iid
就足够了, IQueryable<TEntity> where TEntity : Iid
要查询/添加/更新/过时,我需要一个ISet和一个Set:
interface ISet<TEntity> : IQueryable<TEntity> where TEntity: IRepositoryEntity
{
TEntity Add(TEntity item);
}
class Set<TEntity, TDbEntity> : ISet<TEntity>
where TEntity: IRepositoryEntity,
where TDbEntity: IDbItem
{
public IDbSet<TEntity> DbSet {get; set;}
// implement the interfaces via DbSet
public TEntity Add(TEntity item)
{
// TODO: convert item to a dbItem
return this.DbSet.Add(dbItem);
}
// Similar for IQueryable<TEntity> and IQueryable
}
只读访问和CRUD访问的接口:
interface IReadOnlyRepository : IDisposable
{
IQueryable<IReadOnlyCustomer> Customers {get;}
IQueryable<IReadOnlyOrders> Orders {get;}
}
interface IRepository : IDisposable
{
ISet<ICustomer> Customers {get;}
ISet<IOrder> Orders {get;}
void SaveChanges();
}
那些有权访问ReadOnlyRepository的用户只能查询数据。 他们无法进行任何更改。 有权访问IRepository的用户可以添加项目,更新项目并保存更改。
类存储库实现所有接口:
class Repository : IReadOnlyRepository, // Query Only
IRepository, // Query, Add and Update
IDisposable
{
private readonly dbContext = new CustomerDbContext();
// TODO: Dispose() will Dispose dbContext
// Used by the other interfaces
protected IDbSet<Customer> Customers => this.dbContext.Customers;
protected IDbSet<Orders> Orders => this.dbContext.Orders;
void SaveChanges() {this.dbContext.SaveChanges();}
// IRepository:
ISet<ICustomer> IRepository.Customers => new Set<Customer>{DbSet = this.Customers};
ISet<IOrder> IRepository.Orders => new Set<Order>{DbSet = this.Orders};
void IRepository.SaveChanges() {this.DbContext.SaveChanges();}
// IReadOnlyRepository
IQueryable<IReadOnlyCustomer> IReadOnlyRepository.Customers => this.Customers;
IQueryable<IReadOnlyOrders> IReadOnlyRepository.Orders => this.Orders;
}
似乎有很多代码,但是大多数函数都是一线的,可以调用相应的实体框架函数。
最后,我们需要一个创建存储库的工厂。 如果要在多个存储库中重复使用此功能,请创建一个通用工厂类。 为简单起见,我为订购数据库创建它:
class OrdersRepository
{
public IReadOnlyRepository CreateReadOnly()
{
// TODO: if desired check rights: can this user access this database?
return new Repository();
}
public IRepository CreateUpdateAccess()
{
// TODO: if desired check rights: can this user access this database?
return new Repository();
}
public Repository CreateFullControl()
{
// TODO: if desired check rights: can this user access this database?
return new Repository();
}
实际上:对于删除所有过时项目的后台进程,我们有一个特殊的界面,该界面可以删除一段时间内所有过时的项目。 这里不再提及。
用法:
var repositoryFactory = new RepositoryFactory() {AccessRights = ...}
// I need to query only:
using (var repository = repositoryFactory.CreateUpdatAccess())
{
// you can query, change value and save changes, for instance after a Brexit:
var customersToRemove = repository.Customers.Where(customer => customer.State == "United Kingdom")
foreach (var customerToRemove in customersToRemove);
{
customerToRemove.MarkObsolete();
}
repository.SaveChanges();
}
// I need to change data:
using (var repository = repositoryFactory.CreateReadOnly())
{
// do some queries. Compiler error if you try to change
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.