Как переключаться между базами данных с одной и той же схемой, но разными именами, используя EF и внедрение зависимостей? - PullRequest
0 голосов
/ 07 июня 2019

У меня есть веб-сервис веб-API, который использует EF для операций с базой данных и Unity для внедрения зависимостей. У меня есть несколько баз данных с разными именами, но одна и та же схема. В каждом розничном магазине есть одна база данных. Когда пользователь входит в систему, в зависимости от своих привилегий, он может выбрать, с каким магазином он хочет работать. Это сложная задача с использованием внедрения зависимостей, потому что мне нужно изменить базу данных после внедрения репозитория. У меня есть кое-что, что работает, но я не уверен, что это лучший подход.

Мои конкретные вопросы:

  • Это хороший подход к этой проблеме? Я видел другие вопросы, в которых упоминается об изменении строки подключения во время выполнения, но я думаю, что мне нужно либо иметь строку подключения для магазина в моем Web.Config, либо как-то динамически построить строку подключения.

  • Нужна ли логика Dispose на моем заводе? Если бы я вводил репозиторий напрямую, я бы знал, что он мне не нужен. Поскольку я генерирую репо из внедренной фабрики, могу ли я верить, что Unity утилизирует репо и в какой-то момент закрывает соединения db? Стоит ли использовать вместо операторов using сгенерированные репозитории?

Некоторые вопросы, на которые я смотрел, пытаясь решить эту проблему: этот , этот и этот . Однако никто из них не выполняет то, что я пытаюсь сделать напрямую. Ниже мое текущее решение.

Это мой репозиторий и его интерфейс. Я упустил некоторые методы для краткости:

IGenericRepository

public interface IGenericRepository<T> where T: class
{
    IQueryable<T> Get();
    void ChangeDatabase(string database);
    void Update(T entityToUpdate);
    void Save();
}

Общий репозиторий

public class GenericRepository<TDbSet, TDbContext> : 
    IGenericRepository<TDbSet> where TDbSet : class
    where TDbContext : DbContext, new()
{
    internal DbContext Context;
    internal DbSet<TDbSet> DbSet;
    public GenericRepository() : this(new TDbContext())
    {
    }

    public GenericRepository(TDbContext context)
    {
        Context = context;
        DbSet = Context.Set<TDbSet>();
    }

    public virtual IQueryable<TDbSet> Get()
    {
        return DbSet;
    }       

    public void ChangeDatabase(string database)
    {
        var dbConnection = Context.Database.Connection;

        if (database == null || dbConnection.Database == database)
            return;

        if (dbConnection.State == ConnectionState.Closed)
        {
            dbConnection.Open();
        }

        Context.Database.Connection.ChangeDatabase(database);
    }

    public virtual void Update(TDbSet entityToUpdate)
    {
        DbSet.Attach(entityToUpdate);
        Context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public virtual void Save()
    {
        Context.SaveChanges();
    }
}

Чтобы использовать внедрение зависимостей, я внедряю фабрику репозиториев, в которую я могу передать имя базы данных. Фабрика создает хранилище с базой данных по умолчанию для строки подключения, изменяет базу данных на указанную и возвращает хранилище.

IRepositoryFactory

public interface IRepositoryFactory     
{
    IGenericRepository<TDbSet> GetRepository<TDbSet>(string dbName) where TDbSet : class;
}

StoreEntitiesFactory

public class StoreEntitiesFactory : IRepositoryFactory
{
    private bool _disposed;
    readonly StoreEntities _context;

    public StoreEntitiesFactory()
    {
        _context = new StoreEntities();
    }

    public IGenericRepository<TDbSet> GetRepository<TDbSet>(string dbName) where TDbSet : class
    {
        var repo = new GenericRepository<TDbSet, StoreEntities>(_context);

        repo.ChangeDatabase(dbName);

        return repo;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            _context.Dispose();
        }

        _disposed = true;
    }

    ~StoreEntitiesFactory()
    {
        Dispose(false);
    }
}

Вот как я добавляю фабрику хранилища в мой файл WebApiConfig:

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        var container = new UnityContainer();       

        container.RegisterType<IRepositoryFactory, StoreEntitiesFactory>(new HierarchicalLifetimeManager());

        config.DependencyResolver = new UnityResolver(container);
    }
}

Наконец, вот как я бы использовал фабрику в моем контроллере:

