2016-01-21 35 views
4

我有一個在C#中使用實體框架的簡單場景。我有一個實體帖子:C#實體框架:添加到上下文和saveChanges之間的數據驗證()

public class Post 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 

在我PostManager我有以下方法:

public int AddPost(string name, string description) 
    { 
     var post = new Post() { Name = name, Description = description }; 

     using (var db = new DbContext()) 
     { 
      var res = db.Posts.Add(post); 
      res.Validate(); 
      db.SaveChanges(); 
      return res.Id; 
     } 
    } 

    public void UpdatePost(int postId, string newName, string newDescription) 
    { 
     using (var db = new DbContext()) 
     { 
      var data = (from post in db.Posts.AsEnumerable() 
       where post.Id == postId 
       select post).FirstOrDefault(); 

      data.Name = newName; 
      data.Description = newDescription; 
      data.Validate(); 
      db.SaveChanges(); 
     } 
    } 

方法的validate()是指類:

public static class Validator 
{ 
    public static void Validate(this Post post) 
    { 
     if (// some control) 
      throw new someException(); 
    } 

我之前調用validate方法savechanges(),但在將對象添加到上下文之後。在這個簡單的場景中驗證數據的最佳做法是什麼?反而更好地驗證參數?如果在將對象添加到上下文之後驗證方法拋出異常,那麼對象後期會發生什麼情況?

UPDATE:

我不得不放棄取決於數據驗證錯誤的自定義設置例外。

+1

我通常只使用數據註釋https://msdn.microsoft.com/en-us/library/dd901590(VS.95).aspx其中EF將「查找」;否則還有其他方法在運行時從您自己的代碼中調用它 – MickyD

+0

@Micky嗨!在我的項目中,我必須在驗證數據時拋出customException。有可能使用數據註解做它? –

+0

當然,請查看我的回答 – MickyD

回答

0

我總是使用兩個驗證:

  • 客戶端 - 與數據註釋
  • 服務器端驗證組合使用jQuery不引人注目的驗證 - 在這裏它取決於應用程序 - 確認在控制器的動作或更深執行在商業邏輯。不錯的地方是在你的上下文中重寫OnSave方法,並在那裏執行它

請記住,你可以編寫自定義的數據註記屬性,它可以驗證任何你需要的。

2

我想你應該使用數據註解,正如@Micky上面所說的。添加之後,您目前的方法正在進行手動驗證。

using System.ComponentModel.DataAnnotations; 
// Your class 
public class Post 
{ 
    [Required] 
    public int Id { get; set; } 
    [Required,MaxLength(50)] 
    public string Name { get; set; } 
    [Required,MinLength(15),MyCustomCheck] // << Here is your custom validator 
    public string Description { get; set; } 
} 

// Your factory methods 
public class MyFactory() { 
    public bool AddPost() { 
    var post = new Post() { Id = 1, Name = null, Description = "This is my test post"}; 
     try { 
      using (var db = new DbContext()) { 
       db.Posts.Add(post); 
       db.SaveChanges(); 
       return true; 
      } 
     } catch(System.Data.Entity.Validation.DbEntityValidationException e) { 
      Console.WriteLine("Something went wrong...."); 
     } catch(MyCustomException e) { 
      Console.WriteLine(" a Custom Exception was triggered from a custom data annotation..."); 
     } 
     return false; 

    } 
} 

// The custom attribute 
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] 
sealed public class MyCustomCheckAttribute : ValidationAttribute 
{ 
    public override bool IsValid(object value) 
     { 
      if (value instanceof string) { 
       throw new MyCustomException("The custom exception was just triggered....") 
      } else { 
      return true; 
      } 
     } 
} 

// Your custom exception 
public class MyCustomException : Exception() {} 

參見: DbEntityValidationException類:https://msdn.microsoft.com/en-us/library/system.data.entity.validation.dbentityvalidationexception(v=vs.113).aspx

默認數據註解 http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx

建立自定義數據註解(驗證): https://msdn.microsoft.com/en-us/library/cc668224.aspx

+0

謝謝回覆。我必須拋出自定義異常。有可能通過數據註釋來完成它? –

+0

我建議使用ValidationException類,因爲它的用途是內置於許多其他子系統(ASP.NET有ModelState: if(ModelState.IsValid){//提交} else {//顯示錯誤} 原因你應該使用validationexception類,它已經具有屬性來(自動)保存所有驗證問題。爲什麼你想要使用不同的異常? –

+0

我必須爲考試完成這個程序集。項目需求以簡化評估 –

0

您可以修改代碼,在此方式:

public int AddPost(string name, string description) 
    { 
     var post = new Post() { Name = name, Description = description }; 
     if(res.Validate()) 
     { 
      using (var db = new DbContext()) 
      { 
       var res = db.Posts.Add(post); 
       db.SaveChanges(); 
       return res.Id; 
      } 
     } 
     else 
      return -1; //if not success 
    } 


    public static bool Validate(this Post post) 
    { 
     bool isValid=false; 
     //validate post and change isValid to true if success 
     if(isvalid) 
      return true; 
     } 
     else 
      return false; 
    } 
+0

嗨!感謝您的回覆。我也必須在UpdatePost中做到這一點。當我從DbContext獲取對象時,我必須調用更新數據後檢索的對象的驗證方法 –

0

將數據添加到DbContext並調用SaveChanges()之前,可以調用DbContext的GetValidationErrors()方法並檢查其計數,以便檢查是否有任何錯誤。您可以進一步枚舉所有錯誤,並針對每個錯誤獲取錯誤細節。我已將ICollection的錯誤轉換捆綁到GetValidationErrorsString()擴展方法中的字符串中。

if (db.GetValidationErrors().Count() > 0) 
{ 
    var errorString = db.GetValidationErrorsString(); 
} 


public static string GetValidationErrorsString(this DbContext dbContext) 
{ 
    var validationErrors = dbContext.GetValidationErrors(); 
    string errorString = string.Empty; 
    foreach (var error in validationErrors) 
    { 

     foreach (var innerError in error.ValidationErrors) 
     { 
      errorString += string.Format("Property: {0}, Error: {1}<br/>", innerError.PropertyName, innerError.ErrorMessage); 
     } 
    } 
    return errorString; 
} 
2

我強烈建議你(如果可能的話),所以制定者是私人修改實體(不用擔心,EF仍然可以設置他們代理的創建),標誌着默認的構造函數爲protected( EF仍然可以進行延遲加載/代理創建),並使唯一的公共構造函數可用來檢查參數。

這樣做有幾個好處:

  • 你限制一個實體的狀態是可以改變的名額,從而減少重複
  • 你保護你的類的不變量。通過強制通過構造函數創建實體,確保實體的對象無法處於無效或未知狀態。
  • 你會獲得更高的凝聚力。通過將數據上的約束放在數據本身附近,就可以更容易理解和推理您的類。
  • 您的代碼變得更加自我記錄。一個人從來不會想知道「如果我在這個int屬性上設置一個負值,它可以嗎?」如果甚至不可能做到這一點。
  • 分離關注點。你的經理不應該知道如何驗證一個實體,這隻會導致高耦合。我看到很多管理者都成長爲不可維護的怪物,因爲他們只是做了一切。持久化,加載,驗證,錯誤處理,轉換,映射等。這基本上是SOLID OOP的極端相反。

我知道這是真的時下熱門的只是讓所有的「模特」變成愚蠢的財產袋getter和setter,只有一個默認的構造函數,因爲(壞)的ORM,我們不得不這樣做,但是這不再這種情況,並且這個imo有這麼多問題。

代碼示例:

public class Post 
{ 
    protected Post() // this constructor is only for EF proxy creation 
    { 
    } 

    public Post(string name, string description) 
    { 
     if (/* validation check, inline or delegate */) 
      throw new ArgumentException(); 

     Name = name; 
     Description = description; 
    } 

    public int Id { get; private set; } 
    public string Name { get; private set; } 
    public string Description { get; private set; } 
} 

然後你PostManager代碼變得微不足道:

using (var db = new DbContext()) 
{ 
    var post = new Post(name, description); // possibly try-catch here 
    db.Posts.Add(post); 
    db.SaveChanges(); 
    return post.Id; 
} 

如果創建/驗證邏輯是非常複雜的這種模式使其本身非常好重構到出廠照顧的創作。

我還會注意到,將數據封裝在暴露最小狀態改變API的實體中會導致類的幾個數量級更易於單獨測試,如果您關心的是這類事情。

+0

謝謝您的回覆!我還必須在更新操作中進行驗證。如果我更新後期實體,我也必須在「set」方法中寫入所有驗證條件。 –

+0

如果您有一組已知的需要實體更改狀態的用例,則應該創建封裝這些操作的實例方法。這些也將非常容易測試,具有很高的凝聚力(保持靜態類中的所有驗證也是維護噩夢的燃料),並有助於保護您的不變量。如果驗證是微不足道的,你可以考慮直接暴露setter,但我發現在大多數情況下,訪問私有setter的方法更清潔,更易於維護。 – kai

+0

我不明白「僅僅讓對象處於有效狀態」與我所寫的相反。應該注入所需的值,當然,不需要的值應該被省略。儘管我認爲在可能的情況下傾向於不變性通常是明智的,但我從來沒有提出過所有類都應該是不可改變的情況。 「私人」制定者並不是一成不變的階級。正如我在上面的評論中指出的那樣,在適當的情況下,應該使這個階級發生變化,但它應該是一個衆所周知的小組操作,但不是一個被濫用的大開門。 – kai

1

正如我在上面的評論中提到的,您可能想要查看.NET System.ComponentModel.DataAnnotations命名空間。

數據註釋(DA)允許您指定屬性的屬性來描述可接受的值。知道DA 完全獨立於數據庫和ORM API(如實體框架),因此使用DA屬性修飾的類可以在系統的任何層中使用無論它是數據層; WCF; ASP.NET MVC或WPF。

在下面的例子中,我定義了一個具有一系列屬性的Muppet類。

  • Name是必需的,具有50

  • Scaryness一個最大長度需要一個int但它必須是在{0 ... 100}的範圍內。

  • Email裝飾有一個虛構的自定義驗證程序,用於驗證應該包含電子郵件的字符串。

例子:

public class Muppet 
{ 
    [Required] 
    [StringLength(50)] 
    public string Name {get; set;} 

    public Color Color {get; set; } 

    [Range(0,100)] 
    public int Scaryness {get; set; } 

    [MyCustomEmailValidator] 
    public string Email {get;set; } 
} 

在項目中,我不得不放棄customException當我驗證數據。有可能使用數據註解做它?

是的,你可以。如果你想獲得驗證錯誤消息,你可以使用此方法

using System; 
using System.Collections.Generic; 
using System.ComponentModel.DataAnnotations; 
using System.Linq; 

. 
. 
. 
Post post = ... // fill it in 
Validator.Validate(post); 

public static class Validator 
{ 
    public static void Validate(this Post post) 
    { 
     // uses the extension method GetValidationErrors defined below 
     if (post.GetValidationErrors().Any()) 
     { 
      throw new MyCustomException(); 
     } 
    } 
} 


public static class ValidationHelpers 
{ 

    public static IEnumerable<ValidationResult> GetValidationErrors(this object obj) 
    { 
     var validationResults = new List<ValidationResult>(); 
     var context = new ValidationContext(obj, null, null); 
     Validator.TryValidateObject(obj, context, validationResults, true); 
     return validationResults; 
    } 
. 
. 
. 

要在你的應用程序中的任何時候(無論它是否已經達到EF與否)驗證此對象只是執行此
/// <summary> 
    /// Gets the validation error messages for column. 
    /// </summary> 
    /// <param name="obj">The object.</param> 
    /// <returns></returns> 
    public static string GetValidationErrorMessages(this object obj) 
    { 
     var error = ""; 

     var errors = obj.GetValidationErrors(); 
     var validationResults = errors as ValidationResult[] ?? errors.ToArray(); 
     if (!validationResults.Any()) 
     { 
      return error; 
     } 

     foreach (var ee in validationResults) 
     { 
      foreach (var n in ee.MemberNames) 
      { 
       error += ee + "; "; 
      } 
     } 

     return error; 
    } 

自由設置的牛排刀是驗證屬性將被檢測到,一旦對象達到EF將在那裏驗證,以及在你忘記或對象改變的情況下。