【发布时间】:2017-10-04 20:15:37
【问题描述】:
我在使用依赖注入方面还很陌生,我想我一定忽略了一些非常简单的事情。
我有一个 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();
}
这是我的 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(非常标准):
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;
}
}
}
围绕 _catRepo 对象的 using 语句未按预期运行。在我进行第一次服务调用后,_catRepo 被处理掉了。在随后的通话中,我希望实例化一个新的 _catRepo。但是,情况并非如此,因为我得到的错误是关于正在处理的 dbcontext。
我尝试将 LifeTimeManager 更改为其他可用的,但没有帮助。
我还开始走一条不同的路线,通用存储库将采用第二个通用类并从中实例化自己的 dbcontext。但是,当我这样做时,Unity 找不到我的控制器的单参数构造函数。
根据下面的 cmets,我想我真正需要的是一种基于每个请求实例化 DbContext 的方法。不过我不知道该怎么做。
任何提示将不胜感激。
【问题讨论】:
-
当您执行
using (_catRepo)时,在调用结束时会释放 repo,从而释放 db 上下文。不要在那里放置using块。您的 DI 容器应该能够将 db 上下文配置为按请求进行(不确定这在 Unity 中如何工作),但如果设置正确,则无需调用repo.Dispose() -
不应该对 Web 服务的后续调用实例化一个新的 _catRepo 吗?我想我一定是在设置中遗漏了一些东西......
-
DI 容器应该为您处理资源。您不必手动操作。
-
您仅在应用程序启动时创建上下文,因此如果第一个请求发生并且您处置了数据库,则下一个请求没有更多的数据库可供使用。尝试更改它以根据请求创建和处理数据库。
-
另外,我之前也遇到过这个兔子洞:我不推荐通用存储库。使用特定的存储库。稍后当您需要非 CRUD 的特定于域的操作/事务等时,您会感谢我
标签: c# dependency-injection asp.net-web-api2 unity-container