StoreController

public class StoreController : ApiController
{
    private readonly IRepositoryFactory _storeEntitiesRepoFactory;

    public StoreController(IRepositoryFactory storeEntitiesRepoFactory)
    {
        _storeEntitiesRepoFactory = storeEntitiesRepoFactory;        
    }

    [HttpGet]
    public IHttpActionResult Get()
    {
        var dbName = getStoreDbName(storeNumberWeGotFromSomewhere);

        try
        {
            var employeeRepo = _storeEntitiesRepoFactory.GetRepository<Employee>(dbName);
            var inventoryRepo = _storeEntitiesRepoFactory.GetRepository<Inventory>(dbName);

            var employees = employeeRepo.Get().ToList();
            var inventory = inventoryRepo.Get().ToList();
        }
        catch (Exception ex)
        {
            return InternalServerError();
        }
    }
}

Ответы [ 2 ]

1 голос
/ 07 июня 2019

Я думаю, вы, вероятно, хотите, чтобы ваши IRepositoryFactory реализации возвращали один и тот же репозиторий для того же dbName. Как написано сейчас, вызов StoreEntitesFactory.GetRepository с двумя различными параметрами dbName вызовет проблемы, поскольку он дает один и тот же экземпляр StoreEntites для каждого хранилища.

Для иллюстрации ...

public class DemonstrationController
{
    private readonly IRepositoryFactory _storeEntitiesRepoFactory;

    public DemonstrationController(IRepositoryFactory storeEntitiesRepoFactory)
    {
        _storeEntitiesRepoFactory = storeEntitiesRepoFactory;
    }

    [HttpGet]
    public IHttpActionResult Get()
    {
        var empRepo1 = _storeEntitiesRepoFactory.GetRepository("DB1");
        var empRepo2 = _storeEntitiesRepoFactory.GetRepository("DB2");

        // After the second line, empRepo1 is connected to "DB2" since both repositories are referencing the same
        // instance of StoreEntities
    }
}

Если вы изменили StoreEntitiesFactory для возврата того же хранилища на основе заданного параметра, это решит эту проблему.

public class StoreEntitiesFactory : IRepositoryFactory
{
    private bool _disposed;
    private Dictionary<string, StoreEntities> _contextLookup;

    public StoreEntitiesFactory()
    {
        _contextLookup = new Dictionary<string, StoreEntities>();
    }

    public IGenericRepository<TDbSet> GetRepository<TDbSet>(string dbName) where TDbSet : class
    {
        if (!_contextLookup.TryGetValue(dbName, out StoreEntities context))
        {
            context = new StoreEntities();
            // You would set up the database here instead of in the Repository, and you could eliminate
            // the ChangeDatabase function.

            _contextLookup.Add(dbName, context);
        }
        return new GenericRepository<TDbSet, StoreEntities>(context);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                foreach (var context in _contextLookup.Values)
                {
                    context.Dispose();
                }
            }
            _disposed = true;
        }
    }
}

Что касается второго вопроса, вам понадобится логика утилизации на фабрике, поскольку она владеет экземплярами StoreEntities, которые создаются. Нет необходимости использовать операторы using вокруг репозиториев, которые он создает, просто дайте Unity избавиться от фабрики.

1 голос
/ 07 июня 2019

Я бы порекомендовал вам использовать шаблон проектирования, называемый шаблоном стратегии, для решения этой проблемы.

Этот шаблон позволяет переключаться между двумя или более стратегиями во время выполнения.Ссылка: https://en.wikipedia.org/wiki/Strategy_pattern

Для внедрения я бы предложил вам зарегистрировать два конкретных класса в Unity, по одному для каждого соединения с БД, и вызвать метод Resolve для того, который вам необходим для передачи строки для создания экземпляра БД..

IUnityContainer container = new UnityContainer();
container.RegisterType<ICar, BMW>();
container.RegisterType<ICar, Audi>("LuxuryCar");

ICar bmw = container.Resolve<ICar>();  // returns the BMW object
ICar audi = container.Resolve<ICar>("LuxuryCar"); // returns the Audi object

Справка: https://www.tutorialsteacher.com/ioc/register-and-resolve-in-unity-container

Об утилизации вы можете настроить все эти конкретные классы для БД как одиночные, и разрешить все соединения открываться, но вам потребуетсячтобы проверить, возможно ли это для вашего приложения.

...