How to inject my dbContext with Unity

安稳与你 提交于 2019-12-08 19:14:20

问题


How do I inject my dbContext class using Unity? I can't just create a Interface like for my other "normal" classes? What should I do with my RequestContext class and what should my UnityConfig look like?

public class RequestContext : IdentityDbContext<User>
    {
        public RequestContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
            Database.SetInitializer<RequestContext>(new CreateDatabaseIfNotExists<RequestContext>());
        }

        public DbSet<Request> Requests { get; set; }
        public DbSet<Record> Records { get; set; }



        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            base.OnModelCreating(modelBuilder);
        }

        public static RequestContext Create()
        {
            return new RequestContext();
        }
    }

In my Repository class i use it like this but want to inject instead:

 private RequestContext dbContext;
 private IUserRepository _userRepository;

 public RequestRepository(IUserRepository userRepository)
 {
      dbContext = new RequestContext();
      _userRepository = userRepository;
 }

回答1:


I'm usually solving this with a DbContextFactory. This would allow you to create the context when needed and also dispose it when you're done.

public interface IDbContextFactory
{
    IdentityDbContext<User> GetContext();
}

public class DbContextFactory : IDbContextFactory
{
    private readonly IdentityDbContext<User> _context;

    public DbContextFactory()
    {
        _context = new RequestContext("ConnectionStringName");
    }

    public IdentityDbContext<User> GetContext()
    {
        return _context;
    }
}

This factory can easily be injected. You can see a more complete example here: Repository Pattern universal application

With the factory you will also have the option to create the DbContext in constructor or in the method. When using Unity I recommend you to do as little as possible in the constructor, as Unity will resolve the entire chain for you. This means that the DbContext will be created every time the repository is resolved. This would require the class that injects the repository also needs to dispose the repository (which in turn should dispose the DbContext), and what happens when two classes are using the same repository instance? This can obviously be solved with lifetimemanagers and good programming practices, but I find it more elegant to simply open and close the context when needed.

Example for usage in a method:

using (var context = _dbContextFactory.GenerateContext())
{
    return context.Requests.FirstOrDefault(x => x.Id == foo);
}

And a more complete example for your repository:

public class RequestRepository
{
    private IDbContextFactory _contextFactory;

    public RequestRepository(IDbContextFactory contextFactory)
    {
        // DbContext will not be created in constructor, and therefore your repository doesn't have to implement IDisposable.
        _contextFactory= contextFactory;
    }

    public Request FindById(int id)
    {
         // Context will be properly disposed thanks to using.
        using (var context = _dbContextFactory.GenerateContext())
        {
            return context.Requests.FirstOrDefault(x => x.Id == id);
        }
    }
}

And when you're creating your interface for your context I can also recommend you to change DbSet<T> to IDbSet<T> to allow easier unit testing. Example of interface for DbContext.

public interface IDbContext : IDisposable, IObjectContextAdapter
{
        IDbSet<Request> Requests { get; set; }
        IDbSet<Record> Records { get; set; }
        int SaveChanges();

        DbSet Set(Type entityType);
        DbSet<TEntity> Set<TEntity>() where TEntity : class;
}

If your're looking to inject the DbContext in the constructor you could also take a look at the Unit of Work-pattern, which wraps the DbContext and allows several classes to use the same context over a specific lifetime (eg a request). One may argue that EF already implements the Unit of Work-pattern, but I leave that discussion for another time. Here's a couple of examples:

http://www.codeproject.com/Articles/741207/Repository-with-Unit-of-Work-IoC-and-Unit-Test

Onion Architecture, Unit of Work and a generic Repository pattern




回答2:


I'm solving this problem with DbContext.Set<TEntity>() method, DbContext wrapper class and generics.

I have IRepositoryContext interface and RepositoryContext to wrap my DbContext:

public interface IRepositoryContext
{
    DbContext DbContext { get; }

    /// <summary>
    /// Commit data.
    /// </summary>
    void Save();
}

public class RepositoryContext : IRepositoryContext
{
    private readonly DbContext _dbContext;

    public RepositoryContext(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public DbContext DbContext { get { return _dbContext; } }

    public void Save()
    {
        _dbContext.SaveChanges();
    }
}

Ok, then I write bas implementation of generic repository:

