我在使用依赖注入时相当新,我认为我必须忽略一些非常简单的东西 .
我有一个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无法找到我的控制器的单参数构造函数 .
根据下面的评论,我想我真正需要的是一种基于每个请求实例化 DbContext
的方法 . 我不知道该怎么做 .
任何提示将不胜感激 .
3 回答
我们来看看您的注册情况:
您在启动时创建了两个
AnimalEntities
实例,但这些实例在整个应用程序的持续时间内重用 . 这是terrible idea . 您可能打算使用one DbContext per request,但InjectionConstructor
包装的实例是常量 .您应该将配置更改为以下内容:
这更简单,现在
AnimalEntities
也被注册为'scoped' .有什么好处的是,一旦范围(Web请求)结束,
Unity
现在将处理您的AnimalEntities
. 这可以防止您必须在AnimalEntities
的使用者上实现IDisposable
,如here和here所述 .我弄清楚发生了什么 . 有几个人指出,我的存储库不需要继承IDisposable,因为Unity容器会在时机成熟时处理这些存储库 . 但是,这不是我问题的根源 .
要克服的主要挑战是每个请求获得一个
dbContext
. 我的IGenericRepository
界面保持不变但我的GenericRepository
实现现在看起来像这样:默认构造函数现在负责创建实例化类时指定类型的新
DbContext
(我的应用程序中实际上有多种类型的DbContext
) . 这允许为每个Web请求创建新的DbContext
. 我在原始存储库实现中使用using
语句对此进行了测试 . 我能够验证我不再获得关于DbContext
在后续请求中处理的异常 .我的
WebApiConfig
现在看起来像这样:导致我很痛苦的一件事是我没有意识到我仍然必须调用
InjectionConstructor
才能使用存储库类的无参数构造函数 . 不包括InjectionConstructor
导致我得到关于我的控制器的构造函数未找到的错误 .一旦我克服了这个障碍,我就可以将我的控制器更改为下面的内容 . 这里的主要区别是我不再使用
using
语句:我解决问题的方式与这些答案的建议略有不同 .
我在一个MVC应用程序,但逻辑应该类似于此 .
正如其他人所说,在内部创建一个对象的实例,
InjectionContructor
本质上创建了该实例的静态副本,该副本用于解析类型的所有未来实例 . 要解决这个问题,我只需将上下文注册为一种类型,然后让Unity在解析服务时解析上下文 . 默认情况下,它会分别创建一个新实例时间:UnityConfig:
PrimaryContext:
LocationService:
我无法详细说明它是如何工作的,但这似乎解决了我遇到的问题(我得到了相同的错误信息)而且它非常简单 .