3

Мое приложение увеличивается, и до сих пор у меня есть один MyDbContext, который имеет все необходимые мне таблицы в моем приложении. Я хочу (ради обзора) разделить их на несколько DbContext, например MainDbContext, EstateModuleDbContext, AnotherModuleDbContext и UserDbContext.Использование нескольких DbContexts с общим хранилищем и единицей работы

Я не уверен, как это делается, наверное, как я сейчас, используя инъекции зависимых пакетов (Ninject), чтобы поместить мой DbContext на моем UnitOfWork классе, как:

kernel.Bind(typeof(IUnitOfWork)).To(typeof(UnitOfWork<MyDbContext>)); 

Должен ли я отказаться от этого подхода с инъекцией зависимостей и явной установить DbContext Я хочу использовать на свои услуги, как:

private readonly EstateService _estateService; 

public HomeController() 
{ 
    IUnitOfWork uow = new UnitOfWork<MyDbContext>(); 
    _estateService = new EstateService(uow); 
} 

Вместо:

private readonly EstateService _estateService; 

public HomeController(IUnitOfWork uow) 
{ 
    _estateService = new EstateService(uow); 
} 

Или это еще один подход? Также, как побочный вопрос, мне не нравится передавать uow на мой сервис - есть ли другой (лучший) подход?

Код

Я это IDbContext и MyDbContext:

public interface IDbContext 
{ 
    DbSet<T> Set<T>() where T : class; 

    DbEntityEntry<T> Entry<T>(T entity) where T : class; 

    int SaveChanges(); 

    void Dispose(); 
} 

public class MyDbContext : DbContext, IDbContext 
{ 
    public DbSet<Table1> Table1 { get; set; } 
    public DbSet<Table2> Table1 { get; set; } 
    public DbSet<Table3> Table1 { get; set; } 
    public DbSet<Table4> Table1 { get; set; } 
    public DbSet<Table5> Table1 { get; set; } 
    /* and so on */ 

    static MyDbContext() 
    { 
     Database.SetInitializer<MyDbContext>(new CreateDatabaseIfNotExists<MyDbContext>()); 
    } 

    public MyDbContext() 
     : base("MyDbContext") 
    { 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 

    } 
} 

Тогда у меня есть эта IRepository и реализация:

public interface IRepository<T> where T : class 
{ 
    IQueryable<T> GetAll(); 

    void Add(T entity); 

    void Delete(T entity); 

    void DeleteAll(IEnumerable<T> entity); 

    void Update(T entity); 

    bool Any(); 
} 

public class Repository<T> : IRepository<T> where T : class 
{ 
    private readonly IDbContext _context; 
    private readonly IDbSet<T> _dbset; 

    public Repository(IDbContext context) 
    { 
     _context = context; 
     _dbset = context.Set<T>(); 
    } 

    public virtual IQueryable<T> GetAll() 
    { 
     return _dbset; 
    } 

    public virtual void Add(T entity) 
    { 
     _dbset.Add(entity); 
    } 

    public virtual void Delete(T entity) 
    { 
     var entry = _context.Entry(entity); 
     entry.State = EntityState.Deleted; 
     _dbset.Remove(entity); 
    } 

    public virtual void DeleteAll(IEnumerable<T> entity) 
    { 
     foreach (var ent in entity) 
     { 
      var entry = _context.Entry(ent); 
      entry.State = EntityState.Deleted; 
      _dbset.Remove(ent); 
     } 
    } 

    public virtual void Update(T entity) 
    { 
     var entry = _context.Entry(entity); 
     _dbset.Attach(entity); 
     entry.State = EntityState.Modified; 
    } 

    public virtual bool Any() 
    { 
     return _dbset.Any(); 
    } 
} 

И в IUnitOfWor к и РЕАЛИЗАЦИЯ, который обрабатывает работу с DbContext

public interface IUnitOfWork : IDisposable 
{ 
    IRepository<TEntity> GetRepository<TEntity>() where TEntity : class; 

    void Save(); 
} 

public class UnitOfWork<TContext> : IUnitOfWork where TContext : IDbContext, new() 
{ 
    private readonly IDbContext _ctx; 
    private readonly Dictionary<Type, object> _repositories; 
    private bool _disposed; 

    public UnitOfWork() 
    { 
     _ctx = new TContext(); 
     _repositories = new Dictionary<Type, object>(); 
     _disposed = false; 
    } 

    public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class 
    { 
     // Checks if the Dictionary Key contains the Model class 
     if (_repositories.Keys.Contains(typeof(TEntity))) 
     { 
      // Return the repository for that Model class 
      return _repositories[typeof(TEntity)] as IRepository<TEntity>; 
     } 

     // If the repository for that Model class doesn't exist, create it 
     var repository = new Repository<TEntity>(_ctx); 

     // Add it to the dictionary 
     _repositories.Add(typeof(TEntity), repository); 

     return repository; 
    } 

    public void Save() 
    { 
     _ctx.SaveChanges(); 
    } 

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

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

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

     this._disposed = true; 
    } 
} 
+0

Существуют ли логические швы для разделения данных по нескольким dbcontexts? Если вы это сделаете, вы не сможете иметь свойства навигации или коллекции, которые пересекают контексты (например, вы не можете выполнять отношения EF между объектом в MainDbContext и другим объектом в UsersDbContext). – danludwig

+0

В чем причина разделения контекста? Также, если вы разделите их, вы не сможете использовать DI. Мне также не понравился ваш блок работы с репозиторием в нем (словарь вещь) –

+0

Я буду иметь несколько модулей в своем приложении и просто не загрязнять MyDbContext с большим количеством модулей DbSets. Может быть, я должен просто проигнорировать это? – janhartmann

