ASP.NET MVC 5 EF 6 - 仅业务逻辑 类 与存储库和工作单元已更新
ASP.NET MVC 5 EF 6 - Just buiness logic classes vs repository and unit of work UPDATED
在过去的几个月里,我一直在学习使用 EF6 的 MVC5。老实说,这是一件 love/hate 的事情,不是与框架本身有关,而是与 Microsoft 的在线教程有关。不是全部,但他们的大部分教程似乎都像电视连续剧的最后一季一样结束。观众们在家里绞尽脑汁想弄清楚故事的其余部分,或者他们甚至首先开始观看它的原因。
无论如何,我最近的绊脚石是我是否应该使用 EF6 实现存储库模式和工作单元?我对任何类型的附加层感兴趣的原因只是为了帮助减少控制器中的逻辑代码量。
在考虑存储库之前,我的模型文件夹中只有一个简单的 public class,名为 productservice.cs,它允许我从控制器传递产品模型以执行一些数据操纵并 return 将其保存到要保存的视图中。从我的角度来看,它工作得很好,但我在该服务中调用了一个额外的 dbcontext class,我认为这似乎不正确。
在网上进行一些研究后,我开始实施一个存储库,它允许我执行与 productservice.cs 中相同的数据操作,但它似乎遵循了一个既定的模式并允许上下文从控制器传递到存储库,我可以在保存前执行一些操作。考虑到 EF 保存、更新和删除工作正常,需要编写更多代码,但我不介意编写更多代码,如果它能让我在更好的地方前进。
现在的问题是如何在控制器中获取下拉列表列表?一个快速的解决方案是在我的产品存储库中为每个列表放置一个 Get()。它有效,但似乎我正在编写比我需要的更多的代码。我看到为每个模型编写单独的存储库的优势,因为它们可能有不同的存储和检索方法。使用存储库的正确解决方案是创建一个工作单元来引用该控制器所需的每个存储库吗?这是我的一些代码
产品库界面:
public interface IProductRepository : IDisposable
{
IEnumerable<Category> GetCategories();
IEnumerable<Manufacturer> GetManufacturers();
IEnumerable<ProductType> GetProductTypes();
IEnumerable<Availability> GetAvailabilities();
IEnumerable<ShipMethod> GetShipMethods();
IEnumerable<Product> GetProducts();
IEnumerable<Product> GetProductsByName(string productName);
Product GetProductById(int productId);
void InsertProduct(Product product);
void DeleteProduct(int productId);
void UpdateProduct(Product product);
void Save();
}
我的控制器的顶部:
public class ProductController : Controller
{
private IProductRepository productRepository;
public ProductController()
{
this.productRepository = new ProductRepository(new ProductContext())
}
public ProductController(IProductRepository productRepository)
{
this.productRepository = productRepository;
}
public ActionResult Create()
{
Product product = new Product();
product.Created = DateTime.Now;
ViewBag.AvailabilityId = new SelectList(productRepository.GetAvailabilities(), "AvailabilityId", "Name");
ViewBag.CategoryId = new SelectList(productRepository.GetCategories(), "CategoryId", "Name");
ViewBag.ManufacturerId = new SelectList(productRepository.GetManufacturers(), "ManufacturerId", "Name");
ViewBag.ProductTypeId = new SelectList(productRepository.GetProductTypes(), "ProductTypeId", "Name");
ViewBag.ShipMethodId = new SelectList(productRepository.GetShipMethods(), "ShipMethodId", "Name");
return View(product);
}
在花了一些时间找出最终解决方案后,我一直回到同一个问题。为什么我不能将 IProductRepository 和 Repository 变成 IProductService 和 ProductService?
基本上将所有 CRUD 保留在控制器中,并在需要时调用一个服务,该服务可以传回控制器以进行最终存储或呈现?如果 EF 已经在控制器中为我创建一堆 CRUD 方法,那么为每个实体创建一堆 CRUD 方法的真正意义是什么?
提前感谢您的帮助。只是有点困惑。
更新 - 我想展示一个我最初的控制器和服务理念的例子。我知道逻辑很简单,可以在控制器中,但是我的一些其他服务,如在创建新产品时裁剪和保存缩略图和原始图像,在控制器中占用了大量 space。
来自控制器的块:
public class ProductTesterController : Controller
{
private ProductContext db = new ProductContext();
private ProductService service = new ProductService();
// GET: Dashboard/ProductManager/Details/5
public ActionResult Detail(int id)
{
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
service.UpdatePid(product, db);
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Detail", new { id = product.ProductId });
}
ViewBag.AvailabilityId = new SelectList(db.Availability, "AvailabilityId", "Name", product.AvailabilityId);
ViewBag.CategoryId = new SelectList(db.Categories, "CategoryId", "Name", product.CategoryId);
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "Name", product.ManufacturerId);
ViewBag.ProductTypeId = new SelectList(db.ProductTypes, "ProductTypeId", "Name", product.ProductTypeId);
ViewBag.ShipMethodId = new SelectList(db.ShipMethods, "ShipMethodId", "Name", product.ShipMethodId);
return View(product);
}
然后是服务:
public class ProductService
{
public Product CreatePid(Product product, ProductContext context)
{
int lastProductId = context.Products.OrderByDescending(x => x.ProductId).First().ProductId;
product.PID = Convert.ToInt32("" + (100 * product.CategoryId) + (lastProductId + 1));
return (product);
}
public Product UpdatePid(Product product, ProductContext context)
{
product.PID = Convert.ToInt32("" + (100 * product.CategoryId) + product.ProductId);
return (product);
}
}
我没有在服务中创建新的上下文,只是从控制器传递上下文和 return 我需要的上下文。对于我的项目规模和缺乏实际 repository/uow 经验,这似乎适合我的情况。我的意思是服务 class 的全部原因是为了减小我的控制器的大小并保持简单。只是这样做而不是创建 repository/uow/service 是否有任何负面影响?谢谢!
更新
在花了很多时间研究这个之后,我偶然发现了这个博客,它似乎正是我要找的东西。自此 post 我也开始使用 Autofac 将我的上下文注入构造函数。很有魅力。
这是 link 希望能帮助寻求类似解决方案的人 -
Making Entity Framework More Unit Testable - Josh Kodroff
我将 GenericRepository
class 与 UnitOfWork
结合使用,它为通过 EntityFramework 链接回数据库的每个模型创建了一个 GenericRepository 实例。看这里:http://www.codeproject.com/Articles/825646/Generic-Repository-and-UnitofWork-patterns-in-MVC
这是一个非常简单的例子,说明我过去是如何使用它的。值得注意的是,对于更复杂的 SQL 需求,我使用了像 Dapper
这样的 ORM,这太棒了! :
dbEntities.cs
public class dbEntities : DbContext
{
public DbSet<Product> Products{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Product>().HasKey(x => x.id);
}
}
GenericRepository.cs
public class GenericRepository<TEntity> where TEntity :class
{
internal dbEntities _db;
internal DbSet<TEntity> dbSet;
public GenericRepository(dbEntities _db)
{
this._db = _db;
this.dbSet = _db.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (_db.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
_db.Entry(entityToUpdate).State = EntityState.Modified;
}
}
UnitOfWork.cs
public class UnitOfWork :IDisposable
{
private dbEntities _db = new dbEntities();
private GenericRepository<Product> productRepository;
public GenericRepository<Product> ProductRepository
{
get
{
if (this.productRepository == null)
{
this.productRepository = new GenericRepository<Product>(_db);
}
return productRepository;
}
}
public void Save()
{
_db.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
在 unitofwork.cs
和 dbEntities.cs
文件中为每个模型添加了一个新条目。模型 class 在结构上对应于数据库 table.
简单用法示例
var _uow = new UnitOfWork();
//get all products over £100
var lst = _uow.ProductRepository.Get(n=>!n.price>100);
_uow.Dispose();
在过去的几个月里,我一直在学习使用 EF6 的 MVC5。老实说,这是一件 love/hate 的事情,不是与框架本身有关,而是与 Microsoft 的在线教程有关。不是全部,但他们的大部分教程似乎都像电视连续剧的最后一季一样结束。观众们在家里绞尽脑汁想弄清楚故事的其余部分,或者他们甚至首先开始观看它的原因。
无论如何,我最近的绊脚石是我是否应该使用 EF6 实现存储库模式和工作单元?我对任何类型的附加层感兴趣的原因只是为了帮助减少控制器中的逻辑代码量。
在考虑存储库之前,我的模型文件夹中只有一个简单的 public class,名为 productservice.cs,它允许我从控制器传递产品模型以执行一些数据操纵并 return 将其保存到要保存的视图中。从我的角度来看,它工作得很好,但我在该服务中调用了一个额外的 dbcontext class,我认为这似乎不正确。
在网上进行一些研究后,我开始实施一个存储库,它允许我执行与 productservice.cs 中相同的数据操作,但它似乎遵循了一个既定的模式并允许上下文从控制器传递到存储库,我可以在保存前执行一些操作。考虑到 EF 保存、更新和删除工作正常,需要编写更多代码,但我不介意编写更多代码,如果它能让我在更好的地方前进。
现在的问题是如何在控制器中获取下拉列表列表?一个快速的解决方案是在我的产品存储库中为每个列表放置一个 Get()。它有效,但似乎我正在编写比我需要的更多的代码。我看到为每个模型编写单独的存储库的优势,因为它们可能有不同的存储和检索方法。使用存储库的正确解决方案是创建一个工作单元来引用该控制器所需的每个存储库吗?这是我的一些代码
产品库界面:
public interface IProductRepository : IDisposable
{
IEnumerable<Category> GetCategories();
IEnumerable<Manufacturer> GetManufacturers();
IEnumerable<ProductType> GetProductTypes();
IEnumerable<Availability> GetAvailabilities();
IEnumerable<ShipMethod> GetShipMethods();
IEnumerable<Product> GetProducts();
IEnumerable<Product> GetProductsByName(string productName);
Product GetProductById(int productId);
void InsertProduct(Product product);
void DeleteProduct(int productId);
void UpdateProduct(Product product);
void Save();
}
我的控制器的顶部:
public class ProductController : Controller
{
private IProductRepository productRepository;
public ProductController()
{
this.productRepository = new ProductRepository(new ProductContext())
}
public ProductController(IProductRepository productRepository)
{
this.productRepository = productRepository;
}
public ActionResult Create()
{
Product product = new Product();
product.Created = DateTime.Now;
ViewBag.AvailabilityId = new SelectList(productRepository.GetAvailabilities(), "AvailabilityId", "Name");
ViewBag.CategoryId = new SelectList(productRepository.GetCategories(), "CategoryId", "Name");
ViewBag.ManufacturerId = new SelectList(productRepository.GetManufacturers(), "ManufacturerId", "Name");
ViewBag.ProductTypeId = new SelectList(productRepository.GetProductTypes(), "ProductTypeId", "Name");
ViewBag.ShipMethodId = new SelectList(productRepository.GetShipMethods(), "ShipMethodId", "Name");
return View(product);
}
在花了一些时间找出最终解决方案后,我一直回到同一个问题。为什么我不能将 IProductRepository 和 Repository 变成 IProductService 和 ProductService?
基本上将所有 CRUD 保留在控制器中,并在需要时调用一个服务,该服务可以传回控制器以进行最终存储或呈现?如果 EF 已经在控制器中为我创建一堆 CRUD 方法,那么为每个实体创建一堆 CRUD 方法的真正意义是什么?
提前感谢您的帮助。只是有点困惑。
更新 - 我想展示一个我最初的控制器和服务理念的例子。我知道逻辑很简单,可以在控制器中,但是我的一些其他服务,如在创建新产品时裁剪和保存缩略图和原始图像,在控制器中占用了大量 space。
来自控制器的块:
public class ProductTesterController : Controller
{
private ProductContext db = new ProductContext();
private ProductService service = new ProductService();
// GET: Dashboard/ProductManager/Details/5
public ActionResult Detail(int id)
{
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid)
{
service.UpdatePid(product, db);
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Detail", new { id = product.ProductId });
}
ViewBag.AvailabilityId = new SelectList(db.Availability, "AvailabilityId", "Name", product.AvailabilityId);
ViewBag.CategoryId = new SelectList(db.Categories, "CategoryId", "Name", product.CategoryId);
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "Name", product.ManufacturerId);
ViewBag.ProductTypeId = new SelectList(db.ProductTypes, "ProductTypeId", "Name", product.ProductTypeId);
ViewBag.ShipMethodId = new SelectList(db.ShipMethods, "ShipMethodId", "Name", product.ShipMethodId);
return View(product);
}
然后是服务:
public class ProductService
{
public Product CreatePid(Product product, ProductContext context)
{
int lastProductId = context.Products.OrderByDescending(x => x.ProductId).First().ProductId;
product.PID = Convert.ToInt32("" + (100 * product.CategoryId) + (lastProductId + 1));
return (product);
}
public Product UpdatePid(Product product, ProductContext context)
{
product.PID = Convert.ToInt32("" + (100 * product.CategoryId) + product.ProductId);
return (product);
}
}
我没有在服务中创建新的上下文,只是从控制器传递上下文和 return 我需要的上下文。对于我的项目规模和缺乏实际 repository/uow 经验,这似乎适合我的情况。我的意思是服务 class 的全部原因是为了减小我的控制器的大小并保持简单。只是这样做而不是创建 repository/uow/service 是否有任何负面影响?谢谢!
更新
在花了很多时间研究这个之后,我偶然发现了这个博客,它似乎正是我要找的东西。自此 post 我也开始使用 Autofac 将我的上下文注入构造函数。很有魅力。
这是 link 希望能帮助寻求类似解决方案的人 - Making Entity Framework More Unit Testable - Josh Kodroff
我将 GenericRepository
class 与 UnitOfWork
结合使用,它为通过 EntityFramework 链接回数据库的每个模型创建了一个 GenericRepository 实例。看这里:http://www.codeproject.com/Articles/825646/Generic-Repository-and-UnitofWork-patterns-in-MVC
这是一个非常简单的例子,说明我过去是如何使用它的。值得注意的是,对于更复杂的 SQL 需求,我使用了像 Dapper
这样的 ORM,这太棒了! :
dbEntities.cs
public class dbEntities : DbContext
{
public DbSet<Product> Products{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Product>().HasKey(x => x.id);
}
}
GenericRepository.cs
public class GenericRepository<TEntity> where TEntity :class
{
internal dbEntities _db;
internal DbSet<TEntity> dbSet;
public GenericRepository(dbEntities _db)
{
this._db = _db;
this.dbSet = _db.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query);
}
else
{
return query;
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (_db.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
_db.Entry(entityToUpdate).State = EntityState.Modified;
}
}
UnitOfWork.cs
public class UnitOfWork :IDisposable
{
private dbEntities _db = new dbEntities();
private GenericRepository<Product> productRepository;
public GenericRepository<Product> ProductRepository
{
get
{
if (this.productRepository == null)
{
this.productRepository = new GenericRepository<Product>(_db);
}
return productRepository;
}
}
public void Save()
{
_db.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
在 unitofwork.cs
和 dbEntities.cs
文件中为每个模型添加了一个新条目。模型 class 在结构上对应于数据库 table.
简单用法示例
var _uow = new UnitOfWork();
//get all products over £100
var lst = _uow.ProductRepository.Get(n=>!n.price>100);
_uow.Dispose();