1

我正在編寫一個愚蠢的程序,試圖以實用的方式充分理解設計模式中涉及的所有各種概念。例如,我完全理解DI/IOC(我認爲),但我不完全瞭解如何將它應用於實際的ASP.Net MVC 4/5環境。Ninject DI/ASP.Net MVC - 如何添加業務層?

我正在寫一個有發票和產品的商店程序作爲我唯一的2張表。到目前爲止,我已經成功地完全應用了DI/IOC,並完成了以下結構:

Store.Models < ==實體框架類。 (數據訪問層)。
Store.Interfaces < ==接口。
Store.Repositories < ==包含實際進行並獲取或設置數據的代碼。
Store.Web < ==我的MVC應用程序。

所有的依賴關係設置和工作正常。現在,這是我的問題和問題。

我想如下添加一個業務層:

Store.Business

對於練習,我已經決定簡單地計算,因爲給定日期的年數的目的。當然,在正常情況下,我會將它作爲計算字段存儲在數據庫中並檢索它。但我正在爲學術練習做這件事,因爲在某些時候,我會遇到一種情況,我必須對數據集進行一些複雜的計算。我認爲這不應該真的與模型,存儲庫或控制器完成一起存儲。應該有一個單獨的「業務」層。現在這裏是我的問題:

實體框架根據我的模型定義了一個名爲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中。

現在,當我運行該程序,並輸入發票的網址之前,我得到了以下錯誤:

Invalid column name 'Discriminator'. 
Invalid column name 'Age'. 

當我添加[NotMapped]屬性我只得到這個錯誤:

Invalid column name 'Discriminator'. 

下面是所有相關的代碼開始與EF自動生成的模型:

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> 

請忽略任何評論中的代碼,因爲他們是爲我自己的參考和指導。

再次,這個問題是關於具體爲什麼我得到的錯誤提及以及我需要改變以解決它。我教過添加[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>(); ; 
     } 
    } 
} 
+0

我將發佈不久的人誰願意作出貢獻GitHub上的完整的解決方案。我會在這裏發佈一個鏈接。我只想把它恢復到第一個工作版本,這將需要大約一個小時。 –

+0

我的項目的完整代碼現在位於以下鏈接的github上。 https://github.com/franasm/Store 請轉到破碎的分支獲得上述作爲主分支是最後一個工作日分支我的代碼。感謝任何幫助,並隨時在那裏發表評論,並提出任何改進建議。 –

回答

1

你沒有提到你是否有EF自動重新生成Invoice實體您加入後列。假設您使用的是代碼優先,而不是通過T4模板(.TT文件)生成實體,則您自己維護實體。這一代人是一次性的幫助你入門的東西,所以你不必從頭開始編寫所有的實體。

在這種情況下,你可以在Age字段添加到您的Invoice實體,並讓您的業務服務可以接受一個Invoice實體實例在CalcAge功能,或只是通過一個DateTime該功能,並得到一個時代回來。通常情況下,您希望使用視圖模型,而不是爲此使用EF實體,並且您可能會將出生日期存儲在數據庫中,並計算年齡字段,或者在數據庫中或在實體邏輯中計算屬性getter(它會像[NotMapped]一樣,你已經有了)。

您不希望將業務層中的類耦合到實際的EF實體,而是對實體執行操作,無論是新創建的實體還是通過存儲庫層從數據庫中檢索的實體你現在有。

既然你想使用的業務層,你可以做這樣的事情:

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; } 

     [NotMapped] 
     public int Age {get; set; 
    // ... 

業務服務:

using Store.Models; 

namespace Store.Business 
{ 
    public class InvoiceBL 
    { 
     public int CalcAge(DateTime? date) 
     { 
      Age = 25; 
      //TODO: Come back and enter proper logic to work out age 
      // Something like: 
      // return date != null ? <DateTime.Now.Year - date.Year etc.> : null 
      return Age; 
     } 

在控制器中,你必須計算時代現場每一次設置的Invoice爲你的數據模型返回一個View.這不是最佳的,但它確實使用業務層。

[HttpPost] 
    [ValidateAntiForgeryToken] 
    public ActionResult Create(Store.Model invoice) 
    { 
     if (ModelState.IsValid) 
     { 
      _invoiceRepository.CreateInvoice(invoice); 
      // _service is your business service, injected as a dependency via the constructor, same as the _invoiceRepository is now 
      invoice.Age = __service.CalcAge(invoice.BirthDate); // or some such thing 
      return RedirectToAction("Index"); 
     } 
     return View(invoice); 
    } 

您還必須爲更新操作等進行此操作;任何將Invoice作爲視圖模型返回的操作。

視圖的模型將綁定到發票實體:

@model IEnumerable<Store.Models.Invoice> 

@{ 
    ViewBag.Title = "Index"; 
} 
// ... and so on 

你Ninject容器會綁定服務,這將是該控制器的依賴。我個人有倉庫作爲注入服務的依賴,並注入到控制器,而不是讓服務和控制器內部分離儲存庫內的服務,但我跟你有什麼打算。

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>(); 
      // Add this, assuming there isn't an interface for your service 
      ninjectKernel.Bind<InvoiceBL>().ToSelf();    
     } 
    } 
} 

我沒有看到有關Discriminator列的任何代碼,但如果是在實體和它的映射,它需要在數據庫表中。映射可以通過上下文中使用的類(或直接在上下文中完成)或使用DataAnnotation屬性進行設置,如[NotMapped] is。

+0

我沒有先使用代碼。至少不用於發票和產品表。我不知道如何處理登錄和註冊內容。我只是將這些表物理複製到我自己的數據庫中,並更改了con字符串,並且它工作正常。所以我把它留在那。我確定有一個更好的方法來做到這一點。 我知道我可能只是增加了一個計算字段的數據庫表和所有將正常工作。但我的工作前提是這不適合所有問題。那麼,如果我有一些我想從數據集中計算出來的東西,我不需要用......堵塞數據庫,那我該怎麼辦? –

+0

它更多地關於創建單獨的業務層的問題的答案模型。我知道我使用的例子是人爲的,但足以說明我的觀點。我不知道我完全理解。你是說視圖模型是業務層(或者至少應該是)。這在一定程度上是有意義的。所以在這種情況下,他們應該從存儲庫中讀取數據。不知道這是否是你說的。 –

+0

關於依賴的東西。我仍然在學習這一點。所以,如果您對所說的內容有點清楚,我將不勝感激。我不必使用業務層。但是我想分開視圖模型,如果這要成爲我的業務層,並實際調用它而不是使用另一個模式(MVVM)的印象的視圖模型,即使情況並非如此。 沒有實際上是一個鑑別列。這是錯誤信息的一部分,我不知道它從哪裏得到這個單詞。無論如何,我的代碼沒有什麼。 –