Meilleur modèle de dépôt pour ASP.NET MVC
j'ai appris récemment ASP.NET MVC (I love it). Je travaille avec une entreprise qui utilise l'injection de dépendances pour charger une instance de dépôt dans chaque requête, et je suis familier avec l'utilisation de ce dépôt.
mais maintenant j'écris quelques applications MVC de ma propre. Je ne comprends pas très bien comment et pourquoi mon entreprise utilise le référentiel, et j'essaie de décider de la meilleure approche pour mettre en œuvre l'accès aux données.
j'utilise C# et Entity Cadre (avec toutes les dernières versions).
je vois trois approches générales pour le traitement de l'accès aux données.
-
contexte DB régulier dans une déclaration d'utilisation chaque fois que j'accède à des données. C'est simple et ça fonctionne bien. Toutefois, si deux endroits doivent lire les mêmes données à l'intérieur d'une même demande, les données doivent être lues deux fois. (Avec un seul dépôt par requête, la même instance serait utilisée aux deux endroits et je comprends la seconde read renverrait simplement les données de la première lecture.)
-
typique modèle de référentiel . Pour des raisons que je ne comprends pas, ce modèle typique implique la création d'une classe de wrapper pour chaque table utilisée à partir de la base de données. Qui semble mauvais pour moi. En fait, puisqu'ils sont implémentés aussi comme interfaces, je créerais techniquement deux classes d'enrubannage pour chaque table. EF crée des tables pour moi. Je ne crois pas que cette approche du sens.
-
il y a aussi un schéma générique de dépôt où une classe de dépôt unique est créée pour servir tous les objets entity. Cela fait beaucoup plus de sens pour moi. Mais est-il judicieux d'autres? Le lien au-dessus de la meilleure approche?
j'aimerais obtenir quelques commentaires d'autres personnes sur ce sujet. Est-ce que vous écrivez votre propre dépôt, en utilisant l'un de ceux ci-dessus, ou en faisant quelque chose de différent. S'il vous plaît partager.
4 réponses
j'ai utilisé un mélange de #2 et #3, mais je préfère un dépôt Générique strict si possible (plus strict que même suggéré dans le lien pour #3). #1 n'est pas bon parce qu'il joue mal avec les tests unitaires.
si vous avez un domaine plus petit ou si vous avez besoin de restreindre les entités que votre domaine permet d'être interrogé, je suppose que #2 - ou #3 qui définit les interfaces de dépôt spécifiques à l'entité qui mettent en œuvre elles - mêmes un dépôt générique-est logique. Cependant, je trouve que c' être épuisant et inutile d'écrire une interface et une implémentation concrète pour chaque entité, je veux interroger. À quoi sert public interface IFooRepository : IRepository<Foo>
(encore une fois, à moins que je doive contraindre les développeurs à un ensemble de racines agrégées autorisées)?
je viens de définir mon interface de dépôt générique, avec Add
, Remove
, Get
, GetDeferred
, Count
, et Find
méthodes (trouver renvoie une interface IQueryable
permettant LINQ), créer un générique concret la mise en œuvre, et l'appeler un jour. Je m'appuie fortement sur Find
et donc LINQ. Si j'ai besoin d'utiliser une requête spécifique plus d'une fois, j'utilise des méthodes d'extension et j'écris la requête en utilisant LINQ.
cela couvre 95% de mes besoins de persistance. Si j'ai besoin d'effectuer une sorte d'action de persistance qui ne peut pas être faite de façon générique, j'utilise une API ICommand
maison. Par exemple, disons que je travaille avec NHibernate et que je dois effectuer une requête complexe dans mon domaine, ou peut-être J'ai besoin de faire une commande en vrac. L'API ressemble à peu près à ceci:
// marker interface, mainly used as a generic constraint
public interface ICommand
{
}
// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
void Execute();
}
// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
TResult Execute();
}
// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
int Count();
}
// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
TCommand Create<TCommand>() where TCommand : ICommand;
}
Maintenant, je peux créer une interface pour représenter une commande spécifique.
public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
Decimal MinimumBalance { get; set; }
}
je peux créer une implémentation concrète et utiliser du SQL brut, du HQL NHibernate, peu importe, et l'enregistrer avec mon Localisateur de service.
maintenant dans ma logique d'affaires je peux faire quelque chose comme ceci:
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
vous pouvez également utiliser une spécification pattern Avec IQuery
pour construire des requêtes significatives, déterminées par l'utilisateur, plutôt que d'avoir une interface avec des millions de propriétés confuses, mais cela suppose que vous ne trouvez pas le pattern de spécification confus dans son propre droit ;).
une dernière pièce du puzzle est quand votre dépôt a besoin de faire des opérations pré - et post-dépôt spécifiques. Maintenant, vous pouvez très facilement créer une implémentation de votre référentiel générique pour une entité spécifique, puis méthode (s) et faire ce que vous avez à faire, et mettre à jour votre inscription au CIO ou au Localisateur de service et en faire l'objet.
cependant, cette logique est parfois transversale et difficile à mettre en œuvre en supplantant une méthode de dépôt. Donc j'ai créé IRepositoryBehavior
, qui est essentiellement un évier d'événement. (Ci-dessous est juste une définition approximative sur le dessus de ma tête)
public interface IRepositoryBehavior
{
void OnAdding(CancellableBehaviorContext context);
void OnAdd(BehaviorContext context);
void OnGetting(CancellableBehaviorContext context);
void OnGet(BehaviorContext context);
void OnRemoving(CancellableBehaviorContext context);
void OnRemove(BehaviorContext context);
void OnFinding(CancellableBehaviorContext context);
void OnFind(BehaviorContext context);
bool AppliesToEntityType(Type entityType);
}
Maintenant, ces comportements peuvent être n'importe quoi. Audit, contrôle de sécurité, soft-supprimer, application des contraintes de domaine, validation, etc. Je crée un comportement, l'enregistre avec le CIO ou le Localisateur de service, et modifie mon dépôt générique pour prendre en charge une collection de IRepositoryBehavior
enregistrés, et vérifie chaque comportement par rapport au type de dépôt courant et enveloppe l'opération dans les gestionnaires pré/post pour chaque comportement applicable.
voici un exemple de comportement soft-delete (soft-delete signifie que lorsque quelqu'un demande de supprimer une entité, nous l'indiquons simplement comme supprimé il ne peut donc pas être retourné à nouveau, mais il n'est jamais physiquement enlevé).
public SoftDeleteBehavior : IRepositoryBehavior
{
// omitted
public bool AppliesToEntityType(Type entityType)
{
// check to see if type supports soft deleting
return true;
}
public void OnRemoving(CancellableBehaviorContext context)
{
var entity = context.Entity as ISoftDeletable;
entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated
context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
}
}
oui, il s'agit essentiellement d'une implémentation simplifiée et abstraite des écouteurs d'événements de NHibernate, mais c'est pourquoi je l'aime bien. A) je peux tester un comportement en unité sans amener NHibernate dans l'image B) je peux utiliser ces comportements en dehors de NHibernate (disons que le dépôt est une implémentation client qui enveloppe les appels de service de repos) C) Les écouteurs d'événements de NH peuvent être une vraie douleur dans le cul ;)
je recommande le numéro 1, avec quelques mises en garde. Le numéro 2 est ce qui semble être le plus commun, mais d'après mon expérience, le dépôt finit juste par être un dépotoir pour les requêtes. Si vous utilisez un dépôt Générique (2), il ne s'agit que d'une couche mince autour du DBContext, un peu inutile en réalité à moins que vous ne planifiez de changer les ORMS (mauvaise idée).
mais quand J'accède directement à DBContext je préfère utiliser un modèle de tuyaux et de filtres afin que vous puissiez réutiliser la logique commune, quelque chose comme
items = DBContext.Clients
.ByPhoneNumber('1234%')
.ByOrganisation(134);
le Biphonenumber et par Organisation ne sont que des méthodes d'extension.
ici, nous allons pour le meilleur modèle de dépôt en Asp.Net MVC:
le motif du dépôt ajoute une couche de séparation entre les couches de données et de domaine d'une application. Il rend également les parties d'accès aux données d'une application plus testable.
Base De Données D'Usine (IDatabaseFactory.cs):
public interface IDatabaseFactory : IDisposable
{
Database_DBEntities Get();
}
Base De Données De Usine De Implémentations (DatabaseFactory.cs):
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private Database_DBEntities dataContext;
public Database_DBEntities Get()
{
return dataContext ?? (dataContext = new Database_DBEntities());
}
protected override void DisposeCore()
{
if (dataContext != null)
dataContext.Dispose();
}
}
Interface De Base (IRepository.cs):
public interface IRepository<T> where T : class
{
void Add(T entity);
void Update(T entity);
void Detach(T entity);
void Delete(T entity);
T GetById(long Id);
T GetById(string Id);
T Get(Expression<Func<T, bool>> where);
IEnumerable<T> GetAll();
IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
void Commit();
}
Classe Abstraite (Référentiel.cs):
public abstract class Repository<T> : IRepository<T> where T : class
{
private Database_DBEntities dataContext;
private readonly IDbSet<T> dbset;
protected Repository(IDatabaseFactory databaseFactory)
{
DatabaseFactory = databaseFactory;
dbset = DataContext.Set<T>();
}
/// <summary>
/// Property for the databasefactory instance
/// </summary>
protected IDatabaseFactory DatabaseFactory
{
get;
private set;
}
/// <summary>
/// Property for the datacontext instance
/// </summary>
protected Database_DBEntities DataContext
{
get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
}
/// <summary>
/// For adding entity
/// </summary>
/// <param name="entity"></param>
public virtual void Add(T entity)
{
try
{
dbset.Add(entity);
// dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Added;
int iresult = dataContext.SaveChanges();
}
catch (UpdateException ex)
{
}
catch (DbUpdateException ex) //DbContext
{
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// For updating entity
/// </summary>
/// <param name="entity"></param>
public virtual void Update(T entity)
{
try
{
// dbset.Attach(entity);
dbset.Add(entity);
dataContext.Entry(entity).State = EntityState.Modified;
int iresult = dataContext.SaveChanges();
}
catch (UpdateException ex)
{
throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
}
catch (DbUpdateException ex) //DbContext
{
throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex);
}
catch (Exception ex) {
throw ex;
}
}
/// <summary>
/// for deleting entity with class
/// </summary>
/// <param name="entity"></param>
public virtual void Delete(T entity)
{
dbset.Remove(entity);
int iresult = dataContext.SaveChanges();
}
//To commit save changes
public void Commit()
{
//still needs modification accordingly
DataContext.SaveChanges();
}
/// <summary>
/// Fetches values as per the int64 id value
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetById(long id)
{
return dbset.Find(id);
}
/// <summary>
/// Fetches values as per the string id input
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetById(string id)
{
return dbset.Find(id);
}
/// <summary>
/// fetches all the records
/// </summary>
/// <returns></returns>
public virtual IEnumerable<T> GetAll()
{
return dbset.AsNoTracking().ToList();
}
/// <summary>
/// Fetches records as per the predicate condition
/// </summary>
/// <param name="where"></param>
/// <returns></returns>
public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
return dbset.Where(where).ToList();
}
/// <summary>
///
/// </summary>
/// <param name="entity"></param>
public void Detach(T entity)
{
dataContext.Entry(entity).State = EntityState.Detached;
}
/// <summary>
/// fetches single records as per the predicate condition
/// </summary>
/// <param name="where"></param>
/// <returns></returns>
public T Get(Expression<Func<T, bool>> where)
{
return dbset.Where(where).FirstOrDefault<T>();
}
}
comment accéder à ce motif de dépôt dans le contrôleur:
1. Vous avez le modèle D'utilisateur:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
}
2. Maintenant, vous devez créer la classe de dépôt de votre UserModel
public class UserRepository : Repository<User>, IUserRepository
{
private Database_DBEntities dataContext;
protected IDatabaseFactory DatabaseFactory
{
get;
private set;
}
public UserRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{
DatabaseFactory = databaseFactory;
}
protected Database_DBEntities DataContext
{
get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
}
public interface IUserRepository : IRepository<User>
{
}
}
3. Maintenant, vous devez créer L'Interface UserService (IUserService.cs) avec toutes les méthodes CRUD:
public interface IUserService
{
#region User Details
List<User> GetAllUsers();
int SaveUserDetails(User Usermodel);
int UpdateUserDetails(User Usermodel);
int DeleteUserDetails(int Id);
#endregion
}
4. Maintenant, vous devez créer L'Interface UserService (UserService.cs) avec toutes les méthodes CRUD:
public class UserService : IUserService
{
IUserRepository _userRepository;
public UserService() { }
public UserService(IUserRepository userRepository)
{
this._userRepository = userRepository;
}
public List<User> GetAllUsers()
{
try
{
IEnumerable<User> liUser = _userRepository.GetAll();
return liUser.ToList();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// Saves the User details.
/// </summary>
/// <param name="User">The deptmodel.</param>
/// <returns></returns>
public int SaveUserDetails(User Usermodel)
{
try
{
if (Usermodel != null)
{
_userRepository.Add(Usermodel);
return 1;
}
else
return 0;
}
catch
{
throw;
}
}
/// <summary>
/// Updates the User details.
/// </summary>
/// <param name="User">The deptmodel.</param>
/// <returns></returns>
public int UpdateUserDetails(User Usermodel)
{
try
{
if (Usermodel != null)
{
_userRepository.Update(Usermodel);
return 1;
}
else
return 0;
}
catch
{
throw;
}
}
/// <summary>
/// Deletes the User details.
/// </summary>
/// <param name="Id">The code identifier.</param>
/// <returns></returns>
public int DeleteUserDetails(int Id)
{
try
{
User Usermodel = _userRepository.GetById(Id);
if (Usermodel != null)
{
_userRepository.Delete(Usermodel);
return 1;
}
else
return 0;
}
catch
{
throw;
}
}
}
5. Maintenant, vous Tous ensemble pour votre motif de dépôt et vous pouvez accéder à toutes les données dans le contrôleur D'utilisateur:
//Here is the User Controller
public class UserProfileController : Controller
{
IUserService _userservice;
public CustomerProfileController(IUserService userservice)
{
this._userservice = userservice;
}
[HttpPost]
public ActionResult GetAllUsers(int id)
{
User objUser=new User();
objUser = _userservice.GetAllUsers().Where(x => x.Id == id).FirstOrDefault();
}
}
il y a une solution prête à l'emploi à URF - Unité de travail & (extensible/generic) Repositories Framework . Il vous permettra d'économiser beaucoup de temps. Ils ont mis en place un référentiel générique (il y a aussi un référentiel async). Pour étendre n'importe quel dépôt ils ont utilisé des extensions comme ceci:
public static decimal GetCustomerOrderTotalByYear(this IRepository<Customer> repository, string customerId, int year)
{
return repository
.Queryable()
.Where(c => c.CustomerID == customerId)
.SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year))
.SelectMany(c => c.OrderDetails)
.Select(c => c.Quantity*c.UnitPrice)
.Sum();
}
certaines classes comme QueryObject peuvent être surchargées en fonction de votre projet, mais en général, c'est une bonne solution pour vous aider à démarrer.