C# ASP.NET MVC 的最佳存储库模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10925257/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best Repository Pattern for ASP.NET MVC
提问by Jonathan Wood
I recently learned ASP.NET MVC (I love it). I'm working with a company that uses dependency injection to load a Repository instance in each request, and I'm familiar with using that repository.
我最近学习了 ASP.NET MVC(我喜欢它)。我正在与一家使用依赖注入在每个请求中加载 Repository 实例的公司合作,我熟悉使用该存储库。
But now I'm writing a couple of MVC applications of my own. I don't fully understand the hows and whys of the repository my company uses, and I'm trying to decide the best approach to implement data access.
但是现在我正在编写自己的几个 MVC 应用程序。我不完全了解我公司使用的存储库的方式和原因,我正在尝试确定实现数据访问的最佳方法。
I am using C# and Entity Framework (with all the latest versions).
我正在使用 C# 和实体框架(所有最新版本)。
I see three general approaches for handling data access.
我看到了三种处理数据访问的一般方法。
Regular DB context within a using statement each time I access data. This is simple and it works okay. However, if two locations need to read the same data within one request, the data must be read twice. (With a single repository per request, the same instance would be used in both places and I understand the second read would simply return the data from the first read.)
A typical repository pattern. For reasons I don't understand, this typical pattern involves creating a wrapper class for every table used from the database. That seems wrong to me. In fact, since they are implemented also as interfaces, I'd technically be creating two wrapper classes for each table. EF creates tables for me. I don't believe this approach makes sense.
There is also a generic repository patternwhere a single repository class is created to serve all entity objects. This makes much more sense to me. But does it make sense to others? Is the link above the best approach?
每次访问数据时,使用语句中的常规数据库上下文。这很简单,而且工作正常。但是,如果在一个请求中两个位置需要读取相同的数据,则必须读取该数据两次。(每个请求有一个存储库,两个地方都会使用相同的实例,我知道第二次读取只会返回第一次读取的数据。)
典型的存储库模式。由于我不明白的原因,这种典型的模式涉及为数据库中使用的每个表创建一个包装类。这对我来说似乎是错误的。事实上,因为它们也是作为接口实现的,所以从技术上讲,我会为每个表创建两个包装类。EF 为我创建表格。我不相信这种方法有意义。
还有一个通用存储库模式,其中创建单个存储库类来为所有实体对象提供服务。这对我来说更有意义。但对其他人有意义吗?上面的链接是最好的方法吗?
I'd love to get some input from others on this topic. Are you writing your own repository, using one of those above, or doing something different altogether. Please share.
我很想从其他人那里得到一些关于这个主题的意见。您是编写自己的存储库,使用上述存储库之一,还是做一些完全不同的事情。请分享。
采纳答案by moribvndvs
I have used a blend of #2 and #3, but I prefer a strict generic repository if possible (stricter than even suggested in the link for #3). #1 is no good because it plays poorly with unit testing.
我使用了 #2 和 #3 的混合,但如果可能的话,我更喜欢严格的通用存储库(甚至比 #3 的链接中建议的更严格)。#1 不好,因为它在单元测试中表现不佳。
If you have a smaller domain or need to constrict which entities that your domain allows to be queried, I suppose #2- or #3 that defines entity specific repository interfaces that themselves implement a generic repository- makes sense. However, I find it to be exhausting and unnecessary to write an interface and a concrete implementation for every entity I want to query. What good is public interface IFooRepository : IRepository<Foo>(again, unless I need to constrain developers to a set of allowed aggregate roots)?
如果您有一个较小的域或需要限制您的域允许查询哪些实体,我想 #2- 或 #3 定义实体特定的存储库接口,这些接口本身实现了通用存储库- 是有道理的。但是,我发现为我要查询的每个实体编写一个接口和一个具体的实现是很累人的,也没有必要。有什么好处public interface IFooRepository : IRepository<Foo>(同样,除非我需要将开发人员限制为一组允许的聚合根)?
I just define my generic repository interface, with Add, Remove, Get, GetDeferred, Count, and Findmethods (Find returns an IQueryableinterface allowing LINQ), create a concrete generic implementation, and call it a day. I rely heavily on Findand thus LINQ. If I need to use a specific query more than once, I use extension methods and write the query using LINQ.
我只是定义了我的通用存储库接口,使用Add, Remove, Get, GetDeferred,Count和Find方法(Find 返回一个IQueryable允许 LINQ的接口),创建一个具体的通用实现,然后称之为一天。我非常依赖FindLINQ,因此也依赖于LINQ。如果我需要多次使用特定查询,我会使用扩展方法并使用 LINQ 编写查询。
This covers 95% of my persistence needs. If I need to perform some sort of persistence action that can't be done generically, I use a home-grown ICommandAPI. For example, say I'm working with NHibernate and I need to perform a complex query as part of my domain, or perhaps I need to do a bulk command. The API looks roughly like this:
这涵盖了我 95% 的持久性需求。如果我需要执行某种一般无法完成的持久性操作,我会使用自己开发的ICommandAPI。例如,假设我正在使用 NHibernate 并且我需要在我的域中执行一个复杂的查询,或者我可能需要执行一个批量命令。API 大致如下所示:
// 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;
}
Now I can create an interface to represent a specific command.
现在我可以创建一个接口来表示一个特定的命令。
public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
Decimal MinimumBalance { get; set; }
}
I can create a concrete implementation and use raw SQL, NHibernate HQL, whatever, and register it with my service locator.
我可以创建一个具体的实现并使用原始 SQL、NHibernate HQL 等,并将其注册到我的服务定位器中。
Now in my business logic I can do something like this:
现在在我的业务逻辑中,我可以做这样的事情:
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
You can also use a Specification pattern with IQueryto build meaningful, user-input-driven queries, rather than having an interface with million confusing properties, but that assumes you don't find the specification pattern confusing in its own right ;).
您还可以使用规范模式 withIQuery来构建有意义的、用户输入驱动的查询,而不是拥有具有数百万个令人困惑的属性的界面,但前提是您没有发现规范模式本身令人困惑;)。
One last piece of the puzzle is when your repository needs to do specific pre- and -post repository operation. Now, you can very easily create an implementation of your generic repository for a specific entity, then override the relevant method(s) and do what you need to do, and update your IoC or service locator registration and be done with it.
最后一个难题是您的存储库何时需要执行特定的存储库前后操作。现在,您可以非常轻松地为特定实体创建通用存储库的实现,然后覆盖相关方法并执行您需要执行的操作,并更新您的 IoC 或服务定位器注册并完成它。
However, sometimes this logic is cross-cutting and awkward to implement by overriding a repository method. So I created IRepositoryBehavior, which is basically an event sink. (Below is just a rough definition off the top of my head)
然而,有时这种逻辑是横切的,并且难以通过覆盖存储库方法来实现。所以我创建了IRepositoryBehavior,它基本上是一个事件接收器。(以下只是我头脑中的粗略定义)
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);
}
Now, these behaviors can be anything. Auditing, security checking, soft-delete, enforcing domain constraints, validation, etc. I create a behavior, register it with the IoC or service locator, and modify my generic repository to take in a collection of registered IRepositoryBehaviors, and check each behavior against the current repository type and wrap the operation in the pre/post handlers for each applicable behavior.
现在,这些行为可以是任何东西。审计、安全检查、软删除、强制域约束、验证等。我创建一个行为,用 IoC 或服务定位器注册它,并修改我的通用存储库以接收注册IRepositoryBehavior的集合,并检查每个行为当前存储库类型并将操作包装在每个适用行为的前/后处理程序中。
Here's an example soft-delete behavior (soft-delete means that when someone asks to delete an entity, we just mark it as deleted so it can't be returned again, but is never actually physically removed).
这是一个软删除行为示例(软删除意味着当有人要求删除一个实体时,我们只是将其标记为已删除,这样就无法再次返回,但实际上从未实际删除过)。
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.
}
}
Yes, this is basically a simplified and abstracted implementation of NHibernate's event listeners, but that's why I like it. A) I can unit test a behavior without bringing NHibernate into the picture B) I can use these behaviors outside of NHibernate (say the repository is client implementation that wraps REST service calls) C) NH's event listeners can be a real pain in the ass ;)
是的,这基本上是 NHibernate 事件侦听器的简化和抽象实现,但这就是我喜欢它的原因。A) 我可以在不将 NHibernate 引入图片的情况下对行为进行单元测试 B) 我可以在 NHibernate 之外使用这些行为(比如存储库是包装 REST 服务调用的客户端实现)C) NH 的事件侦听器可能是一个真正的痛苦;)
回答by Craig
I would recommend number 1, with some caveats. Number 2 is what seems to be most common but in my experience the repository just ends up a messy dumping ground for queries. If you use a generic repository (2) it is just a thin wrapper around the DBContext, a bit pointless really unless you are planning on changing ORM's (bad idea).
我会推荐第 1 个,但有一些警告。数字 2 似乎是最常见的,但根据我的经验,存储库最终只是一个混乱的查询倾倒场。如果您使用通用存储库 (2),它只是围绕 DBContext 的一个瘦包装器,除非您计划更改 ORM(坏主意),否则真的有点毫无意义。
But when I access DBContext directly I prefer to use a Pipes and Filters pattern so you can reuse common logic, something like
但是当我直接访问 DBContext 时,我更喜欢使用管道和过滤器模式,以便您可以重用通用逻辑,例如
items = DBContext.Clients
.ByPhoneNumber('1234%')
.ByOrganisation(134);
The ByPhoneNumber and By Organisation are just extension methods.
ByPhoneNumber 和 By Organization 只是扩展方法。
回答by Arvand
There is a ready to use solution at URF - Unit of Work & (extensible/generic) Repositories Framework. It will save you a lot of time. They implemented a generic repository (also there is an async repository). For extending any repository they have used extensions like this:
在URF - Unit of Work & (extensible/generic) Repositories Framework 中有一个现成的解决方案。它会为您节省很多时间。他们实现了一个通用存储库(还有一个异步存储库)。为了扩展任何存储库,他们使用了这样的扩展:
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();
}
Some classes like QueryObject may be an overwork depending on your project but overally it is good solution to help you get up and running.
根据您的项目,像 QueryObject 这样的一些类可能会过度工作,但总的来说,它是帮助您启动和运行的好解决方案。
回答by Laxman Gite
Here we go for Best Repository Pattern in Asp.Net MVC:
在这里,我们在 Asp.Net MVC 中寻找最佳存储库模式:
The Repository pattern adds a separation layer between the data and domain layers of an application. It also makes the data access parts of an application better testable.
Repository 模式在应用程序的数据层和域层之间添加了一个分离层。它还使应用程序的数据访问部分具有更好的可测试性。
Database Factory (IDatabaseFactory.cs):
数据库工厂 (IDatabaseFactory.cs):
public interface IDatabaseFactory : IDisposable
{
Database_DBEntities Get();
}
Database Factory Implementations (DatabaseFactory.cs):
数据库工厂实现(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();
}
}
Base Interface (IRepository.cs):
基本接口 (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();
}
Abstract Class (Repository.cs):
抽象类(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>();
}
}
How to access this repository pattern in controller:
如何在控制器中访问此存储库模式:
1. You have User Model :
1. 你有用户模型:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
}
2. Now you have to create Repository Class of your UserModel
2. 现在你必须创建你的 UserModel 的 Repository Class
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. Now you have to create UserService Interface (IUserService.cs) with all CRUD Methods:
3. 现在您必须使用所有 CRUD 方法创建 UserService 接口 (IUserService.cs):
public interface IUserService
{
#region User Details
List<User> GetAllUsers();
int SaveUserDetails(User Usermodel);
int UpdateUserDetails(User Usermodel);
int DeleteUserDetails(int Id);
#endregion
}
4. Now you have to create UserService Interface (UserService.cs) with all CRUD Methods:
4. 现在您必须使用所有 CRUD 方法创建 UserService 接口 (UserService.cs):
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. Now you All set for your Repository Pattern and you can access all data in User Controller:
5. 现在你已经为你的存储库模式设置好了,你可以访问用户控制器中的所有数据:
//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();
}
}