 public abstract class RepositoryBase<TEntity, TId> : IRepository<TEntity, TId>
    where TEntity : class , IEntity<TId>, IRetrievableEntity<TEntity, TId>
    where TId : struct
{
    protected readonly IRepositoryContext RepositoryContext;
    protected readonly DbContext Context;

    protected RepositoryBase(IRepositoryContext repositoryContext)
    {
        RepositoryContext = repositoryContext;
    }

    public DbSet<TEntity> Data { get { return RepositoryContext.DbContext.Set<TEntity>(); }

    public TEntity Get(TId id)
    {
        return Data.Find(id);
    }

    public virtual IList<TEntity> GetAll()
    {
        return Data.ToList();
    }

    public virtual TEntity Save(TEntity entity)
    {
        try
        {
            var state = entity.Id.Equals(default(TId)) ? EntityState.Added : EntityState.Modified;
            RepositoryContext.DbContext.Entry(entity).State = state;
            RepositoryContext.Save();
            return entity;
        }
        catch (DbEntityValidationException e)
        {
            throw ValidationExceptionFactory.GetException(e);
        }
    }

    public virtual void Delete(TEntity entity)
    {
        if (entity == null) return;
        Data.Remove(entity);
        Context.SaveChanges();
    }

    public void Commit()
    {
        RepositoryContext.Save();
    }

    public IList<TEntity> Get(Expression<Func<TEntity, bool>> criteria)
    {
        return Data.Where(criteria).ToList();
    }

    // some other base stuff here
}

Ok, now I can register my DbContext with next extension methods:

public static class RikropCoreDataUnityExtensions
{
    #region Const

    private readonly static Type _repositoryInterfaceType = typeof(IRepository<,>);
    private readonly static Type _deactivatableRepositoryInterfaceType = typeof(IDeactivatableRepository<,>);
    private readonly static Type _deactivatableEntityType = typeof(DeactivatableEntity<>);
    private readonly static Type _retrievableEntityType = typeof(IRetrievableEntity<,>);

    #endregion Const

    #region public methods

    /// <summary>
    /// Register wrapper class.
    /// </summary>
    /// <typeparam name="TContext">DbContext type.</typeparam>
    /// <param name="container">Unity-container.</param>
    public static void RegisterRepositoryContext<TContext>(this IUnityContainer container)
        where TContext : DbContext, new()
    {
        container.RegisterType<IRepositoryContext, RepositoryContext>(new InjectionFactory(c => new RepositoryContext(new TContext())));
    }

    /// <summary>
    /// Register wrapper class.
    /// </summary>
    /// <typeparam name="TContext">DbContext type.</typeparam>
    /// <param name="container">Unity-container.</param>
    /// <param name="contextConstructor">DbContext constructor.</param>
    /// <param name="connectionString">Connection string name.</param>
    public static void RegisterRepositoryContext<TContext>(this IUnityContainer container,
        Func<string, TContext> contextConstructor, string connectionString)
        where TContext : DbContext
    {
        container.RegisterType<IRepositoryContext, RepositoryContext>(
            new InjectionFactory(c => new RepositoryContext(contextConstructor(connectionString))));
    }

    /// <summary>
    /// Automatically generation and registration for generic repository marked by attribute.
    /// </summary>
    /// <param name="container">Unity-container.</param>
    /// <param name="assembly">Assembly with repositories marked with RepositoryAttribute.</param>
    public static void RegisterCustomRepositories(this IUnityContainer container, Assembly assembly)
    {
        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());
            }
        }
    }

    /// <summary>
    /// Automatically generation and registration for generic repository for all entities.
    /// </summary>
    /// <param name="container">Unity-container.</param>
    /// <param name="assembly">Assembly with Entities which implements IRetrievableEntity.</param>
    public static void RegisterRepositories(this IUnityContainer container, Assembly assembly)
    {
        foreach (var entityType in assembly.GetTypes().Where(type => type.IsClass))
        {
            if (!entityType.InheritsFromGeneric(_retrievableEntityType))
                continue;

            Type[] typeArgs = entityType.GetGenericTypeArguments(_retrievableEntityType);
            Type constructedRepositoryInterfaceType = _repositoryInterfaceType.MakeGenericType(typeArgs);
            container.RegisterRepository(constructedRepositoryInterfaceType);

            if (entityType.InheritsFrom(_deactivatableEntityType.MakeGenericType(new[] { typeArgs[1] })))
            {
                var constructedDeactivatableRepositoryInterfaceType =
                    _deactivatableRepositoryInterfaceType.MakeGenericType(typeArgs);
                container.RegisterRepository(constructedDeactivatableRepositoryInterfaceType);
            }
        }
    }

    #endregion public methods

    #region private methods

    /// <summary>
    /// Generate and register repository.
    /// </summary>
    /// <param name="container">Unity-container.</param>
    /// <param name="repositoryInterfaceType">Repository interface type.</param>
    private static void RegisterRepository(this IUnityContainer container, Type repositoryInterfaceType)
    {
        var factoryGenerator = new RepositoryGenerator();
        var concreteFactoryType = factoryGenerator.Generate(repositoryInterfaceType);
        container.RegisterType(
            repositoryInterfaceType,
            new TransientLifetimeManager(),
            new InjectionFactory(
                c =>
                {
                    var activator = new RepositoryActivator();
                    return activator.CreateInstance(c, concreteFactoryType);
                }));
    }

    #endregion private methods
}

Finally you can just resolve IRepository<EntityType> on your classes. You just need to register your RepositoryContext:

container.RegisterRepositoryContext<MyDbContext>();
//container.RegisterRepositoryContext(s => new MyDbContext(s), "myConStr");

And your repository will resolve IRepositoryContext and you can have access to DbSet<TEntity> and other DbContext members via IRepositoryContext property.

You can use full source code for repositories, Unity-helpers on Github.



来源:https://stackoverflow.com/questions/36678971/how-to-inject-my-dbcontext-with-unity

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!