代码之家  ›  专栏  ›  技术社区  ›  Bruno

在WebApi项目上使用Unity依赖项注入时,会释放DbContext

  •  3
  • Bruno  · 技术社区  · 7 年前

    我对使用依赖注入相当陌生,我想我一定忽略了一些非常简单的东西。

    我有一个Web API项目,我正在注册通用存储库。存储库将dbContext作为其构造函数中的参数。

    我觉得奇怪的是,我可以对服务进行一次成功调用,但任何后续调用都告诉我dbcontext已被释放。我确实在那里有一个using语句,但这不应该是一个问题,因为DI应该为每个web请求创建依赖项的新实例(尽管我可能错了)。

    这是我的通用存储库:

     public class GenericRepository<T> : IGenericRepository<T> where T : class
    {
        internal DbContext _context;
        internal DbSet<T> _dbSet;
        private bool disposed;
    
        public GenericRepository(DbContext context)
        {
            _context = context;
            _dbSet = _context.Set<T>();
        }
    
        /// <summary>
        /// This constructor will set the database of the repository 
        /// to the one indicated by the "database" parameter
        /// </summary>
        /// <param name="context"></param>
        /// <param name="database"></param>       
        public GenericRepository(string database = null)
        {
            SetDatabase(database);
        }
    
        public void SetDatabase(string database)
        {
            var dbConnection = _context.Database.Connection;
            if (string.IsNullOrEmpty(database) || dbConnection.Database == database)
                return;
    
            if (dbConnection.State == ConnectionState.Closed)
                dbConnection.Open();
    
            _context.Database.Connection.ChangeDatabase(database);
        }
    
        public virtual IQueryable<T> Get()
        {
            return _dbSet;
        }
    
        public virtual T GetById(object id)
        {
            return _dbSet.Find(id);
        }
    
        public virtual void Insert(T entity)
        {
            _dbSet.Add(entity);
        }
    
        public virtual void Delete(object id)
        {
            T entityToDelete = _dbSet.Find(id);
            Delete(entityToDelete);
        }
    
        public virtual void Delete(T entityToDelete)
        {
            if (_context.Entry(entityToDelete).State == EntityState.Detached)
            {
                _dbSet.Attach(entityToDelete);
            }
    
            _dbSet.Remove(entityToDelete);
        }
    
        public virtual void Update(T entityToUpdate)
        {
            _dbSet.Attach(entityToUpdate);
            _context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    
        public virtual void Save()
        {
            _context.SaveChanges();
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;
    
            if (disposing)
            {
                //free managed objects here
                _context.Dispose();
            }
    
            //free any unmanaged objects here
            disposed = true;
        }
    
        ~GenericRepository()
        {
            Dispose(false);
        }
    }
    

    这是我的通用存储库界面:

     public interface IGenericRepository<T> : IDisposable where T : class
    {
        void SetDatabase(string database);
        IQueryable<T> Get();       
        T GetById(object id);
        void Insert(T entity);
        void Delete(object id);
        void Delete(T entityToDelete);
        void Update(T entityToUpdate);
        void Save();
    }
    

    这是我的网络配置:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            var container = new UnityContainer();
    
            container.RegisterType<IGenericRepository<Cat>, GenericRepository<Cat>>(new HierarchicalLifetimeManager(), new InjectionConstructor(new AnimalEntities()));
            container.RegisterType<IGenericRepository<Dog>, GenericRepository<Dog>>(new HierarchicalLifetimeManager(), new InjectionConstructor(new AnimalEntities()));           
    
            config.DependencyResolver = new UnityResolver(container);
    
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
    

    这是我的DependencyResolver(相当标准):

    public class UnityResolver : IDependencyResolver
    {
        protected IUnityContainer container;
    
        public UnityResolver(IUnityContainer container)
        {
            this.container = container ?? throw new ArgumentNullException(nameof(container));
        }
    
        public object GetService(Type serviceType)
        {
            try
            {
                return container.Resolve(serviceType);
            }
            catch (ResolutionFailedException)
            {
                return null;
            }
        }
    
        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return container.ResolveAll(serviceType);
            }
            catch (ResolutionFailedException)
            {
                return new List<object>();
            }
        }
    
        public IDependencyScope BeginScope()
        {
            var child = container.CreateChildContainer();
            return new UnityResolver(child);
        }
    
        public void Dispose()
        {
            Dispose(true);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            container.Dispose();
        }
    }
    

    最后,这是给我带来麻烦的控制器的一部分:

    public class AnimalController : ApiController
    {
        private readonly IGenericRepository<Cat> _catRepo;
        private readonly IGenericRepository<Dog> _dogPackRepo;
    
        public AnimalController(IGenericRepository<Cat> catRepository,
            IGenericRepository<Dog> dogRepository)
        {
            _catRepo = catRepository;
            _dogRepo = dogRepository;
        }
    
        [HttpGet]
        public AnimalDetails GetAnimalDetails(int tagId)
        {
            var animalDetails = new animalDetails();
    
            try
            {
                var dbName = getAnimalName(tagId);
    
                if (dbName == null)
                {
                    animalDetails.ErrorMessage = $"Could not find animal name for tag Id {tagId}";
                    return animalDetails;
                }
    
            }
            catch (Exception ex)
            {
                //todo: add logging
                Console.WriteLine(ex.Message);
                animalDetails.ErrorMessage = ex.Message;
                return animalDetails;
            }
    
            return animalDetails;
        }
    
        private string getAnimalName(int tagId)
        {
            try
            {
                //todo: fix DI so dbcontext is created on each call to the controller
                using (_catRepo)
                {
                    return _catRepo.Get().Where(s => s.TagId == tagId.ToString()).SingleOrDefault();
                }
            }
            catch (Exception e)
            {
                //todo: add logging
                Console.WriteLine(e);
                throw;
            }
        }       
    }
    

    围绕\u catRepo对象的using语句的行为不符合预期。在我进行第一次服务调用之后,catRepo就被处理掉了。在随后的调用中,我希望实例化一个新的\u catRepo。然而,情况并非如此,因为我得到的错误是关于正在处理的dbcontext的。

    我试着把LifeTimeManager换成其他一些可用的,但没用。

    根据下面的评论,我想我真正需要的是一种实例化 DbContext 基于每个请求。不过我不知道该怎么做。

    如有任何提示,我们将不胜感激。

    3 回复  |  直到 7 年前
        1
  •  4
  •   NightOwl888 Jabrwoky    7 年前

    让我们看看您的注册:

    container.RegisterType<IGenericRepository<Cat>, GenericRepository<Cat>>(
        new HierarchicalLifetimeManager(), 
        new InjectionConstructor(new AnimalEntities()));
    
    container.RegisterType<IGenericRepository<Dog>, GenericRepository<Dog>>(
        new HierarchicalLifetimeManager(), 
        new InjectionConstructor(new AnimalEntities()));
    

    您正在创建两个 AnimalEntities 在启动时,这些实例将在整个应用程序期间重用。这是一个 terrible idea . 你可能打算 one DbContext per request ,但实例由 InjectionConstructor 是一个常数。

    您应该将配置更改为以下内容:

    container.RegisterType<IGenericRepository<Cat>, GenericRepository<Cat>>(
        new HierarchicalLifetimeManager());
    
    container.RegisterType<IGenericRepository<Dog>, GenericRepository<Dog>>(
        new HierarchicalLifetimeManager());
    
    // Separate 'scoped' registration for AnimalEntities.
    container.Register<AnimalEntities>(
        new HierarchicalLifetimeManager()
        new InjectionFactory(c => new AnimalEntities()));
    

    这要简单得多,现在 也注册为“范围”。

    这件事的好处是 Unity 现在将处理您的 动物性 一旦作用域(web请求)结束。这可以防止您不得不实施 IDisposable 关于消费者 动物性 ,如前所述 here here .

        2
  •  0
  •   Bruno    7 年前

    我知道发生了什么。正如一些人指出的那样,我的存储库不需要从IDisposable继承,因为Unity容器将在适当的时候处理这些存储库。然而,这并不是我问题的根源。

    要克服的主要挑战是获得一个 dbContext IGenericRepository 界面保持不变,但我的 GenericRepository 现在的实现如下所示:

    public class GenericRepository<TDbSet, TDbContext> : 
        IGenericRepository<TDbSet> where TDbSet : class
        where TDbContext : DbContext, new()
    {
        internal DbContext _context;
        internal DbSet<TDbSet> _dbSet;
    
        public GenericRepository(DbContext context)
        {
            _context = context;
            _dbSet = _context.Set<TDbSet>();
        }
    
        public GenericRepository() : this(new TDbContext())
        {
        }
    
        /// <summary>
        /// This constructor will set the database of the repository 
        /// to the one indicated by the "database" parameter
        /// </summary>
        /// <param name="context"></param>
        /// <param name="database"></param>       
        public GenericRepository(string database = null)
        {
            SetDatabase(database);
        }
    
        public void SetDatabase(string database)
        {
            var dbConnection = _context.Database.Connection;
            if (string.IsNullOrEmpty(database) || dbConnection.Database == database)
                return;
    
            if (dbConnection.State == ConnectionState.Closed)
                dbConnection.Open();
    
            _context.Database.Connection.ChangeDatabase(database);
        }
    
        public virtual IQueryable<TDbSet> Get()
        {
            return _dbSet;
        }
    
        public virtual TDbSet GetById(object id)
        {
            return _dbSet.Find(id);
        }
    
        public virtual void Insert(TDbSet entity)
        {
            _dbSet.Add(entity);
        }
    
        public virtual void Delete(object id)
        {
            TDbSet entityToDelete = _dbSet.Find(id);
            Delete(entityToDelete);
        }
    
        public virtual void Delete(TDbSet entityToDelete)
        {
            if (_context.Entry(entityToDelete).State == EntityState.Detached)
            {
                _dbSet.Attach(entityToDelete);
            }
    
            _dbSet.Remove(entityToDelete);
        }
    
        public virtual void Update(TDbSet entityToUpdate)
        {
            _dbSet.Attach(entityToUpdate);
            _context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    
        public virtual void Save()
        {
            _context.SaveChanges();
        }
    }
    

    默认构造函数现在负责创建新的 DbContext 数据库上下文 数据库上下文 为每个web请求创建。我通过使用 using 声明。我能够验证我不再得到关于 数据库上下文 在后续请求中处理。

    我的 WebApiConfig 现在看起来像这样:

     public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            var container = new UnityContainer();
    
            container.RegisterType<IGenericRepository<Cat>, GenericRepository<Cat, AnimalEntities>>(new HierarchicalLifetimeManager(), new InjectionConstructor());
            container.RegisterType<IGenericRepository<Dog>, GenericRepository<Dog, AnimalEntities>>(new HierarchicalLifetimeManager(), new InjectionConstructor());                                  
    
            config.DependencyResolver = new UnityResolver(container);
    
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
    

    有一件事让我很痛苦,那就是我没有意识到我还得打电话给 InjectionConstructor 注入构造函数

    一旦我跨过那个障碍,我就能够改变我下面的控制器。这里的主要区别是我不再使用 声明:

    public class IntegrationController : ApiController
    {
        private readonly IGenericRepository<Cat> _catRepo;
        private readonly IGenericRepository<Dog> _dogPackRepo;
    
        public IntegrationController(IGenericRepository<Cat> catRepository,
            IGenericRepository<Dog> dogRepository)
        {
            _catRepo = catRepository;
            _dogRepo = dogRepository;
        }
    
    [HttpGet]
    public AnimalDetails GetAnimalDetails(int tagId)
    {
        var animalDetails = new animalDetails();
    
        try
        {
            var dbName = getAnimalName(tagId);
    
            if (dbName == null)
            {
                animalDetails.ErrorMessage = $"Could not find animal name for tag Id {tagId}";
                return animalDetails;
            }
        }
        catch (Exception ex)
        {
            //todo: add logging
            Console.WriteLine(ex.Message);
            animalDetails.ErrorMessage = ex.Message;
            return animalDetails;
        }
    
        return animalDetails;
    }
    
    private string getAnimalName(int tagId)
    {
        try
        {            
             return _catRepo.Get().Where(s => s.TagId == 
               tagId.ToString()).SingleOrDefault();            
        }
        catch (Exception e)
        {
            //todo: add logging
            Console.WriteLine(e);
            throw;
        }
    }       
    }
    
        3
  •  0
  •   Eric Aya    5 年前

    我解决问题的方式与这些答案所暗示的略有不同。

    我使用的是MVC应用程序,但逻辑应该与此类似。

    正如其他人所说,在内部创建对象的实例 InjectionContructor 本质上创建该实例的静态副本,用于解析类型的所有未来实例。为了解决这个问题,我只需将上下文注册为一个类型,然后让Unity在解析服务时解析上下文。默认情况下,它每次都会创建一个新实例:

    单位配置:

    public static void RegisterComponents()
    {
        var container = new UnityContainer();
    
        container.RegisterType<PrimaryContext>(new InjectionConstructor());
        container.RegisterType<LocationService>();
    
        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }
    

    PrimaryContext:

    //Allows for a default value if none is passed
    public PrimaryContext() : base(Settings.Default.db) { }
    public PrimaryContext(string connection) : base(connection)
    {
    }
    

    位置服务:

    PrimaryContext _context;
    public LocationService(PrimaryContext context)
    {
        _context = context;
    }
    

    我无法给出它具体的工作原理,但这似乎已经解决了我遇到的问题(我收到了相同的错误消息),而且非常简单。