简体   繁体   English

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

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

I have a user repository and partner repository. 我有一个用户存储库和合作伙伴存储库。 My repositories do not return IQuerables. 我的存储库不返回IQuerables。 A user entity has a partnerID. 用户实体具有partnerID。 I want to use the partnerID to join the two tables user and partner Table by using the repositories using Linq. 我想通过使用使用Linq的存储库,使用partnerID将两个表用户和伙伴表联接在一起。 However I'm not sure where to do these joins. 但是我不确定在哪里进行这些连接。 There is no foreign key on partner and user so I cannot do a Include via navigation property. 在合作伙伴和用户上没有外键,因此我无法通过导航属性执行“包含”。

I know that the joins should not go in the repository. 我知道联接不应该放在存储库中。 Should the join happen in the UoW? 加入是否应该在UoW中进行? Or a service? 还是服务? What is best practice in terms of where I do these joins? 就我在何处进行这些联接而言,最佳实践是什么?

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

On your aggregates, which ever is the root would be the name of your repo because 在您的集合中,根目录就是您的回购名称,因为

Any references from outside the aggregate should only go to the aggregate root 聚合外部的任何引用都应仅到达聚合根

In our company we separate the object that holds the use cases that must be performed (Unit of Work), from the concept of storing data (Repository) from the method on how data is stored (in a database using entity framework). 在我们公司中,我们将保存必须执行的用例的对象(工作单元)与存储数据的概念(存储库)和数据存储方法(使用实体框架存储在数据库中)分开。

Advantages of separating DbContext from Repository 从存储库中分离DbContext的优点

By using this separation it is possible to change the database to anything else that stores tables. 通过使用这种分隔,可以将数据库更改为其他任何存储表的数据库。 For instance, you could use a sequence of CSV-files, or use Dapper to access the database. 例如,您可以使用一系列CSV文件,也可以使用Dapper访问数据库。

Another advantage of separation between Entity Framework and Repository is that you can provide interfaces that don't give access to items that you don't want users to have access to. 实体框架和存储库之间分离的另一个优点是,您可以提供不授予您不希望用户访问的项目的接口。 For instance, some users may only query data, others may add or update data, and only a few may delete objects. 例如,某些用户只能查询数据,其他用户可以添加或更新数据,只有少数用户可以删除对象。

A very nice side effect is that we can unit test the code that uses the repository with a collection of Test Lists instead of real database tables. 一个非常好的副作用是,我们可以使用Test Lists而不是真实的数据库表来对使用存储库的代码进行单元测试。

Only if new use cases require new data, we need to change all three. 仅当新的用例需要新的数据时,我们才需要更改所有三个。 Users of our unit of work who don't need the new data won't notice the difference 我们工作单元中不需要新数据的用户不会注意到差异

DbContext 的DbContext

The DbSets in the DbContext represent the tables of our database. DbContext中的DbSet代表我们数据库的表。 Every table has at least an Id as primary key, and a nullable DateTime object that marks the date when an object was declared obsolete. 每个表都至少具有一个ID作为主键,以及一个可为空的DateTime对象,该对象标记了宣布对象作废的日期。 A background process regularly removes all objects that are obsolete for some time. 后台进程会定期删除所有过时的对象。

The latter part is done to prevent that user A is updating a record while user B is deleting the same record. 完成后一部分是为了防止用户A正在更新记录,而用户B正在删除同一记录。 Users can only mark records obsolete, they can't delete them. 用户只能将记录标记为过时,不能删除它们。

interface IDbItem
{
    int Id {get; }      // no need to ever change the primary key
    DateTime? Obsolete {get; set;}
}

For example a Customer: 例如客户:

class Customer : IDbItem
{
     public int Id {get; set;}
     public DateTime? ObsoleteDate {get; set;}

     public string Name {get; set;}
     ... // other properties
}

The DbContext is kept as simple as possible: it only represents the tables and the relations between the tables DbContext保持尽可能简单:它仅表示表以及表之间的关系

Repository 知识库

The repository hides which storage method is used to store the data. 存储库隐藏用于存储数据的存储方法。 It could be a database, a series of CSV-files, the data could be divided over several databases. 它可以是一个数据库,也可以是一系列CSV文件,数据可以分为几个数据库。

The repository usually has several interfaces: 该存储库通常具有几个接口:

  • A query-only interface, returning IQueryable<...> of every table that you want to expose. 仅查询接口,返回要公开的每个表的IQueryable<...> Users of this interface can do every query they want. 该界面的用户可以执行他们想要的每个查询。 They can't change data. 他们无法更改数据。 This has the advantage that you can hide properties and tables that you don't want to expose. 这样做的好处是您可以隐藏不想公开的属性和表。 Users can't accidently change items. 用户不能意外更改项目。
  • An interface to Create / Update items, as well as query. 用于创建/更新项目以及查询的界面。 For the few forms that really add or update the database. 对于真正添加或更新数据库的几种形式。 They can also mark items Obsolete. 他们还可以将项目标记为“过时”。
  • An interface to remove data that was marked Obsolete . 用于删除标记为“ Obsolete数据的接口。 Used by a background process to regularly remove obsolete data. 由后台进程用来定期删除过时的数据。

