2017-07-21 1 views
2

Dans mon application, j'ai besoin d'interagir avec deux bases de données. J'ai deux classes de domaine qui sont situées dans deux bases de données différentes. J'ai aussi un modèle de dépôt générique qui accepte un UoW dans son constructeur. Je cherche un moyen d'injecter un UoW approprié basé sur la classe de domaine. Je ne veux pas écrire un deuxième référentiel générique pour la deuxième base de données.. Y a-t-il une solution soignée?Injecter différents DbContexts dans un référentiel générique basé sur la classe Domain - Autofac

public interface IEntity 
{ 
    int Id { get; set; } 
} 

Situé dans la base de données A

public class Team: IEntity 
{ 
    public int Id { get; set; } 
    public string Name{ get; set; } 

} 

Situé dans la base de données B

public class Player: IEntity 
{ 
    public int Id { get; set; } 
    public string FullName { get; set; } 
} 

J'ai aussi un modèle de référentiel générique avec UOW

public interface IUnitOfWork 
{ 
    IList<IEntity> Set<T>(); 
    void SaveChanges(); 
} 

public class DbADbContext : IUnitOfWork 
{ 
    public IList<IEntity> Set<T>() 
    { 
     return new IEntity[] { new User() { Id = 10, FullName = "Eric Cantona" } }; 
    } 

    public void SaveChanges() 
    { 

    } 
} 

public class DbBDataContext: IUnitOfWork 
{ 
    public IList<IEntity> Set<T>() 
    { 
     return new IEntity[] { new Tender() { Id = 1, Title = "Manchester United" } }; 
    } 

    public void SaveChanges() 
    { 

    } 

public interface IRepository<TEntity> where TEntity: class, IEntity 
{ 
    IList<IEntity> Table(); 
} 

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity 
{ 

    protected readonly IUnitOfWork Context; 
    public BaseRepository(IUnitOfWork context) 
    { 
     Context = context; 
    } 

    IList<IEntity> IRepository<TEntity>.Table() 
    { 
     return Context.Set<TEntity>(); 
    } 
} 

J'ai déjà trouvé des articles disant que Autofac remplace l'enregistrement avec la dernière valeur. Je sais que mon problème est de savoir comment DbContexts sont enregistrés.

var builder = new ContainerBuilder(); 
// problem is here 
     builder.RegisterType<DbADbContext >().As<IUnitOfWork>() 
     builder.RegisterType<DbBDbContext >().As<IUnitOfWork>() 

builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IRepository<>)); 
     var container = builder.Build(); 

Répondre

1

Je me suis inspiré de la réponse de @ tdragon.

La première étape est l'enregistrement Nommée DbContext

 builder.RegisterType<Database1>() 
      .Keyed<IUnitOfWork>(DbName.Db1) 
      .Keyed<DbContext>(DbName.Db1).AsSelf().InstancePerRequest(); 

     builder.RegisterType<Database2>() 
      .Keyed<IUnitOfWork>(DbName.Db2) 
      .Keyed<DbContext>(DbName.Db2).AsSelf().InstancePerRequest(); 

S'il vous plaît noter que DbName est juste un ENUM.

Le code suivant analyse l'ensemble de la couche d'accès aux données pour rechercher les classes de domaine. Ensuite, il enregistre ReadOnlyRepository et BaseRepository. la place de ce code est en DIConfig

Type entityType = typeof(IEntity); 
var entityTypes = Assembly.GetAssembly(typeof(IEntity)) 
        .DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(entityType)); 


var baseRepoType = typeof(BaseRepository<>); 
var readOnlyRepoType = typeof(ReadOnlyRepository<>); 
var baseRepoInterfaceType = typeof(IRepository<>); 
var readOnlyRepoInterfaceType = typeof(IReadOnlyRepository<>); 
var dbContextResolver = typeof(DbContextResolverHelper).GetMethod("ResolveDbContext"); 

