在WebApi项目上使用Unity依赖注入时,将处置DbContext [英] DbContext is Disposed When Using Unity Dependency Injection on WebApi project

查看:116
本文介绍了在WebApi项目上使用Unity依赖注入时,将处置DbContext的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用依赖注入方面还很陌生,我认为我必须忽略一些非常简单的东西.

I'm fairly new at using dependency injection and I think I must be overlooking something really simple.

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

I have a Web API project where I'm registering generic repositories. The repositories take a dbContext as a parameter in their constructor.

我发现很奇怪的行为是我可以成功调用该服务,但是随后的任何调用都告诉我dbcontext已被处置.我的确有一个using语句,但这应该不是问题,因为DI应该为每个Web请求创建依赖项的新实例(尽管我可能错了).

The behavior I find strange is that I can make one successfull call to the service but any subsequent calls tell me that the dbcontext has been disposed. I do have a using statement in there but that shouldn't be a problem since DI is supposed to be creating new instances of my dependencies for each web request(although I could be wrong).

这是我的通用存储库:

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

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

Here is my generic repository interface:

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

这是我的WebApiConfig:

This is my 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>>(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(非常标准):

This is my DependencyResolver(which is pretty standard):

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

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

And finally this is part of the controller that's giving me trouble:

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

_catRepo对象周围的using语句行为不符合预期.在我进行第一个服务调用后,_catRepo被处理掉了.在随后的调用中,我希望实例化一个新的_catRepo.但是,情况并非如此,因为我遇到的错误是关于dbcontext被处置的.

The using statement around the _catRepo object is not behaving as expected. After I make the first service call the _catRepo is disposed of. On a subsequent call I'm expecting to have a new _catRepo instantiated. However, this is not the case since the error I'm getting talks about the dbcontext being disposed.

我尝试将LifeTimeManager更改为其他可用的功能,但这无济于事.

I've tried changing the LifeTimeManager to some of the other ones available but that didn't help.

我也开始沿着另一条路线走,通用存储库将采用第二个通用类,并从中实例化其自己的dbcontext.但是,当我这样做时,Unity找不到控制器的单参数构造函数.

I also started going down a different route where the generic repository would take a second generic class and instantiate its own dbcontext from it. However, when I did that Unity couldn't find my controller's single-parameter constructor.

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

I guess what I really need, per the comments below, is a way to instantiate the DbContext on a per-request basis. I don't know how to go about doing that though.

任何提示将不胜感激.

推荐答案

我知道发生了什么事.正如几个人所指出的,我的存储库不需要从IDisposable继承,因为在适当的时候Unity容器将处置这些存储库.但是,那不是我问题的根源.

I figured out what was going on. As several people have indicated, my repository doesn't need to inherit from IDisposable since the Unity container will dispose of these repositories when the time is right. However, that wasn't the root of my problems.

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

The main challenge to overcome was getting one dbContext per request. My IGenericRepository interface has stayed the same but my GenericRepository implemenation now looks like this:

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(我的应用程序中实际上有多种类型的DbContext).这允许为每个Web请求创建一个新的DbContext.我通过在原始存储库实现中使用using语句对此进行了测试.我能够验证是否不再有DbContext被处理在后续请求中的异常.

The default constructor is now responsible for creating a new DbContext of the type specified when the class is instantiated(I actually have more than one type of DbContext in my application). This allows for a new DbContext to be created for every web request. I tested this by using the using statement in my original repository implementation. I was able to verify that I no longer get the exception about the DbContext being disposed on subsequent requests.

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

My WebApiConfig now looks like this:

 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才能使用存储库类的无参数构造函数.不包含InjectionConstructor导致我得到有关找不到控制器的构造函数的错误.

One thing that was causing me a lot of pain here is that I didn't realize I still had to call the InjectionConstructor in order to use the parameterless constructor of the repository class. Not including the InjectionConstructor was causing me to get the error about my controller's constructor not being found.

一旦克服了这个障碍,我就可以更改我的控制器下面的内容.这里的主要区别是我不再使用using陈述:

Once I got over that hurdle I was able to change my controller what I have below. The main difference here is that I'm no longer using using statments:

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

这篇关于在WebApi项目上使用Unity依赖注入时,将处置DbContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