繁体   English   中英

最佳实践:实体框架:如果使用存储库和UoW模式,在哪里进行联接

[英]Best practice: entity framework: where to do joins if using repository and UoW patterns

我有一个用户存储库和合作伙伴存储库。 我的存储库不返回IQuerables。 用户实体具有partnerID。 我想通过使用使用Linq的存储库,使用partnerID将两个表用户和伙伴表联接在一起。 但是我不确定在哪里进行这些连接。 在合作伙伴和用户上没有外键,因此我无法通过导航属性执行“包含”。

我知道联接不应该放在存储库中。 加入是否应该在UoW中进行? 还是服务? 就我在何处进行这些联接而言,最佳实践是什么?

聚合根: https : //martinfowler.com/bliki/DDD_Aggregate.html

在您的集合中,根目录就是您的回购名称,因为

聚合外部的任何引用都应仅到达聚合根

在我们公司中,我们将保存必须执行的用例的对象(工作单元)与存储数据的概念(存储库)和数据存储方法(使用实体框架存储在数据库中)分开。

从存储库中分离DbContext的优点

通过使用这种分隔,可以将数据库更改为其他任何存储表的数据库。 例如,您可以使用一系列CSV文件,也可以使用Dapper访问数据库。

实体框架和存储库之间分离的另一个优点是,您可以提供不授予您不希望用户访问的项目的接口。 例如,某些用户只能查询数据,其他用户可以添加或更新数据,只有少数用户可以删除对象。

一个非常好的副作用是,我们可以使用Test Lists而不是真实的数据库表来对使用存储库的代码进行单元测试。

仅当新的用例需要新的数据时,我们才需要更改所有三个。 我们工作单元中不需要新数据的用户不会注意到差异

的DbContext

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 FirstNameMiddleNameFamilyName拆分值,则可以在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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM