7

Tworzę swoją pierwszą aplikację MVC w N-Tier i natknąłem się na blokadę drogi, jak zarządzać wieloma DbContexts przy pierwszym podejściu do bazy danych.Wiele DbContexts w N-Tier Application

Mam następujące warstwy

Presentation 
Service (WCF) 
Business 
Data Access 

nie chcę odwołanie Entity Framework w moim warstwie usług, ale nie widzę w jaki sposób utworzyć interfejs lub coś do zarządzania dwóch kontekstach. Pracuję z jednym kontekstem wypaczonym w IDatabaseFactory, ale nie mogę znaleźć sposobu na zarządzanie dwoma.

Poniżej znajduje się moja , która jest tworzona w moim Service ctor, ale na każdy sposób, w jaki na nią patrzę, wciąż jestem związany z SiteModelContainer, kiedy w rzeczywistości mam inny kontekst.

public class UnitOfWork : IUnitOfWork 
    { 
     private SiteModelContainer _context; 

     private readonly IDatabaseFactory _databaseFactory; 

     protected SiteModelContainer SiteContext 
     { 
      get { return _context ?? (_context = _databaseFactory.Get()); } 
     } 

     public UnitOfWork(IDatabaseFactory factory) 
     { 
      _databaseFactory = factory; 
      _context = _databaseFactory.Get(); 
     } 
     //More code 
    } 



public class DatabaseFactory : Disposable, IDatabaseFactory 
{ 
    private SiteModelContainer _dataContext; 

    public SiteModelContainer Get() 
    { 
     return _dataContext ?? (_dataContext = new SiteModelContainer()); 
    } 

    protected override void DisposeCore() 
    { 
     if (_dataContext != null) 
      _dataContext.Dispose(); 
    } 
} 
+1

"Pracuję z jednym kontekstem wypaczonym w IDatabaseFactory" Czuję twój ból; dużo mojego kodu wydaje się być w czasie i/lub warp. –

Odpowiedz

15

Nadawanie fabryce i UnitOfWork ogólny parametr typu może być rozwiązanie:

public class UnitOfWork<T> : IUnitOfWork<T> 
    where T : DbContext, new() 
{ 
    private T _context; 

    private readonly IDatabaseFactory<T> _databaseFactory; 

    protected T Context 
    { 
     get { return _context ?? (_context = _databaseFactory.Get()); } 
    } 

    public UnitOfWork(IDatabaseFactory<T> factory) 
    { 
     _databaseFactory = factory; 
     _context = _databaseFactory.Get(); 
    } 
    //More code 
} 

public class DatabaseFactory<T> : Disposable, IDatabaseFactory<T> 
    where T : DbContext, new() 
{ 
    private T _dataContext; 

    public T Get() 
    { 
     return _dataContext ?? (_dataContext = new T()); 
    } 

    protected override void DisposeCore() 
    { 
     if (_dataContext != null) 
      _dataContext.Dispose(); 
    } 
} 

W IDatabaseFactory i IUnitWork interfejsy musiałby również być rodzajowy wtedy.

Następnie można tworzyć Jednostka pracuje dla różnych kontekstach:

var factory1 = new DatabaseFactory<SiteModelContainer>(); 
var unitOfWork1 = new UnitOfWork<SiteModelContainer>(factory1); 

var factory2 = new DatabaseFactory<AnotherModelContainer>(); 
var unitOfWork2 = new UnitOfWork<AnotherModelContainer>(factory2); 

Edit:

Aby pozbyć się uzależnienia od EF w klasach serwisowych można spróbować czegoś takiego. Usługa zna tylko te trzy interfejsy:

public interface IUnitOfWorkFactory 
{ 
    IUnitOfWork Create(string contextType); 
} 

public interface IUnitOfWork : IDisposable 
{ 
    IRepository<TEntity> CreateGenericRepository<TEntity>() 
     where TEntity : class; 
    void Commit(); 
} 

public interface IRepository<T> 
{ 
    IQueryable<T> Find(Expression<Func<T, bool>> predicate); 
    void Attach(T entity); 
    void Add(T entity); 
    // etc. 
} 

Oto specjalne implementacje EF specyficzne:

public class UnitOfWorkFactory : IUnitOfWorkFactory 
{ 
    public IUnitOfWork Create(string contextType) 
    { 
     switch (contextType) 
     { 
      case "SiteModelContainer": 
       return new UnitOfWork<SiteModelContainer>(); 
      case "AnotherModelContainer": 
       return new UnitOfWork<AnotherModelContainer>(); 
     } 

     throw new ArgumentException("Unknown contextType..."); 
    } 
} 

public class UnitOfWork<TContext> : IUnitOfWork 
    where TContext : DbContext, new() 
{ 
    private TContext _dbContext; 

    public UnitOfWork() 
    { 
     _dbContext = new TContext(); 
    } 

    public IRepository<TEntity> CreateGenericRepository<TEntity>() 
     where TEntity : class 
    { 
     return new Repository<TEntity>(_dbContext); 
    } 

    public void Commit() 
    { 
     _dbContext.SaveChanges(); 
    } 

    public void Dispose() 
    { 
     _dbContext.Dispose(); 
    } 
} 

