J'ai récemment appris ASP.NET MVC (je l'aime). Je travaille avec une entreprise qui utilise l'injection de dépendance pour charger une instance de référentiel dans chaque demande, et je suis habitué à utiliser ce référentiel.
Mais maintenant, je suis en train d'écrire quelques applications MVC. Je ne comprends pas bien le pourquoi et le pourquoi du référentiel utilisé par mon entreprise et j'essaie de décider de la meilleure approche pour implémenter l'accès aux données.
J'utilise C # et Entity Framework (avec toutes les dernières versions).
Je vois trois approches générales pour gérer l’accès aux données.
Contexte de base de données standard dans une instruction using chaque fois que j'accède aux données. C'est simple et ça fonctionne bien. Toutefois, si deux sites doivent lire les mêmes données dans une même demande, les données doivent être lues deux fois. (Avec un seul référentiel par requête, la même instance serait utilisée aux deux endroits et, si je comprends bien, la deuxième lecture renverrait simplement les données de la première lecture.)
Un modèle typique de référentiel . Pour des raisons que je ne comprends pas, ce modèle typique implique la création d'une classe wrapper pour chaque table utilisée dans la base de données. Cela me semble faux. En fait, puisqu'elles sont également implémentées en tant qu'interfaces, je créerais techniquement deux classes de wrapper pour chaque table. EF crée des tables pour moi. Je ne crois pas que cette approche a du sens.
Il existe également un modèle générique de référentiel dans lequel une classe de référentiel unique est créée pour servir tous les objets d'entité. Cela a beaucoup plus de sens pour moi. Mais cela a-t-il un sens pour les autres? Le lien ci-dessus est-il la meilleure approche?
J'aimerais avoir l'avis d'autres personnes sur ce sujet. Etes-vous en train d'écrire votre propre référentiel, en utilisant l'un des précédents, ou en train de faire quelque chose de différent Partagez s'il vous plait.
J'ai utilisé un mélange de # 2 et # 3, mais je préfère si possible un référentiel générique strict (plus strict que celui suggéré dans le lien pour # 3). N ° 1 n'est pas bon car il joue mal avec les tests unitaires.
Si vous avez un domaine plus petit ou avez besoin d'indiquer quelles entités votre domaine vous permet d'interroger, je suppose que les options n ° 2 ou n ° 3 définissant des interfaces de référentiel spécifiques à une entité qui implémentent elles-mêmes un référentiel générique ont du sens. Cependant, je trouve qu'il est épuisant et inutile d'écrire une interface et une implémentation concrète pour chaque entité que je souhaite interroger. À quoi sert public interface IFooRepository : IRepository<Foo>
(encore une fois, sauf si je dois contraindre les développeurs à un ensemble de racines d'agrégat autorisées)?
Je viens de définir mon interface de référentiel générique, avec les méthodes Add
, Remove
, Get
, GetDeferred
, Count
et Find
(Find renvoie une interface IQueryable
permettant LINQ), crée une implémentation générique concrète et l'appelle un jour. Je compte beaucoup 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 écris la requête à l'aide de LINQ.
Cela couvre 95% de mes besoins en persistance. Si j'ai besoin d'effectuer une sorte d'action de persistance qui ne peut pas être effectuée de manière générique, j'utilise une API ICommand
développée à la maison. Par exemple, disons que je travaille avec NHibernate et que je dois effectuer une requête complexe dans le cadre de mon domaine, ou peut-être que je dois exécuter une commande en bloc. 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 SQL brut, NHibernate HQL, peu importe, et l'enregistrer auprès de mon localisateur de services.
Maintenant, dans ma logique métier, je peux faire quelque chose comme ceci:
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
Vous pouvez également utiliser un modèle de spécification avec IQuery
pour créer des requêtes significatives, pilotées par les entrées utilisateur, plutôt que d'avoir une interface avec des millions de propriétés confuses, mais en supposant que le modèle de spécification ne prête pas à confusion en soi;).
Une dernière pièce du casse-tête est lorsque votre référentiel doit effectuer des opérations de référentiels pré et post 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 substituer les méthodes appropriées et faire ce que vous devez faire, et mettre à jour votre enregistrement IoC ou de localisateur de service.
Cependant, cette logique est parfois transversale et difficile à mettre en œuvre en surchargeant une méthode de référentiel. J'ai donc créé IRepositoryBehavior
, qui est fondamentalement un puits d'événements. (Vous trouverez ci-dessous une définition approximative.)
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, vérification de sécurité, suppression logicielle, application de contraintes de domaine, validation, etc. Je crée un comportement, l'enregistre auprès de l'IoC ou du localisateur de service et modifie mon référentiel générique pour intégrer une collection de IRepositoryBehavior
s enregistrés, et vérifie chaque comportement. le type de référentiel actuel et encapsulez l'opération dans les gestionnaires de pré/post pour chaque comportement applicable.
Voici un exemple de comportement de suppression logicielle (soft-delete signifie que lorsque quelqu'un demande à supprimer une entité, nous la marquons simplement comme supprimée afin qu'elle ne puisse plus être renvoyée mais qu'elle ne soit jamais physiquement supprimée).
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 auditeurs d’événements de NHibernate, mais c’est pour cette raison que je l’aime. A) Je peux tester le comportement d'un comportement sans mettre NHibernate en évidence. B) Je peux utiliser ces comportements en dehors de NHibernate (par exemple, le référentiel est une implémentation client qui encapsule les appels de service REST). C) Les écouteurs d'événements de NH peuvent être réels. douleur dans le cul;)
Je recommanderais le numéro 1, avec quelques réserves. Le numéro 2 est ce qui semble être le plus courant, mais d'après mon expérience, le référentiel ne fait que créer un dépotoir désordonné pour les requêtes. Si vous utilisez un référentiel générique (2), il ne s'agit que d'une mince couche entourant le DBContext, un peu inutile à moins que vous ne envisagiez de modifier les ORM (mauvaise idée).
Mais lorsque j'accède directement à DBContext, je préfère utiliser un modèle Pipes and Filters afin de pouvoir réutiliser une logique commune, telle que
items = DBContext.Clients
.ByPhoneNumber('1234%')
.ByOrganisation(134);
ByPhoneNumber et By Organization ne sont que des méthodes d'extension.
C'est parti pour le meilleur modèle de référentiel dans Asp.Net MVC:
Le modèle de référentiel ajoute une couche de séparation entre les couches de données et de domaine d'une application. Cela permet également de mieux tester les éléments d'accès aux données d'une application.
Usine de base de données (IDatabaseFactory.cs):
public interface IDatabaseFactory : IDisposable
{
Database_DBEntities Get();
}
Implémentations de fabrique de bases de données (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 (Repository.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 modèle de référentiel dans le contrôleur:
1. Vous avez un modèle d'utilisateur:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
}
2. Vous devez maintenant créer une classe de référentiel 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. Vous devez maintenant créer une interface de service utilisateur (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. Vous devez maintenant créer une 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 avez tout défini pour votre modèle de référentiel et vous pouvez accéder à toutes les données dans User Controller:
//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 existe une solution prête à l’emploi à URF - Unité de travail et référentiel (extensible/générique) . Cela vous fera gagner beaucoup de temps… .. Ils ont implémenté un référentiel générique (il existe également un référentiel asynchrone). Pour l'extension d'un référentiel, ils ont utilisé des extensions telles que:
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 telles que QueryObject peuvent être surchargées en fonction de votre projet, mais globalement, c'est une bonne solution pour vous aider à être opérationnel.