foreach (var domainType in entityTypes) 
{ 
    var baseRepositoryMaker = baseRepoType.MakeGenericType(domainType); 
    var readonlyRepositoryMarker = readOnlyRepoType.MakeGenericType(domainType); 

var registerAsForBaseRepositoryTypes = baseRepoInterfaceType.MakeGenericType(domainType); 
var registerAsForReadOnlyRepositoryTypes = readOnlyRepoInterfaceType.MakeGenericType(domainType); 

var dbResolver = dbContextResolver.MakeGenericMethod(domainType); 
      // register BaseRepository 
builder.Register(c => Activator.CreateInstance(baseRepositoryMaker, dbResolver.Invoke(null, new object[] { c })) 
      ).As(registerAsForBaseRepositoryTypes).InstancePerRequest(jobTag); 
      //register readonly repositories 
builder.Register(c => Activator.CreateInstance(readonlyRepositoryMarker, dbResolver.Invoke(null, new object[] { c }))) 
      .As(registerAsForReadOnlyRepositoryTypes).InstancePerRequest(jobTag); 

} 

Les méthodes suivantes tentent de trouver DbSet dans chaque DbContext afin de connaître les classes de domaine appartient à qui DataContext/Base de données.

public class DbContextResolverHelper 
{ 
    private static readonly ConcurrentDictionary<Type, DbName> TypeDictionary = new ConcurrentDictionary<Type, DbName>(); 


    public static DbContext ResolveDbContext<TEntity>(IComponentContext c) where TEntity : class, IEntity 
    { 
     var type = typeof(DbSet<TEntity>); 


     var dbName = TypeDictionary.GetOrAdd(type, t => 
     { 

      var typeOfDatabase1 = typeof(Database1); 
      var entityInDatabase1 = typeOfDatabase1 .GetProperties().FirstOrDefault(p => p.PropertyType == type); 
      return entityInDatabase1 != null ? DbName.Db1: DbName.Db2; 


     }); 

     return c.ResolveKeyed<DbContext>(dbName); 
    } 
} 
0

Qu'en est-ce:

builder.RegisterType<DbContextBase>().As<IUnitOfWork>() 

Et

DbADataContext: DbContextBase,IUnitOfWork 
    DbBDataContext: DbContextBase,IUnitOfWork 

Ou dans votre inscription, vous pouvez simplement faire quelque chose comme:

containerBuilder.RegisterGeneric(typeof(DbADataContext<>)).Named("DbADataContext", typeof(IUnitOfWork<>)); 
containerBuilder.RegisterGeneric(typeof(DbBDataContext<>)).Named("DbBDataContext", typeof(IUnitOfWork<>)); 
+0

cela ne fonctionnera pas, le premier ne correspond pas à mes points. le second n'est pas non plus une solution car la résolution dépend du type de 'TEntity' dans le Repository voir ici pour plus d'informations sur les Named et Meta data http://docs.autofac.org/en/latest/ faq/select-by-context.html # option-4-use-metadata – Mahdi

0

Si vous voulez garder seul BaseRepository et son interface, vous devez en quelque sorte configurer, avec l'entité serait géré par quel DbContext. Cela pourrait se faire en partie d'enregistrement de la demande, mais dans ce cas, vous ne pouvez pas enregistrer votre BaseRepostory<T> aussi ouvert générique, mais être explicite dans vos enregistrements, comme ceci:

containerBuilder.RegisterType<DbADataContext>().Named<IUnitOfWork>("A"); 
containerBuilder.RegisterType<DbBDataContext>().Named<IUnitOfWork>("B"); 

containerBuilder.Register(c => new BaseRepository<Team>(c.ResolveNamed<IUnitOfWork>("A")).As<IRepostory<Team>>(); 
containerBuilder.Register(c => new BaseRepository<Player>(c.ResolveNamed<IUnitOfWork>("B")).As<IRepository<Player>>(); 

(juste preuve de concept, le code non testé)

Autofac n'est pas assez intelligent pour connaître "automatiquement" l'unité de travail que vous souhaitez utiliser dans chaque référentiel.

+0

La solution me force à enregistrer le référentiel pour chaque classe de domaine. Il est difficile dans mon cas, car j'ai plus de 200 classes de domaine – Mahdi

+1

@Mahdi pour résoudre ce problème, vous pouvez utiliser l'analyse d'assemblage lire [ce document] (http://docs.autofac.org/en/latest/register/scanning.html) –