DbContext est supprimé lors de l’utilisation de Unity Dependency Injection sur un projet WebApi

Je suis assez nouveau pour utiliser l’dependency injection et je pense que je dois négliger quelque chose de vraiment simple.

J’ai un projet d’API Web dans lequel j’enregistre des référentiels génériques. Les référentiels prennent un dbContext en tant que paramètre dans leur constructeur.

Le comportement que je trouve étrange est que je peux passer un appel réussi au service, mais tous les appels suivants me disent que le dbcontext a été supprimé. J’ai effectivement une instruction using mais cela ne devrait pas poser de problème, car DI est censé créer de nouvelles instances de mes dépendances pour chaque requête Web (bien que je puisse me tromper).

Voici mon référentiel générique:

public class GenericRepository : IGenericRepository where T : class { internal DbContext _context; internal DbSet _dbSet; private bool disposed; public GenericRepository(DbContext context) { _context = context; _dbSet = _context.Set(); } ///  /// This constructor will set the database of the repository /// to the one indicated by the "database" parameter ///  ///  ///  public GenericRepository(ssortingng database = null) { SetDatabase(database); } public void SetDatabase(ssortingng database) { var dbConnection = _context.Database.Connection; if (ssortingng.IsNullOrEmpty(database) || dbConnection.Database == database) return; if (dbConnection.State == ConnectionState.Closed) dbConnection.Open(); _context.Database.Connection.ChangeDatabase(database); } public virtual IQueryable 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); } } 

Voici mon interface de repository générique:

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

Voici mon WebApiConfig:

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

Voici mon DependencyResolver (qui est assez 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 GetServices(Type serviceType) { try { return container.ResolveAll(serviceType); } catch (ResolutionFailedException) { return new List(); } } public IDependencyScope BeginScope() { var child = container.CreateChildContainer(); return new UnityResolver(child); } public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { container.Dispose(); } } 

Et enfin, cela fait partie du contrôleur qui me pose problème:

 public class AnimalController : ApiController { private readonly IGenericRepository _catRepo; private readonly IGenericRepository _dogPackRepo; public AnimalController(IGenericRepository catRepository, IGenericRepository 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 ssortingng 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.ToSsortingng()).SingleOrDefault(); } } catch (Exception e) { //todo: add logging Console.WriteLine(e); throw; } } } 

L’instruction using autour de l’object _catRepo ne se comporte pas comme prévu. Après le premier appel de service, le _catRepo est éliminé. Lors d’un prochain appel, je m’attends à ce qu’un nouveau _catRepo soit instancié. Cependant, ce n’est pas le cas depuis l’erreur que je reçois parle de la suppression de dbcontext.

J’ai essayé de remplacer LifeTimeManager par d’autres, mais cela n’a pas aidé.

J’ai également commencé à emprunter un chemin différent où le référentiel générique prendrait une deuxième classe générique et instancierait son propre dbcontext. Cependant, lorsque j’ai fait cela, Unity ne pouvait pas trouver le constructeur à paramètre unique de mon contrôleur.

Je suppose que ce dont j’ai vraiment besoin, d’après les commentaires ci-dessous, est un moyen d’instancier DbContext sur demande. Je ne sais pas comment s’y prendre si.

Toute allusion serait grandement appréciée.

Jetons un coup d’oeil à votre inscription:

 container.RegisterType, GenericRepository>( new HierarchicalLifetimeManager(), new InjectionConstructor(new AnimalEntities())); container.RegisterType, GenericRepository>( new HierarchicalLifetimeManager(), new InjectionConstructor(new AnimalEntities())); 

Vous créez deux instances d’ AnimalEntities au démarrage, mais ces instances sont réutilisées pour toute la durée de l’application. C’est une idée terrible . Vous avez probablement l’intention d’avoir un DbContext par requête , mais l’instance encapsulée par InjectionConstructor est une constante.

Vous devriez modifier votre configuration comme suit:

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

C’est beaucoup plus simple et maintenant, AnimalEntities est également enregistré comme “périmètre”.

Ce qui est bien avec cela, c’est que Unity disposera désormais de vos AnimalEntities une fois que la scope (la requête Web) sera terminée. Cela vous évite d’avoir à mettre en œuvre IDisposable sur les consommateurs d’ AnimalEntities , comme expliqué ici et ici .

J’ai compris ce qui se passait. Comme plusieurs personnes l’ont indiqué, mon référentiel n’a pas besoin d’hériter d’IDisposable car le conteneur Unity disposera de ces référentiels le moment venu. Cependant, ce n’était pas la racine de mes problèmes.

Le principal défi à surmonter consistait à obtenir un dbContext par requête. Mon interface IGenericRepository est restée la même, mais mon implémentation GenericRepository ressemble maintenant à ceci:

 public class GenericRepository : IGenericRepository where TDbSet : class where TDbContext : DbContext, new() { internal DbContext _context; internal DbSet _dbSet; public GenericRepository(DbContext context) { _context = context; _dbSet = _context.Set(); } public GenericRepository() : this(new TDbContext()) { } ///  /// This constructor will set the database of the repository /// to the one indicated by the "database" parameter ///  ///  ///  public GenericRepository(ssortingng database = null) { SetDatabase(database); } public void SetDatabase(ssortingng database) { var dbConnection = _context.Database.Connection; if (ssortingng.IsNullOrEmpty(database) || dbConnection.Database == database) return; if (dbConnection.State == ConnectionState.Closed) dbConnection.Open(); _context.Database.Connection.ChangeDatabase(database); } public virtual IQueryable 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(); } } 

Le constructeur par défaut est maintenant responsable de la création d’un nouveau DbContext du type spécifié lors de l’instanciation de la classe (j’ai en fait plusieurs types de DbContext dans mon application). Cela permet de créer un nouveau DbContext pour chaque requête Web. J’ai testé cela en utilisant l’instruction using dans l’implémentation de mon référentiel d’origine. J’ai pu vérifier que je ne DbContext plus l’exception concernant la DbContext lors de demandes ultérieures.

Mon WebApiConfig ressemble maintenant à ceci:

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

Une chose qui me causait beaucoup de douleur ici est que je n’avais pas réalisé que j’avais toujours besoin d’appeler InjectionConstructor pour pouvoir utiliser le constructeur sans paramètre de la classe repository. Si InjectionConstructor n’incluait pas, j’ai eu l’erreur de ne pas trouver le constructeur de mon contrôleur.

Une fois que j’ai surmonté cet obstacle, j’ai pu changer de contrôleur ce que j’ai ci-dessous. La principale différence ici est que je n’utilise plus d’instructions:

 public class IntegrationController : ApiController { private readonly IGenericRepository _catRepo; private readonly IGenericRepository _dogPackRepo; public IntegrationController(IGenericRepository catRepository, IGenericRepository 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 ssortingng getAnimalName(int tagId) { try { return _catRepo.Get().Where(s => s.TagId == tagId.ToSsortingng()).SingleOrDefault(); } catch (Exception e) { //todo: add logging Console.WriteLine(e); throw; } } }