📜 ⬆️ ⬇️

Autoregistered Unity .net repositories for EF Code first

Hey. Let's get started

Motivation

  1. There is a project with the Entity framework (> = 5.0.0.0) code first.
  2. You love IoC, but you don’t like endless registrations of new entities.
  3. Unity is used as a container (or it is possible to spend 10 minutes on finishing the sources under your container).
  4. The prospect of writing the same type of code for some reason scares you.

So what this article offers. You connect 2 nuget-packages, implement a simple IRetrievableEntity <TEntity, TId> interface for your Entity (you can simplify the task, inheriting from the ready-made Entity <TId> class), add 2 registration lines to the code and get complete DBContext independence and the possibility resolving repositories for each IRetrievableEntity entity with the ability to build object-oriented (typed) requests to these repositories. Just look:
var employeeRepository = container.Resolve<IRepository<Emloyee, int>>(); var employees = employeeRepository.Get(q => { q = q.Filter(e => e.EmploymentDate >= new DateTime(2014, 9, 1)); if(excludeFired) q = q.Filter(e => !e.Fired); q = q.Include(e => e.Department, p => p.Department.Chief) .OrderBy(p => p.FirstName); }); 

How to quickly start using

You can use repositories without IoC, getting bonuses for building queries and isolation from the context, but the following example and source codes will provide comprehensive information about the most productive and simple application.
1. Install the Rikrop.Core.Data and Rikrop.Core.Data.Unity packages . The first is in the project with Entity-entities, the second is in the project with the database context. I used one project for example, it turned out the following:
 <packages> <package id="EntityFramework" version="5.0.0" targetFramework="net45" /> <package id="Rikrop.Core.Data" version="1.0.1.0" targetFramework="net45" /> <package id="Rikrop.Core.Data.Unity" version="1.0.1.0" targetFramework="net45" /> <package id="Unity" version="3.5.1404.0" targetFramework="net45" /> </packages> 

2. Add to registrations in IoC about the following:
 container.RegisterRepositoryContext<MyDbContext>(); //container.RegisterRepositoryContext(s => new MyDbContext(s), "myConStr"); container.RegisterRepositories(typeof(Department).Assembly); 

RepositoryContext is a wrapper over the DBContext class, respectively, registration takes a generic inheritor parameter from the DBContext. You can register a context with the name of the connection string.
The RegisterRepositories extension method takes as input the Assembly in which the POCO objects are located that implement IRetrievableEntity <TId> .

3. Implement for your POCO IRetrievableEntity. For example:
 public class Department : Entity<Int32>, IRetrievableEntity<Department, Int32> {...} public class Employee : DeactivatableEntity<Int32>, IRetrievableEntity<Employee, Int32> {...} 

4. Done. You can use:
 var departmentRepository = container.Resolve<IRepository<Department, int>>(); departmentRepository.Save(new Department { Name = "TestDepartment" }); var testDeps = departmentRepository.Get(q => q.Filter(dep => dep.Name.Contains("Test"))); 

It is impossible to make a mistake, since generic parameters ensure that correct repositories are resolved:
 //  IDeactivatableRepository    ( ), // ..      DeactivatableEntity. //var departmentRepository2 = container.Resolve<IDeactivatableRepository<Department, int>>(); 

5. If the standard functionality offered by the IRepository <TEntity, in TId> and IDeactivatableRepository <TEntity, in TId> interfaces is not enough for any entity, you can always extend the existing implementation in a couple of simple steps. Set the interface:
 public interface IPersonRepository : IDeactivatableRepository<Person, int> { void ExtensionMethod(); } 

Add the implementation and be sure to mark the attribute:
 [Repository(typeof(IPersonRepository))] public class PersonRepository : DeactivatableRepository<Person, int>, IPersonRepository { public PersonRepository(IRepositoryContext repositoryContext) : base(repositoryContext) { } public void ExtensionMethod() { //       DBContext Console.WriteLine("PersonRepository ExtensionMethod called"); } } 

We ask Unity to find and register all extended repositories in the given assembly:
 //   ""     . container.RegisterCustomRepositories(typeof(Department).Assembly); 

We use:
 //  ""   . var personRepository = container.Resolve<IPersonRepository>(); personRepository.ExtensionMethod(); 

In this case, without the need for advanced methods, you can always use the standard implementation:
 //   Person     ,     DeactivatableEntity. var personRepository2 = container.Resolve<IRepository<Person, int>>(); var personRepository3 = container.Resolve<IDeactivatableRepository<Person, int>>(); 


How it works

There is a basic repository implementation that works with the context through the abstraction IRepositoryContext. Accessing the data set from the repository works thanks to generic DBContext methods:
 public override DbSet<TEntity> Data { get { return Context.Set<TEntity>(); } } 

The key class for working with building queries to the repository is the RepositoryQuery class. The class implements the fluent interface and allows you to include by Expression or text path (the latter may be relevant when loading the properties of the child collections when the path cannot be specified via expression), filter, sort, Skip and Take.
Registration Magic is based on Reflection. When registering repositories in an assembly, all classes inherited from IRetrievableEntity <,> are found, generic arguments are derived from them, new IRepository <,> and Repository <,> types are built with the necessary generic arguments, then everything is recorded by the types newly created through reflection . For extended repositories, the search is performed by attribute:
 foreach (var repositoryType in assembly.GetTypes().Where(type => type.IsClass)) { var repositoryAttribute = repositoryType.GetCustomAttribute<RepositoryAttribute>(); if (repositoryAttribute != null) { container.RegisterType(repositoryAttribute.RepositoryInterfaceType, repositoryType, new TransientLifetimeManager()); } } 

Problems

  1. Only Entity framework and Unity only. The tool was created for our personal purposes and therefore it is rather difficult to find the motivation to implement, for example, registrations for other containers.
  2. The script is suitable for use with a single DBContext - different will not be able to kill a repository. This restriction does not apply to the use of Rikrop.Core.Data without Rikrop.Core.Data.Unity.
  3. Fixed version of Unity. If the Nuget-package for 4.0 does not explicitly indicate the version, then nuget will try to resolve the latest version, despite the fact that it is incompatible with .net 4. If anyone knows a way to get rid of this problem, please inform the PM.
  4. Only .net 4.0 and 4.5.

Links


')

Source: https://habr.com/ru/post/243353/


All Articles