Einheit von Arbeit + repository + service layer mit dependency injection
Ich bin der Gestaltung einer web-Anwendung und windows-Dienst nutzen möchten und die Einheit von Arbeit + repository-Schicht in Verbindung mit einer service-Schicht, und ich bin mit einigen Schwierigkeiten setzen Sie alle zusammen, so dass die client-apps Steuern, die Transaktion von Daten, die mit dem Gerät arbeiten.
Die Einheit der Arbeit ist eine Sammlung aller repositories eingeschrieben in die Transaktion zusammen mit commit-und rollback-Operationen
public interface IUnitOfWork : IDisposable
{
IRepository<T> Repository<T>() where T : class;
void Commit();
void Rollback();
}
Den generischen repository-Operationen, die durchgeführt werden, auf dem Daten-layer für ein bestimmtes Modell (Tabelle)
public interface IRepository<T> where T : class
{
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, IList<ISortCriteria<T>> sortCriterias = null);
PaginatedList<T> GetPaged(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, PagingOptions<T> pagingOptions = null);
T Find(Expression<Func<T, bool>> filter, IList<Expression<Func<T, object>>> includedProperties = null);
void Add(T t);
void Remove(T t);
void Remove(Expression<Func<T, bool>> filter);
}
Die konkrete Umsetzung der Einheit von Arbeit verwendet entity framework unter der Haube (DbContext) zum speichern der änderungen an der Datenbank, und eine neue Instanz der DbContext-Klasse erstellt wird pro Einheit der Arbeit
public class UnitOfWork : IUnitOfWork
{
private IDictionary<Type, object> _repositories;
private DataContext _dbContext;
private bool _disposed;
public UnitOfWork()
{
_repositories = new Dictionary<Type, object>();
_dbContext = new DataContext();
_disposed = false;
}
Den repositories in der Einheit von arbeiten erstellt werden, nach Zugang, wenn Sie nicht existiert in der aktuellen Arbeitseinheit Instanz. Das repository übernimmt die DbContext als Konstruktor-parameter, damit es effektiv arbeiten können in der aktuellen Arbeitseinheit
public class Repository<T> : IRepository<T> where T : class
{
private readonly DataContext _dbContext;
private readonly DbSet<T> _dbSet;
#region Ctor
public Repository(DataContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
#endregion
Ich habe auch ein service-Klassen Kapseln-business-workflow-Logik und Ihre Abhängigkeiten im Konstruktor.
public class PortfolioRequestService : IPortfolioRequestService
{
private IUnitOfWork _unitOfWork;
private IPortfolioRequestFileParser _fileParser;
private IConfigurationService _configurationService;
private IDocumentStorageService _documentStorageService;
#region Private Constants
private const string PORTFOLIO_REQUEST_VALID_FILE_TYPES = "PortfolioRequestValidFileTypes";
#endregion
#region Ctors
public PortfolioRequestService(IUnitOfWork unitOfWork, IPortfolioRequestFileParser fileParser, IConfigurationService configurationService, IDocumentStorageService documentStorageService)
{
if (unitOfWork == null)
{
throw new ArgumentNullException("unitOfWork");
}
if (fileParser == null)
{
throw new ArgumentNullException("fileParser");
}
if (configurationService == null)
{
throw new ArgumentNullException("configurationService");
}
if (documentStorageService == null)
{
throw new ArgumentNullException("configurationService");
}
_unitOfWork = unitOfWork;
_fileParser = fileParser;
_configurationService = configurationService;
_documentStorageService = documentStorageService;
}
#endregion
Die web-Anwendung ist eine ASP.NET MVC-app, der controller bekommt seine Abhängigkeiten injiziert
im Konstruktor als gut. In diesem Fall ist die Einheit der Arbeit und der service Klasse injiziert werden. Die Aktion führt eine operation durch den Dienst verfügbar gemacht, wie das erstellen eines Datensatzes in der repository und der Speicherung einer Datei auf einem Datei-server mit einem DocumentStorageService, und dann die Einheit der Arbeit ist verpflichtet, bei der controller-Aktion.
public class PortfolioRequestCollectionController : BaseController
{
IUnitOfWork _unitOfWork;
IPortfolioRequestService _portfolioRequestService;
IUserService _userService;
#region Ctors
public PortfolioRequestCollectionController(IUnitOfWork unitOfWork, IPortfolioRequestService portfolioRequestService, IUserService userService)
{
_unitOfWork = unitOfWork;
_portfolioRequestService = portfolioRequestService;
_userService = userService;
}
#endregion
[HttpPost]
[ValidateAntiForgeryToken]
[HasPermissionAttribute(PermissionId.ManagePortfolioRequest)]
public ActionResult Create(CreateViewModel viewModel)
{
if (ModelState.IsValid)
{
//validate file exists
if (viewModel.File != null && viewModel.File.ContentLength > 0)
{
//TODO: ggomez - also add to CreatePortfolioRequestCollection method
//see if file upload input control can be restricted to excel and csv
//add additional info below control
if (_portfolioRequestService.ValidatePortfolioRequestFileType(viewModel.File.FileName))
{
try
{
//create new PortfolioRequestCollection instance
_portfolioRequestService.CreatePortfolioRequestCollection(viewModel.File.FileName, viewModel.File.InputStream, viewModel.ReasonId, PortfolioRequestCollectionSourceId.InternalWebsiteUpload, viewModel.ReviewAllRequestsBeforeRelease, _userService.GetUserName());
_unitOfWork.Commit();
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
return View(viewModel);
}
return RedirectToAction("Index", null, null, "The portfolio construction request was successfully submitted!", null);
}
else
{
ModelState.AddModelError("File", "Only Excel and CSV formats are allowed");
}
}
else
{
ModelState.AddModelError("File", "A file with portfolio construction requests is required");
}
}
IEnumerable<PortfolioRequestCollectionReason> portfolioRequestCollectionReasons = _unitOfWork.Repository<PortfolioRequestCollectionReason>().Get();
viewModel.Init(portfolioRequestCollectionReasons);
return View(viewModel);
}
Auf die web-Anwendung bin ich mit Unity DI container injizieren die gleiche Instanz der Einheit der Arbeit, die pro http-Anforderung an alle Anrufer, so dass die controller-Klasse wird eine neue Instanz und dann die service-Klasse verwendet, die die Einheit der Arbeit wird die gleiche Instanz wie der controller. Diese Weise der Dienst fügt einige Einträge in das repository, die ist eingeschrieben in eine Einheit der Arbeit und kann begangen werden durch den client-code in den controller.
Eine Frage zu den code-und Architektur-wie oben beschrieben. Wie kann ich loswerden der Arbeitseinheit in Abhängigkeit der service-Klassen? Im Idealfall möchte ich nicht in der service-Klasse eine Instanz der Einheit der Arbeit, weil ich nicht wollen, dass die service-commit für die Transaktion aus, ich möchte nur der service eine Referenz auf das repository, die es braucht, um mit zu arbeiten, und lassen Sie den controller (mit dem client-code) verpflichtet den Betrieb, wenn Sie es sehen, passt.
Auf der windows-Dienst-Anwendung, ich möchte in der Lage sein, um eine Reihe von Datensätzen mit einer einzigen Einheit der Arbeit, sagen alle Datensätze mit dem status ausstehend. Dann würde ich gerne eine Schleife durch alle Datensätze und die Abfrage der Datenbank zu bekommen, jedes einzeln, und überprüfen Sie dann den status für jeden eine während jeder Schleife, denn der status kann sich geändert haben von der Zeit, die ich abgefragt all der Zeit die ich bedienen möchte, auf ein einziges. Ich habe das problem jetzt ist, dass meine aktuellen Architektur nicht erlauben Sie mir mehrere die Einheit von arbeiten, für die gleiche Instanz des service.
public class ProcessPortfolioRequestsJob : JobBase
{
IPortfolioRequestService _portfolioRequestService;
public ProcessPortfolioRequestsJob(IPortfolioRequestService portfolioRequestService)
{
_portfolioRequestService = portfolioRequestService;
}
Die Job-Klasse, über die ein Dienst benötigt, die im Konstruktor als Abhängigkeit-und wieder wird gelöst, indem die Einheit. Die service-Instanz wird aufgelöst und injiziert werden, hängt von einer Einheit der Arbeit. Ich möchte Sie zwei get-Operationen auf der service-Klasse, aber weil ich die bin, die unter der gleichen Instanz der Einheit der Arbeit, ich kann nicht erreichen, dass.
Für alle von Euch gurus da draußen, haben Sie irgendwelche Vorschläge auf, wie ich kann zu überarbeiten meinen Antrag, unit of work + repository + service-Klassen zum erreichen der genannten Ziele?
Ich soll mit der Einheit von Arbeit + repository-Muster zu ermöglichen, Testbarkeit auf meine service-Klassen, aber ich bin offen für andere design-patterns, wird mein code wartbar und testbar, zur gleichen Zeit, während die Trennung von Bedenken.
Update 1
Hinzufügen DataContext-Klasse, inheris von EF-s DbContext, wo ich erklärte mein EF DbSets und Konfigurationen.
public class DataContext : DbContext
{
public DataContext()
: base("name=ArchSample")
{
Database.SetInitializer<DataContext>(new MigrateDatabaseToLatestVersion<DataContext, Configuration>());
base.Configuration.ProxyCreationEnabled = false;
}
public DbSet<PortfolioRequestCollection> PortfolioRequestCollections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new PortfolioRequestCollectionConfiguration());
base.OnModelCreating(modelBuilder);
}
}
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wenn Ihr Zweck für die Verwendung der Unit of Work (UoW) wurde für die Testbarkeit, Sie nahm den falschen Weg. Einheit von Arbeit ist nichts für Testbarkeit. Sein Hauptzweck ist die Bereitstellung von atomaren Transaktionen, die auf unterschiedlichen Datenquellen, bieten UoW-Funktionalität, um einen Daten-layer, die nicht bereits bieten, oder wickeln Sie ein vorhandenes UoW in einer Weise, die macht es leicht austauschbar... etwas, das Sie haben, zunichte gemacht werden, durch Verwendung der generischen repository (dieses fest Paare, die es zu Entity Framework sowieso).
Ich schlage vor, Sie loszuwerden, die Einheit der Arbeit vollständig. Entity Framework ist bereits eine Arbeitseinheit. Sogar Microsoft hat sich verändert, Ihre Meinung und nicht mehr empfehlen UoW mit EF.
Also, wenn Sie loswerden der UoW, dann können Sie nur nutzen repositories zu wickeln Sie Ihre EF-Abfragen. Ich weiß nicht empfehlen die Verwendung eines generischen repository, da dies Leckagen, Ihre Daten layer-Implementierung alle über Ihren code (etwas, das Ihr UoW schon dabei war), sondern erstellen Sie Konkrete repoTsitories (diese können generische repositories intern wenn Sie möchten, aber die generischen repository sollte nicht Leck außerhalb des Repositorys).
Bedeutet dies, dass Ihre service-Schicht nimmt die konkreten repository, die es braucht. Zum Beispiel
IPortfolioRepository
. Dann haben Sie einePortfolioRepository
Klasse, die erbt von IPortfolioRepository nimmt Ihre EFDbContext
als parameter wird injiziert durch Ihre Depndency Injection (DI) Frameworks. Wenn Sie die Konfiguration Ihres DI-container zum Beispiel EF-Kontext auf eine "PerRequest" - basis, dann kann man an der selben Instanz alle Ihre repositories. Sie können eineCommit
Methode auf das repository zu, dass AnrufeSavesChanges
, aber es wird änderungen speichern, um alle änderungen, nicht nur an das repository.Soweit Testbarkeit geht, haben Sie zwei Möglichkeiten. Sie können entweder mock die konkreten repositories, oder Sie kann benutzen Sie der built-in-Spott-Fähigkeiten der EF6.
Habe ich durch, der Teufel hole mich, und hier ist, was Ich habe fertig:
Graben die UoW komplett. EF-s DBContext ist eine Arbeitseinheit im Grunde. Keinen Sinn, das Rad neu erfinden möchte.
Pro MSDN:
Service-layer + Repo-Schicht schien wie eine gute Wahl. Aber repos sind immer eine undichte Abstraktion und espcially, wenn DBContext ist
DbSet
sind vergleichbar mit den repositories.Wenn Sie Fragen, meine 2 Cent, würde ich sagen, gehen mit der service-layer + EF, eine Umhüllung business-Logik, der andere das einwickeln UOW/Repository pattern.
Alternativ, und für Windows-Dienste vor allem, ich finde, dass der übergang zu einer Befehl-Abfrage-basierte Ansatz besser funktioniert.
Nicht nur hilft es, Testbarkeit, es hilft auch bei der asynchronen Aufgaben, wo ich nicht haben, um sorgen über das halten der DBContext lebendig, auch nach der Anfrage beendet hat (DBContext ist jetzt punktegleich mit der command-handler und bleibt lebendig, solange Sie die async-Befehl aktiv bleibt).
Nun, wenn Sie haben vor kurzem endete die Verdauung all den Fakten über die UOW/Repository pattern, dann sicherlich, gerade auch die Lektüre über das Befehl-Abfrage-Muster machen Ihren Geist verletzt. Ich habe auf diesem Weg aber Vertrauen Sie mir die Zeit Wert, um zumindest schauen zu und geben es zu versuchen.
Diese Beiträge können helfen:
Wenn du mutig genug bist (nach Verdauung thru CQRS), dann werfen Sie einen Blick auf MediatR die Implementierung des Vermittler-Muster (die im Grunde wraps up-Befehl-Abfrage-mit-Benachrichtigungen) und ermöglicht das arbeiten über pub-sub. Das pub-sub-Modell passt sehr schön in den Windows-Diensten und-services-Schicht.