ответ

7

Не разделяйте свои модульные части данных на несколько DbContext сек, если не являются логическими швы для этого. Объекты от DbContextA не могут иметь автоматическую навигацию или свойства коллекции с объектами в DbContextB. Если вы разделите контекст, ваш код должен будет нести ответственность за ручное принуждение ограничений и загрузку связанных данных между контекстами.

Для «обзора» (a.k.a.), вы все равно можете упорядочить свой код CLR и таблицы базы данных по модулю. Для POCO сохраните их в разных папках под разными пространствами имен. Для таблиц вы можете группировать по схеме. (Однако вы, вероятно, также должны учитывать соображения безопасности при организации по схеме SQL. Например, если есть какие-либо пользователи, которые должны иметь ограниченный доступ к определенным таблицам, проектируйте схемы в соответствии с этими правилами.) Затем вы можете сделать это при построении модели:

ToTable("TableName", "SchemaName"); // put table under SchemaName, not dbo 

только идти с отдельным DbContext, когда его субъекты не имеют отношения ни с какими лицами в первом DbContext.

Я также согласен с Wiktor в том, что мне не нравится ваш интерфейс & дизайн реализации. Мне особенно не нравится public interface IRepository<T>. Кроме того, зачем объявлять несколько public DbSet<TableN> TableN { get; set; } в вашем MyDbContext? Сделайте мне одолжение, прочитайте this article, затем прочитайте this one.

Вы можете значительно упростить код с дизайном EF интерфейса, как это:

interface IUnitOfWork 
{ 
    int SaveChanges(); 
} 
interface IQueryEntities 
{ 
    IQueryable<T> Query<T>(); // implementation returns Set<T>().AsNoTracking() 
    IQueryable<T> EagerLoad<T>(IQueryable<T> queryable, Expression<Func<T, object>> expression); // implementation returns queryable.Include(expression) 
} 
interface ICommandEntities : IQueryEntities, IUnitOfWork 
{ 
    T Find<T>(params object[] keyValues); 
    IQueryable<T> FindMany<T>(); // implementation returns Set<T>() without .AsNoTracking() 
    void Create<T>(T entity); // implementation changes Entry(entity).State 
    void Update<T>(T entity); // implementation changes Entry(entity).State 
    void Delete<T>(T entity); // implementation changes Entry(entity).State 
    void Reload<T>(T entity); // implementation invokes Entry(entity).Reload 
} 

Если вы объявляете MyDbContext : ICommandEntities, вы просто должны создать несколько методов для реализации интерфейса (как правило, один-лайнеры). Затем вы можете ввести любой из 3-х интерфейсов в свои сервисные реализации: обычно ICommandEntities для операций с побочными эффектами и IQueryEntities для операций, которые этого не делают. Любые сервисы (или декораторы сервисов), ответственные только за сохранение состояния, могут зависеть от IUnitOfWork. Я не согласен с тем, что Controller s должен зависеть от IUnitOfWork. Используя приведенный выше проект, ваши службы должны сохранять изменения перед возвратом в Controller.

Если у вас есть несколько отдельных классов DbContext в вашем приложении, это имеет смысл, вы можете do as Wiktor suggests и сделать вышеуказанные интерфейсы общим. Вы можете затем зависимость впрыснуть в сфере услуг, как так:

public SomeServiceClass(IQueryEntities<UserEntities> users, 
    ICommandEntities<EstateModuleEntities> estateModule) { ... } 

public SomeControllerClass(SomeServiceClass service) { ... } 

// Ninject will automatically constructor inject service instance into controller 
// you don't need to pass arguments to the service constructor from controller 

Создания широкого за агрегат (или даже хуже за лицо) интерфейсы хранилища могут бороться с EF, умножать расточный код слесарного, и над впрыснуть ваши конструктор. Вместо этого предоставляйте вашим услугам большую гибкость. Такие методы, как .Any(), не относятся к интерфейсу, вы можете просто назовите расширения на IQueryable<T>, возвращенные Query<T> или FindMany<T> из ваших методов обслуживания.

+0

Спасибо за это, с вашим выше кодом - как бы выглядел мой DbContext - должен ли он просто реализовать ICommandEntities? Я не уверен, получаю ли я это. Мой пример (первый пост) основан на http://gaui.is/how-to-mock-the-datacontext-entity-framework/ - Может быть, это не лучший метод. – janhartmann

+0

«лучший метод» спорный. Мне еще предстоит увидеть модель интерфейса, которая лучше, чем указано выше, по моему мнению, только на основе типов проектов, над которыми я работаю (от среднего до большого) *. Фактически вы реализуете все 3 интерфейса на 'DbContext', но поскольку' ICommandEntities' реализует оба других 2, вам нужно только указать его в подписи класса. – danludwig

+0

Было бы много спросить, как эти ICommandEntities используются на практике?Думаю, я не могу думать о том, что не использую класс репозитория – janhartmann

2

Ваш интерфейс работы не является общим, но реализация выполнена. Самый простой способ очистить это - это решить и следовать тому же соглашению.

Например, введите свой общий интерфейс. Таким образом, вы можете зарегистрировать три различные интерфейс (тот же интерфейс с тремя различными родовыми параметрами) для трех различных реализаций:

container.Bind(typeof<IUnitOfWork<ContextOne>>).To(typeof<UnitOfWork<ContextOne>>); 
... 

И да, это хорошая идея, чтобы ввести свой блок работ в контроллерах/услуги.

+0

Как бы вы реорганизовали IUnitOfWork, чтобы сделать его общим? Любое предложение? –