C#实体框架:添加到上下文和saveChanges之间的数据验证()

问题描述:

我有一个在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

我总是使用两个验证:

  • 客户端 - 与数据注释
  • 服务器端验证组合使用jQuery不引人注目的验证 - 在这里它取决于应用程序 - 确认在控制器的动作或更深执行在商业逻辑。不错的地方是在你的上下文中重写OnSave方法,并在那里执行它

请记住,你可以编写自定义的数据注记属性,它可以验证任何你需要的。

我想你应该使用数据注解,正如@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

我必须为考试完成这个程序集。项目需求以简化评估 –

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

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获取对象时,我必须调用更新数据后检索的对象的验证方法 –

将数据添加到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; 
} 

我强烈建议你(如果可能的话),所以制定者是私人修改实体(不用担心,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

正如我在上面的评论中提到的,您可能想要查看.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将在那里验证,以及在你忘记或对象改变的情况下。