Unité de travail + dépôt + couche de service avec l'injection de dépendance
Je suis en train de concevoir une application web et un service windows et que vous voulez utiliser à l'unité de travail + dépôt de la couche en conjonction avec une couche de service, et je vais avoir quelques difficultés à mettre tous ensemble pour que le client les applications de contrôle de la transaction de données avec l'unité de travail.
L'unité de travail est une collection de tous les référentiels inscrits dans la transaction avec la validation et les opérations de restauration
public interface IUnitOfWork : IDisposable
{
IRepository<T> Repository<T>() where T : class;
void Commit();
void Rollback();
}
Le générique référentiel a des opérations qui seront effectuées sur la couche de données pour un modèle en particulier (tableau)
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);
}
La mise en œuvre concrète de l'unité de travail utilise entity framework sous le capot (DbContext) pour enregistrer les modifications apportées à la base de données, et une nouvelle instance de la classe DbContext est créé par unité de travail
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;
}
Les référentiels dans l'unité de travail sont créés lors de l'accès si elles n'existent pas dans l'unité de travail de l'instance. Le référentiel prend la DbContext comme un paramètre de constructeur de sorte qu'il peut travailler efficacement dans le courant de l'unité de travail
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
J'ai également un service de classes qui encapsulent les flux de travail d'entreprise de la logique et de prendre leurs dépendances dans le constructeur.
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
L'application web est une ASP.NET MVC application, le contrôleur reçoit ses dépendances injecté
dans le constructeur. Dans ce cas, l'unité de service et de travail de la classe sont injectés. L'action effectue une opération exposées par le service, comme la création d'un enregistrement dans le dépôt et l'enregistrement d'un fichier à un serveur de fichiers à l'aide d'un DocumentStorageService, puis à l'unité de travail s'est engagé dans l'action du controller.
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);
}
Sur l'application web, je suis à l'aide de l'Unité DI conteneur d'injection de la même instance de l'unité de travail par requête http pour tous les appelants, de sorte que le contrôleur de classe devient une nouvelle instance, puis la classe de service qui utilise l'unité de travail est la même instance que le contrôleur. De cette façon, le service ajoute quelques enregistrements pour le dépôt qui s'est enrôlé dans une unité de travail et peuvent être commises par le client code du contrôleur.
Une question concernant le code et l'architecture décrite ci-dessus. Comment puis-je me débarrasser de l'unité de travail de dépendance au service des classes? Idéalement, je ne veux pas la classe de service d'avoir une instance de l'unité de travail parce que je ne veux pas le service pour valider la transaction, j'aimerais juste le service de référence au référentiel, il a besoin de travailler avec, et de laisser le contrôleur (code client) s'engager à l'opération quand il voir s'adapte.
Sur l'application du service windows, je voudrais être en mesure d'obtenir un ensemble de documents, avec une seule unité de travail, dire que tous les dossiers en attente de statut. Ensuite je voudrais faire une boucle par tous les dossiers de requête et de la base de données pour obtenir de chacun d'eux individuellement et ensuite vérifier l'état de chacun à chaque tour de boucle parce que l'état a peut-être changé depuis le temps que j'ai interrogé tout à l'heure je veux faire fonctionner sur un seul. Le problème que j'ai maintenant c'est que mon architecture actuelle ne me permet pas d'avoir plusieurs unités d'œuvres pour la même instance du service.
public class ProcessPortfolioRequestsJob : JobBase
{
IPortfolioRequestService _portfolioRequestService;
public ProcessPortfolioRequestsJob(IPortfolioRequestService portfolioRequestService)
{
_portfolioRequestService = portfolioRequestService;
}
La classe d'Emploi ci-dessus prend un service dans le constructeur comme une dépendance et, de nouveau, est résolu par l'Unité. L'instance de service qui est résolu et injecté dépend de l'unité de travail. Je voudrais effectuer deux opérations get sur la classe de service, mais parce que je suis d'exploitation en vertu de la même instance de l'unité de travail, je ne peux pas l'atteindre.
Pour vous tous les gourous de là-bas, avez-vous des suggestions sur comment je peux re-architecte de ma demande, de l'unité de travail + dépôt + les classes de service pour atteindre les objectifs ci-dessus?
J'ai l'intention d'utiliser l'unité de travail + référentiel de modèles pour permettre la testabilité sur mes classes de service, mais je suis ouvert à d'autres modèles de conception qui permettra de rendre mon code plus facile à maintenir et testable en même temps tout en maintenant la séparation des préoccupations.
Mise à jour de 1
Ajout de la classe DataContext que inheris de EF est DbContext où j'ai déclaré mon EF DbSets et des configurations.
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);
}
}
OriginalL'auteur Guillermo Gomez | 2014-09-20
Vous devez vous connecter pour publier un commentaire.
Si votre objectif pour l'utilisation de l'Unité de Travail (UoW) a été pour la testabilité, vous avez pris le mauvais chemin. Unité de travail ne fait rien pour la testabilité. Son objectif principal est de fournir des transactions atomiques à des sources de données disparates, fournir UoW les fonctionnalités d'une couche de données qui ne sont pas déjà offrent, ou l'envelopper d'un existant UoW dans une manière qui rend plus facile à remplacer... quelque chose qui vous avez annulé en utilisant le générique de dépôt (ce qui hermétiquement les couples à Entity Framework de toute façon).
Je vous suggère de se débarrasser de l'Unité de Travail complètement. Entity Framework est déjà une UoW. Même Microsoft a changé leur esprit et ne plus recommander UoW avec EF.
Donc, si vous vous débarrasser de UoW, vous pouvez simplement utiliser les référentiels pour envelopper votre EF requêtes. Je vous suggère de ne pas utiliser un générique référentiel, que cette fuite de données de la couche de mise en œuvre partout dans votre code (quelque chose de votre UoW le faisait déjà), mais plutôt de créer Béton repoTsitories (que l'on peut utiliser des référentiels génériques en interne si vous le souhaitez, mais le générique de référentiel ne doivent pas fuir à l'extérieur de votre dépôt).
Cela signifie que votre couche de service prend le concrètes référentiel dont il a besoin. Par exemple,
IPortfolioRepository
. Ensuite, vous avez unePortfolioRepository
classe qui hérite de IPortfolioRepository qui prend votre EFDbContext
comme un paramètre qui obtient injecté par votre Depndency Injection (DI). Si vous configurez votre conteneur d'injection de dépendances à l'exemple de votre contexte EF sur un "PerRequest", puis vous pouvez passer à la même instance pour tous vos dépôts. Vous pouvez avoir unCommit
méthode sur votre référentiel qui appelleSavesChanges
, mais il va enregistrer les modifications apportées à tous les changements, et pas seulement à ce référentiel.Autant que la Testabilité va, vous avez deux choix. Vous pouvez simuler le béton référentiels, ou vous pouvez utiliser le microphone en se moquant des capacités de EF6.
Oui, c'est un peu une chasse aux sorcières, l'alternative est de créer une nouvelle instance pour chaque référentiel, mais alors vous devez faire attention à ne pas mélanger proxy objets entre eux. Une unité de travail peut être un désordre proposition si vous ne faites pas attention, peu importe comment la mettre en œuvre. L'abstraction de ça devient malpropre.
OriginalL'auteur Erik Funkenbusch
J'ai été à travers ce trou moi et voici ce que Je l'ai fait:
Fossé de la UoW complètement. EF est DBContext est une UoW essentiellement. Inutile de re-inventer la roue.
Par MSDN:
De la couche de Service + Repo couche semblé être un bon choix. Toutefois, les pensions de titres sont toujours une abstraction qui fuit et en particulier lorsque DBContext de
DbSet
sont l'équivalent des dépôts.Si vous demandez à mes 2 cents, je dirais aller avec la couche de service + EF, un habillage d'affaires de la logique, de l'autre un emballage UOW/modèle de Référentiel.
Sinon, et pour les Services Windows en particulier, je trouve que le passage à un commande de requête de base de approche qui fonctionne le mieux.
Non seulement il aide à la testabilité, il contribue également à des tâches asynchrones où je n'ai pas à vous soucier de garder le DBContext en vie, même après que la demande a été fini (DBContext est maintenant à égalité avec le gestionnaire de commande et reste en vie aussi longtemps que l'async commande reste en vie).
Maintenant, si vous avez récemment terminé de digérer tous ces faits sur UOW/modèle de Référentiel, alors sûrement, tout de même lire sur Commande-modèle de Requête de rendre votre esprit blessé. J'ai été en bas de ce chemin, mais croyez-moi, sa vaut le temps à au moins le regarder et de lui donner un essai.
Ces postes peuvent vous aider:
Si vous êtes assez courageux (après digestion thru CQRS), puis prendre un coup d'oeil à MediatR qui met en œuvre le Médiateur modèle (qui, en gros, enveloppements de commande de requête avec notifications) et permet de travailler via pub-sub. Le pub-sub modèle convient parfaitement dans le Service Windows et les services de la couche.
Tout à fait d'accord avec vous @ErikFunkenbusch.
OriginalL'auteur Mrchief