public class Repository<T> : IRepository<T> 
    where T : class 
{ 
    private DbContext _dbContext; 
    private DbSet<T> _dbSet; 

    public Repository(DbContext dbContext) 
    { 
     _dbContext = dbContext; 
     _dbSet = dbContext.Set<T>(); 
    } 

    public IQueryable<T> Find(Expression<Func<T, bool>> predicate) 
    { 
     return _dbSet.Where(predicate); 
    } 

    public void Attach(T entity) 
    { 
     _dbSet.Attach(entity); 
    } 

    public void Add(T entity) 
    { 
     _dbSet.Add(entity); 
    } 

    // etc. 
} 

Usługa dostanie IUnitOfWorkFactory wstrzykiwany:

public class MyService 
{ 
    private IUnitOfWorkFactory _factory; 

    public MyService(IUnitOfWorkFactory factory) 
    { 
     _factory = factory; 
    } 

    public MyMethod() 
    { 
     using(var unitOfWork1 = _factory.Create("SiteModelContainer")) 
     { 
      var repo1 = unitOfWork1. 
       CreateGenericRepository<SomeEntityTypeInSiteModel>(); 
      // Do some work 
      unitOfWork1.Commit(); 
     } 

     using(var unitOfWork2 = _factory.Create("AnotherModelContainer")) 
     { 
      var repo2 = unitOfWork2. 
       CreateGenericRepository<SomeEntityTypeInAnotherModel>(); 
      // Do some work 
      unitOfWork2.Commit(); 
     } 
    } 
} 

Gdy usługa jest tworzony konkretna instancja fabryki jest wstrzykiwana:

var service = new MyService(new UnitOfWorkFactory()); 

Należy pamiętać, że ciężka praca znajdzie się w abstrakcyjnym repozytorium i jego implementacji. Gdy nie masz już kontekstu EF w swojej klasie usług, musisz naśladować wiele metod w interfejsie repo, obsługujących wszystkie niezbędne scenariusze manipulowania danymi.

+0

Naprawdę podoba mi się sposób, w jaki to wygląda, ale utworzenie w mojej usłudze nowego "UnitOfWork" lub "DatabaseFactory" w ten sposób nadal wymaga odniesienia do struktury encji, której próbuję uniknąć w celu rozdzielenia obaw.Nie chcę, aby moja usługa była zależna od EF, ale prawdopodobnie mogę zakończyć łamanie tej zasady. – MisterIsaak

+0

@Jisaak. Wypróbowałem propozycję usunięcia zależności usługi od EF. Myślę, że to możliwe, ale trochę zmieniłem twój projekt, szczególnie zastąpiłem 'DatabaseFactory' przez' UnitOfWorkFactory' (zarówno interfejs jak i implementację), zobacz moje Edycja ... – Slauma

0

Można utworzyć wrapper, który jest generycznym repozytorium dla DbContexts (i wykorzystuje do tego podstawowy obiekt ObjectContext).

Oto przykład, z którego korzystałem w przeszłości (co również rozdziela kod z bezpośrednią zależnością od Entity Framework).

// Make your DbContext inherit from this. This goes in your Unit of Work. 
public interface IEntitySetProvider : IDisposable 
{ 
    IEntitySet<T> CreateEntitySet<T>(); 
} 

// This is your adapted DBContext 
public class MyDbContext1 : DbContext, IEntitySetProvider 
{ 
    public IEntitySet<T> CreateEntitySet<T>() 
    { 
     return new EntitySet<T>(((IObjectContextAdapter)this).CreateObjectSet<T>()); 
    } 

    . 
    . 
    . 
} 


/// <summary> 
/// A wrapper for an IQueryable that exposes AddNew and Attach methods. 
/// </summary> 
/// <typeparam name = "T"></typeparam> 
public interface IEntitySet<T> : IQueryable<T> 
{ 
    /// <summary> 
    /// Attaches the specified value and considers it new. 
    /// </summary> 
    /// <param name = "value">The value.</param> 
    void AddNew(T value); 

    /// <summary> 
    /// Attaches the specified value and considers it modified. 
    /// </summary> 
    /// <param name = "value">The value.</param> 
    void Attach(T value); 
} 

/// <summary> 
/// An IEntitySet for Entity Framework. 
/// </summary> 
/// <typeparam name = "T"></typeparam> 
internal class EntitySet<T> : IEntitySet<T> where T : class 
{ 
    private readonly ObjectSet<T> _objectSet; 

    public EntitySet(ObjectSet<T> objectSet) 
    { 
     _objectSet = objectSet; 
    } 

    #region IEntitySet<T> Members 

    public void AddNew(T value) 
    { 
     _objectSet.AddObject(value); 
    } 

    public void Attach(T value) 
    { 
     _objectSet.Attach(value); 
     _objectSet.Context.ObjectStateManager.ChangeObjectState(value, EntityState.Modified); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return ((IQueryable<T>) _objectSet).GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return ((IQueryable) _objectSet).GetEnumerator(); 
    } 

    public Type ElementType 
    { 
     get { return ((IQueryable<T>) _objectSet).ElementType; } 
    } 

    public Expression Expression 
    { 
     get { return ((IQueryable<T>) _objectSet).Expression; } 
    } 

    public IQueryProvider Provider 
    { 
     get { return ((IQueryable<T>) _objectSet).Provider; } 
    } 

    #endregion 
}