Just like entity framework has classes the represent the entities (tables: customers, orders, order lines, ...) and classes that represent the collection of entities ( IDbSet<Customer> ), the Repository has similar classes and interfaces. 就像实体框架具有代表实体的类(表:客户,订单,订单行等)和代表实体集合的类( IDbSet<Customer> )一样,存储库具有相似的类和接口。 Most of them are re-usable and one-liners 它们大多数都是可重复使用的,而且只有一线

Repository Entity Classes 存储库实体类

interface IId
{
    int Id {get;}
}

interface IRepositoryEntity : IId
{
    bool IsObsolete {get;}
    void MarkObsolete();
}

Every Repository item can be marked obsolete. 每个存储库项目都可以标记为过时。 A common base class: 常见的基类:

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;
     }
}

For example a Customer: 例如客户:

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;
     ...
}

You see that the Repository Customer only needs to implement the Customer properties that you actually want to expose to the outer world. 您会看到,Repository Customer只需要实现您实际要公开给外部世界的Customer属性。 The repository doesn't need to represent your database tables. 该存储库不需要表示您的数据库表。

For example, if your database has split values for Customer FirstName , MiddleName , FamilyName , then your could concatenate them in the get Name function. 例如,如果您的数据库具有Customer FirstNameMiddleNameFamilyName拆分值,则可以在get Name函数中将它们连接起来。

Repository Collections 资料库集合

The Repository collections are similar to IDbSet<...> . 存储库集合类似于IDbSet<...> There is an interface to Query only , and one to Query, Update, Mark Obsolete . 有一个Query only用于Query only的接口,以及一个用于Query, Update, Mark Obsolete Of course we also have full access, granted to the happy few. 当然,我们也有完全的访问权限,只授予少数幸福的人。

For ReadOnly it is enough to have IQueryable<TEntity> where TEntity : Iid 对于ReadOnly,只要具有IQueryable<TEntity> where TEntity : Iid就足够了, IQueryable<TEntity> where TEntity : Iid

To query / add / update / Obselete I need an ISet and a Set: 要查询/添加/更新/过时,我需要一个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
}

The interface for ReadOnly access and for CRUD access: 只读访问和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();
}

Those who have access to the ReadOnlyRepository can only query data. 那些有权访问ReadOnlyRepository的用户只能查询数据。 They can't make any changes. 他们无法进行任何更改。 Those who have access to IRepository can Add items, Update items and Save the changes. 有权访问IRepository的用户可以添加项目,更新项目并保存更改。

Class Repository implements all interfaces: 类存储库实现所有接口:

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;
}

It seems a lot of code, but most functions are one-liners that call the corresponding entity-framework function. 似乎有很多代码,但是大多数函数都是一线的,可以调用相应的实体框架函数。

Finally we need a factory that creates the Repository. 最后,我们需要一个创建存储库的工厂。 If you want to re-use this for several Repositories create a generic factory class. 如果要在多个存储库中重复使用此功能,请创建一个通用工厂类。 For simplicity I create it for the Ordering database: 为简单起见,我为订购数据库创建它:

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();
    }

In fact: for the background process that removes all obsolete items we have a special interface that deletes all items that are obsolete for some time. 实际上:对于删除所有过时项目的后台进程,我们有一个特殊的界面,该界面可以删除一段时间内所有过时的项目。 Not mentioned here anymore. 这里不再提及。

Usage: 用法:

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.

相关问题 存储库,UoW模式和实体框架之间的关注点实践分离 - Separation of Concerns Practice with Repository, UoW Pattern and Entity Framework 使用时,VS2013,Entity Framework 6和WebAPI 2是否使用通用存储库和UoW框架矫枉过正? - When using, VS2013, Entity Framework 6 and WebAPI 2, is using a generic repository and UoW framework overkill? 实体框架UOW模式和存储库的样板资源(如果需要) - Boilerplate resource for Entity Framework UOW pattern and repository if needed 实体框架和 ASP.NET Core UOW 存储库模式 - Entity Framework and ASP.NET Core UOW Repository pattern 实体框架最佳实践 - Entity Framework best practice 实体框架+“使用”模式的最佳实践 - Entity Framework + 'using' pattern best practice 使用实体框架使用存储库和单元工作模式正确处置? - Correct disposing using Repository and Unit Work patterns with Entity Framework? 使用Entity Framework 4.0检索数据的最佳实践 - Best Practice for retrieving data using Entity Framework 4.0 使用Entity Framework实现高级搜索的最佳实践是什么? - What is the best practice to implement advanced search using Entity Framework? 在MVC 4应用程序中使用Unity with Entity Framework的最佳实践是什么 - What is best practice for using Unity with Entity Framework in a MVC 4 application
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM