【问题标题】:Modelbinding database entities in ASPNET MVCASP NET MVC 中的模型绑定数据库实体
【发布时间】:2009-02-05 00:34:10
【问题描述】:

我很难想出最好的方法是在控制器 Action 中重新创建数据库对象。

我想使用 ModelBinders,因此在我的操作中,我可以通过参数访问对象,而不必重复代码以根据标识符参数从数据库中获取对象。所以我想有一个 ModelBinder 调用数据访问层来获取原始对象(如果数据库中不存在,则创建一个新对象),然后将任何属性绑定到数据库对象以更新它。但是我读到 ModelBinders 不应该进行数据库查询(article 的第一条评论)。

如果 ModelBinder 不应该执行数据库查询(所以只使用 DefaultModelBinder),那么具有其他 db 对象属性的数据库对象呢?这些永远不会被分配。

在用户编辑后保存对象(视图中可编辑 1 或 2 个属性)ModelBinded 对象将丢失数据,因此按原样保存会导致数据库中的数据被无效值覆盖,或 NOT-NULL 约束失败。

那么,从与从视图回发的表单数据绑定的数据库中获取控制器操作中的对象的最佳方法是什么?

注意我正在使用 NHibernate。

【问题讨论】:

  • 我和你的情况完全相同(也使用 NH)。我已经实现了一个模型绑定器以避免代码重复。您对从活页夹访问数据库的结论是什么?
  • 最后我反对在活页夹中访问数据库。我的视图模型现在与我的域模型分开。直接绑定到您的域模型存在问题(nhibernate 将在请求结束时用可能无效的数据刷新绑定的对象,除非您创建一个新会话,重新获取您所追求的对象,否则您最终会使用整个请求中的无效绑定对象)。

标签: asp.net-mvc nhibernate model-view-controller modelbinders


【解决方案1】:

我从数据库中获取模型对象,然后在对象上使用 UpdateModel(或 TryUpdateModel)来更新表单参数中的值。

public ActionResult Update( int id )
{
     DataContext dc = new DataContext();
     MyModel model = dc.MyModels.Where( m => m.ID == id ).SingleOrDefault();

     string[] whitelist = new string[] { "Name", "Property1", "Property2" };

     if (!TryUpdateModel( model, whitelist )) {
        ... model error handling...
        return View("Edit");
     }

     ViewData.Model = model;

     return View("Show");
}

【讨论】:

  • 我发现如果我的模型上有一个属性被分配给某个值并且 FormCollection 中不存在该属性,则在执行 TryUpdateModel 时,即使我包含该属性也会设置为 null /排除属性。所以基本上 TryUpdateModel 会导致数据丢失,这不好。
  • 如果您指定了一个,它应该只替换白名单中的属性。我知道这很有效,因为我有一些编辑视图只能更新某些值而不能更新其他值。你可以在codeplex.com/aspnet查看它的实际代码来验证。
【解决方案2】:

很遗憾,您无法控制模型绑定器的构建,因此您无法注入任何存储库实现。

可以直接联系服务定位器以拉入您的存储库并获取项目:

public class ProductBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext, Type modelType)
    {
        if(modelType != typeof(Product))
            return null;

        var form = controllerContext.HttpContext.Request.Form;
        int id = Int32.Parse(form["Id"]);
        if(id == 0)
            return base.CreateModel(controllerContext, bindingContext, modelType);

        IProductRepository repository = ServiceLocator.Resolve<IProductRepository>();

        return repository.Fetch(id);                                    
    }       
}

如果您可以使用提供类 ID 的基类或接口,您甚至可以让所有实体都使用此功能。

您必须在 Global.asax 中进行设置:

ModelBinders.Binders.Add(typeof(Product), new ProductBinder());

然后你可以这样做:

public ActionResult Save([Bind] Product product)
{
    ....

    _repository.Save(product);
}

【讨论】:

  • 这就是我最初所做的,因为在这里做对我来说很有意义。但是在几个地方读到 ModelBinder 不应该查询数据库后,这让我三思而后行。
  • 有点臭,但如果它能让你感觉好点,Castle 项目的 [ARDataBind] 正是这样做的。
  • 命中数据库是 ModeBinder 不是一个好主意。此外,活页夹在 ActionFilters 之前执行,因此在从活页夹访问数据库时请记住这一点
  • 是的,这很好,您可以在不同的操作过滤器停止请求之前访问数据库...
【解决方案3】:

我先声明一下,我不建议从 ModelBinders 访问数据库,因为从关注点分离的角度来看,ModelBinders 应该只负责解释客户端请求,显然数据库不是。
如果您不想重复自己(DRY),请使用存储库/服务 但是如果你真的想那样做,那么

在 global.asax.cs 中注册一个自定义的 MyModelBinderProvider 到 MVC

ModelBinderProviders.BinderProviders.Add(new EntityModelBinderProvider 
{ 
     ConnectionString = "my connection string"
));

Cunstruct 自定义 ModelBinderProvider 以包含数据库设置

public class EntityBinderProvider: IModelBinderProvider
{
    public string ConnectionString { get; set; }

    public IModelBinder GetBinder(Type modelType)
    {
        if (Is known entity)
            return new EntityBinder(ConnectionString);
        else
            return null;
    }
}

按照 Ben Scheirman 的进一步说明

【讨论】:

    【解决方案4】:

    您实际上不必访问数据库。只需设置对象的 Id 就足以建立关系,但请注意您的级联。确保您的 cascde 设置不会更新相关对象,因为它会清除值。

    【讨论】:

    • 这是非常危险的建议。你应该总是在更新之前选择,因为有很多地方你可能没有更新数据库中的所有记录,这最终会被空白值覆盖。
    • 非常正确,这是一个愚蠢的答案,因为我没有正确阅读问题!
    猜你喜欢
    • 2011-01-26
    • 2012-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多