2013-08-28 53 views
0

我有以下與一對多關係相關的模型類。這些類通過代碼優先的方法一直持續到SQL Server數據庫:在創建時預定義實體值

public class Topic 
{ 
    [Key] 
    public int Id { get; set; } 

    [InverseProperty("Topic")] 
    public virtual IList<Chapter> Chapters { get; set; } 

    //some other properties... 
} 

public class Chapter : IValidatableObject 
{ 
    [Key] 
    public int Id { get; set; } 

    [Required] 
    public string Key { get; set } 

    public virtual Topic Topic { get; set; } 

    //some other properties... 
} 

每個Topic包含了一堆​​。每個Chapter都有一個Key,它的Topic必須是唯一的。

我試圖用下面的方法來驗證這一點:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
{ 
    var chaptersWithSameKey = Topic.Chapters.Where(t => t.Key == Key); 
    foreach (var item in chaptersWithSameKey) 
    { 
     if (item.Id != Id) 
     { 
      yield return new ValidationResult("The key must be unique.", new string[] { "Key" }); 
      break; 
     } 
    }    
} 

然而,Topic總是null在驗證發佈到創建或編輯動作之後發生。這似乎是合理的,因爲視圖不包含有關Topic的信息。但是,我可以在控制器中提取主題,因爲主題的ID是URL的一部分。

我第一次嘗試是設定在文章的開頭的話題就在控制器創建行動:

[HttpPost] 
public ActionResult Create(int topicId, Chapter chapter) 
{ 
    var topic = db.Topics.Find(topicId); 
    if (topic == null) 
     return HttpNotFound(); 
    chapter.Topic = topic; 
    if(ModelState.IsValid) 
     ... 
} 

然而,本章的Validate方法被調用之前控制器可以做任何事情。因此,本章的主題是null

另一種方法就是告訴創建視圖它屬於什麼話題由:

[HttpGet] 
public ActionResult Create(int topicId) 
{ 
    var topic = ... 
    var newChapter = new Chapter() { Topic = topic }; 
    return View(newChapter); 
} 

,併成立了一個隱藏字段中的觀點:

@Html.HiddenFor(model => model.Topic) 
@Html.HiddenFor(model => model.Topic.Id) 

第一個給出了null話題像之前一樣。這似乎很自然,因爲呈現的隱藏字段的值只是主題的ToString()結果。

第二個似乎試圖驗證主題,但因爲缺少屬性而失敗。當Topic的只讀屬性嘗試評估另一個null屬性時,實際原因是NullReferenceException。我不知道爲什麼只讀屬性被訪問。調用堆棧有一些Validate...方法。

那麼上述場景的最佳解決方案是什麼?我試圖在模型中進行驗證,但是缺少一些可能在控制器中檢索到的必要值。

我可以爲此任務創建視圖模型,其中包含int TopicId而不是Topic Topic。但是之後我必須將每個屬性和註解複製到視圖模型或通過繼承來完成。第一種方法似乎相當低效。

所以到現在爲止,繼承方法可能是最好的選擇。但是有沒有其他的選擇不需要引入額外的類型呢?

+0

如果'Topic'爲空,爲什麼不是在'Topic.Chapters.Where'處引發'NullReferenceException'? – haim770

+0

@ haim770是的,當然。我認爲這很明顯。 –

回答

1

首先,您必須認識到,在Action執行之前,驗證(然後您的Validate()方法)在ModelBinder的早期執行。

其次,我認爲您的主要問題是您沒有使用ViewModel,而是將您的Entity/Model返回到視圖並返回到控制器。

您的意見通常與模型/實體本身(就像您的情況一樣)有不同的責任和擔憂。不同的數據結構,不同的驗證規則,最重要的是,你可以塑造你的ViewModel對象來適應頁面/視圖的需求。

您目前的Validate()方法似乎適合數據層驗證需求,而不是您的視圖驗證需求。在Action

public class CreateChapterViewModel : IValidatableObject 
{ 
    public int Id { get; set; } // possible not needed for 'Create' flow 
    public string Key { get; set } 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
     // validation logic that applies to Chapter creation only, for example: 
     // if (this.Key == null) ... 
    } 
} 

然後:

試試這個

[HttpPost] 
public ActionResult Create(int topicId, CreateChapterViewModel chapter) 
{ 
    ... 
} 

總之,不要試圖強迫你的瀏覽你的實體,它們通常有不同的需求,給他們ViewModels並讓它們發送ViewModels。

這種方法的折衷在於,您必須將實體映射到ViewModel並返回,或者創建您自己的映射器或使用類似AutoMapper之類的東西。

0

這是我的最終解決方案,它是haim的答案略作修改。我完全不同意他的原因是,我想在創建(或編輯)實體時強制執行模型層約束。所提到的約束(唯一鍵)是一個模型層約束,我不明白爲什麼它應該移動到視圖。

我做了以下事情。 Chapter保持不變(包括驗證屬性和自定義驗證方法)。我創建了一個繼承Chapter's屬性和行爲的視圖模型,並添加了用於標識主題的TopicId屬性。此外,它會覆蓋Topic屬性並從數據庫中提取主題。

[NotMapped] 
public class ChapterViewModel : Chapter 
{ 
    public int TopicId { get; set; } 
    public override Topic Topic 
    { 
     get 
     { 
      return DbContext.Topics.Find(TopicId); 
     } 
    } 

    private MyDbContext ctx; 
    public MyDbContext DbContext 
    { 
     private get { if (ctx == null) ctx = new CadenzaDbContext(); return ctx; } 
     set { ctx = value; } 
    } 

    public ChapterViewModel() { } 
    public ChapterViewModel(Chapter c) 
    { 
     Id = c.Id; 
     TopicId = c.Topic == null ? -1 : c.Topic.Id; 
     Key = c.Key; 
    } 
    public Chapter ToPlainChapter(MyDbContext db) 
    { 
     DbContext = db; 
     return new Chapter() 
     { 
      Id = Id, 
      Topic = Topic, 
      Key = Key, 
      Name = Name 
     }; 
    } 
} 

它可以像原來的Chapter一樣使用。對於視圖或訪問DbContext中的章節跟蹤條目,可能需要對Chapter進行特殊轉換。

上述方法的優點是我不必將每個屬性複製到視圖模型。此外,現有的驗證規則也適用於視圖模型,這是我所期望的行爲。任何顯示屬性只能應用一次,而不能應用於每個視圖模型。