【发布时间】:2014-06-21 04:43:04
【问题描述】:
我正在编写一个愚蠢的程序,试图以一种实用的方式充分理解设计模式中涉及的所有各种概念。例如,我完全了解 DI / IOC,(我认为),但我不完全了解如何在实际的 ASP.Net MVC 4/5 环境中应用它。
我正在编写一个以发票和产品作为我仅有的 2 个表的商店程序。到目前为止,我已经成功地完全应用了 DI/IOC,并完成了以下结构:
Store.Models Store.Interfaces Store.Repositories Store.Web
所有依赖项都已设置并且工作正常。现在这是我的问题。
我想添加一个业务层如下:
商店.商业
出于练习的目的,我决定简单地计算自给定日期以来的年数。当然,在正常情况下我会将它作为计算字段存储在数据库中并检索它。但我这样做是为了学术练习,因为在某些时候,我会遇到一种情况,我将不得不对数据集进行一些复杂的计算。我认为这不应该与模型、存储库一起存储或在控制器中完成。应该有一个单独的“业务”层。现在这是我的问题:
实体框架根据我的模型定义了一个名为 Invoice 的类。这是一个很好的课程,它一直工作到现在。
我定义了一个接口和存储库,设置了 Ninject,让它与 MVC 一起工作。一切都很完美。再开心不过了。
然后我在发票表中添加了一个日期字段。在 EF 中更新了我的模型,更新了我需要更新的其他内容,一切顺利。
接下来我添加了一个 Store.Business 类项目。我设置了一个新的 Invoice 类,它从模型中继承了 Invoice 类,并添加了一个新的属性、构造函数和方法。
namespace Store.Business
{
//NOTE: Because of limitations in EF you cant declare a subclass of the same name.
public class InvoiceBL : Store.Models.Invoice
{
[NotMapped]
public int Age { get; set; }
public InvoiceBL()
{
Age = CalcAge(Date);
}
private int CalcAge(DateTime? Date)
{
Age = 25;
//TODO: Come back and enter proper logic to work out age
return Age;
}
}
}
然后我修改了我的接口、存储库、控制器、视图等,以使用这个新的 InvoiceBL 类而不是 EF 生成的类。
我开始使用部分类。但我显然对此有麻烦,因为它在不同的项目中。我什至尝试使用相同的命名空间,但没有。对我来说,按项目分开是很重要的。我希望明确定义这些层。因此,由于这不起作用,我选择了继承。无论如何,我更喜欢这种方法,因为我假设部分类是微软的东西,我希望我的理念能够轻松地转移到任何可能没有部分类的 OOP 语言中。另请注意,我将其放回了自己的命名空间中,因此它不再位于命名空间 Store.Models 中,而是在 Store.Business 中。
现在当我运行程序并像以前一样在 url 中输入发票时,我收到以下错误:
Invalid column name 'Discriminator'.
Invalid column name 'Age'.
当我添加 [NotMapped] 属性时,我只收到此错误:
Invalid column name 'Discriminator'.
以下是所有以 EF Auto Generated 模型开头的相关代码:
Store.Models:
namespace Store.Models
{
using System;
using System.Collections.Generic;
public partial class Invoice
{
public Invoice()
{
this.Products = new HashSet<Product>();
}
public int Id { get; set; }
public string Details { get; set; }
public Nullable<decimal> Total { get; set; }
public Nullable<System.DateTime> Date { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}
接下来我们有界面:
namespace Store.Interfaces
{
public interface IInvoice
{
void CreateInvoice(InvoiceBL invoice);
DbSet<InvoiceBL> Invoices { get; }
void UpdateInvoice(InvoiceBL invoice);
InvoiceBL DeleteInvoice(int invoiceId);
}
}
接下来我们有存储库:
namespace Store.Repositories
{
public class InvoiceRepository : BaseRepository, IInvoice
{
public void CreateInvoice(InvoiceBL invoice)
{
ctx.Invoices.Add(invoice);
ctx.SaveChanges();
}
public DbSet<InvoiceBL> Invoices
{
get { return ctx.Invoices; }
}
public void UpdateInvoice(InvoiceBL invoice)
{
ctx.Entry(invoice).State = EntityState.Modified;
ctx.SaveChanges();
}
public InvoiceBL DeleteInvoice(int invoiceId)
{
InvoiceBL invoice = ctx.Invoices.Find(invoiceId);
if (invoice != null)
{
ctx.Invoices.Remove(invoice);
ctx.SaveChanges();
}
return invoice;
}
}
}
我已经向您展示了接口层和存储库层都需要的业务层。所以我将继续讨论控制器:
namespace Store.Web.Controllers
{
public class InvoiceController : Controller
{
//---------------------Initialize---------------------------
private IInvoice _invoiceRepository;
private IProduct _productRepository;
public InvoiceController(IInvoice invoiceRepository, IProduct productRepository)
{
_invoiceRepository = invoiceRepository;
_productRepository = productRepository;
}
//-----------------------Create-----------------------------
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Store.Business.InvoiceBL invoice)
{
if (ModelState.IsValid)
{
_invoiceRepository.CreateInvoice(invoice);
return RedirectToAction("Index");
}
return View(invoice);
}
//-------------------------Read-----------------------------
[ActionName("Index")]
public ActionResult List()
{
return View(_invoiceRepository.Invoices);
}
public ViewResult Details(int id)
{
//How is this DI - If your model changes you have to alter the fields
//addressed here.
return View(_invoiceRepository.Invoices.FirstOrDefault(i => i.Id == id));
}
//-----------------------Update-----------------------------
[ActionName("Edit")]
public ActionResult Update(int id)
{
//How is this DI - If your model changes you have to alter the fields
//addressed here.
var invoice = _invoiceRepository.Invoices.FirstOrDefault(i => i.Id == id);
if (invoice == null) return HttpNotFound();
return View(invoice);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult Update(Store.Business.InvoiceBL invoice)
{
if (ModelState.IsValid)
{
_invoiceRepository.UpdateInvoice(invoice);
return RedirectToAction("Index");
}
else
{
return View(invoice);
}
}
//-----------------------Delete-----------------------------
public ActionResult Delete(int id = 0)
{
//Do you really want to always delete only the first one found?? Not cool?
//Even though in this case, because Id is unique, it will always get the right one.
//But what if you wanted to delete or update based on name which may not be unique.
//The other method (Find(invoice) would be better. See products for more.
//How is this DI - If your model changes you have to alter the fields
//addressed here.
var invoice = _invoiceRepository.Invoices.FirstOrDefault(i => i.Id == id);
if (invoice == null) return HttpNotFound();
return View(invoice);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
if(_invoiceRepository.DeleteInvoice(id)!=null)
{
//Some code
}
return RedirectToAction("Index");
}
//-----------------------Master / Detail--------------------
}
}
终于看到了:
@model IEnumerable<Store.Business.InvoiceBL>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Age)
</th>
<th>
@Html.DisplayNameFor(model => model.Details)
</th>
<th>
@Html.DisplayNameFor(model => model.Total)
</th>
<th>
@Html.DisplayNameFor(model => model.Date)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Age)
</td>
<td>
@Html.DisplayFor(modelItem => item.Details)
</td>
<td>
@Html.DisplayFor(modelItem => item.Total)
</td>
<td>
@Html.DisplayFor(modelItem => item.Date)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
请忽略代码中的任何 cmets,因为它们仅供我自己参考和指导。
再一次,这个问题是关于我为什么会收到提到的错误以及我需要更改什么来解决它。我教过添加 [NotMapped] 属性可以做到这一点,但它没有。
但是,我仍在学习与 MVC 相关的设计模式,所以如果有人对如何更好地构建项目或其他可能有帮助的建议有任何建议,我也很欢迎。
编辑:我忘记了 NinjectControllerFactory:
namespace Store.Web.Ninject
{
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel ninjectKernel;
public NinjectControllerFactory()
{
ninjectKernel = new StandardKernel();
AddBinding();
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
//return base.GetControllerInstance(requestContext, controllerType);
return controllerType == null
? null
: (IController)ninjectKernel.Get(controllerType);
}
private void AddBinding()
{
//TODO FR: Step 4 - Add your interface and repository to the bindings
ninjectKernel.Bind<IProduct>().To<ProductRepository>(); ;
ninjectKernel.Bind<IInvoice>().To<InvoiceRepository>(); ;
}
}
}
【问题讨论】:
-
我很快就会在 GitHub 上发布完整的解决方案,供任何想要贡献的人使用。我会在这里发布一个链接。我只想先让它恢复到工作版本,这需要大约一个小时。
-
我的项目的完整代码现在在 github 上,位于以下链接。 github.com/franasm/Store 请转到损坏的分支以获取上面的代码,因为主分支是我拥有的最后一个工作分支。感谢您提供任何帮助,并随时在此处发表评论并提供任何改进建议。
标签: c# asp.net-mvc entity-framework dependency-injection ninject