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

And*_*lia 5 c# validation entity-framework

我有一个在 C# 中使用实体框架的简单场景。我有一个实体帖子:

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

在我的 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

方法 validate() 指的是类:

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

我在 savechanges() 之前但在将对象添加到上下文之后调用了 validate 方法。在这个简单的场景中验证数据的最佳实践是什么?最好验证参数?如果在将对象添加到上下文后验证方法抛出异常,对象 post 会发生什么?

更新:

我必须根据数据验证错误抛出一组自定义异常。

sar*_*ara 9

我强烈建议您(如果可能的话)修改您的实体,以便设置器是私有的(不用担心,EF 仍然可以在代理创建时设置它们),将默认构造函数标记为受保护(EF 仍然可以进行延迟加载/代理创建),并让唯一可用的公共构造函数检查参数。

这样做有几个好处:

  • 您限制可以更改实体状态的位置数量,从而减少重复
  • 您保护班级的不变量。通过强制通过构造函数创建实体,您可以确保实体的对象不可能以无效或未知状态存在。
  • 你会获得更高的凝聚力。通过将数据约束置于更接近数据本身的位置,可以更轻松地理解和推理您的类。
  • 您的代码在更高程度上变得自我记录。人们永远不必想知道“如果我为此属性设置负值可以吗int?” 如果一开始就不可能做到的话。
  • 关注点分离。您的经理不应该知道如何验证实体,这只会导致高耦合。我见过许多管理者成长为难以维持的怪物,因为他们什么都做。持久化、加载、验证、错误处理、转换、映射等。这基本上与 SOLID OOP 相反。

我知道现在很流行将所有“模型”放入带有 getter 和 setter 的愚蠢属性包中,并且只有一个默认构造函数,因为(坏的)ORM 迫使我们这样做,但情况已不再是这样,并且有这个海事组织有很多问题。

代码示例:

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; }
}
Run Code Online (Sandbox Code Playgroud)

那么你的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;
}
Run Code Online (Sandbox Code Playgroud)

如果创建/验证逻辑极其复杂,那么这种模式非常适合重构为负责创建的工厂。

我还要指出的是,如果您非常关心这类事情,那么将数据封装在暴露最小状态更改 API 的实体中会导致类更容易单独测试几个数量级。


Daw*_*ski 0

我总是使用两个验证:

  • 客户端 - 使用 jQuery Unobtrusive Validation 结合数据注释
  • 服务器端验证 - 这里取决于应用程序 - 验证在控制器操作中或更深入的业务逻辑中执行。这样做的好地方是在您的上下文中重写 OnSave 方法并在那里执行

请记住,您可以编写自定义数据注释属性,该属性可以验证您需要的任何内容。