在ASP.NET MVC中绑定数据库实体模型

5

我在尝试思考在控制器操作中重新创建数据库对象的最佳方法时遇到了麻烦。

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

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

在用户编辑对象之后保存它(视图中可编辑的1或2个属性),ModelBinded对象将缺少数据,因此将其保存为它将导致数据库中的数据被覆盖为无效值,或者NOT-NULL约束失败。

那么,从视图返回的表单数据绑定到控制器操作中来自数据库的对象的最佳方法是什么?

注意我正在使用NHibernate。


我和你处于完全相同的情况(也在使用NH)。我已经实现了一个模型绑定器来避免代码重复。你对从绑定器访问数据库的结论是什么? - kay.herzam
最终我反对在绑定器中访问数据库。我的视图模型现在与我的领域模型分开。直接绑定领域模型存在问题(nhibernate将在请求结束时刷新绑定对象,可能使用无效数据,并且除非创建新会话重新获取所需对象,否则您将在整个请求期间使用无效的绑定对象)。 - Luke Smith
4个回答

4

我从数据库中获取模型对象,然后使用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");
}

1
我发现如果我的模型中有一个属性被赋了值,但在FormCollection中不存在,当使用TryUpdateModel时,即使包括/排除属性,该属性也会被设置为null。所以基本上TryUpdateModel会导致数据丢失,这并不好。 - Luke Smith
只有在指定了白名单的情况下,才应该替换白名单中的属性。 我知道这个方法可行,因为我有一些编辑视图只能更新某些值而不能更新其他值。 您可以查看 http://www.codeplex.com/aspnet 上的实际代码进行验证。 - tvanfosson

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 不应查询数据库的内容后,我开始重新考虑。 - Luke Smith
1
有点臭,但如果这让你感觉更好的话,[ARDataBind]来自Castle项目正是做这个的。 - Ben Scheirman
1
在ModeBinder中访问数据库并不是一个好方法。此外,在ActionFilters之前执行绑定器,因此在从绑定器访问数据库时请牢记这一点。 - Chris Canal
是的,这是一个很好的观点,在不同的动作过滤器停止请求之前,您可能会访问数据库... - Ben Scheirman

0
首先,我要声明的是,我不建议从ModelBinders访问数据库,因为从关注点分离的角度来看,ModelBinders只应负责解释客户端请求,显然数据库不是。如果您不想重复自己(DRY),请使用repositories/services。但是,如果您真的想这样做,那么在global.asax.cs中注册一个自定义的MyModelBinderProvider到MVC即可。
ModelBinderProviders.BinderProviders.Add(new EntityModelBinderProvider 
{ 
     ConnectionString = "my connection string"
));

构建自定义的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的进一步指示


-1

实际上,您不必访问数据库。仅设置对象的Id就足以建立关系,但要注意级联设置。确保级联设置不会更新相关对象,否则它将清除值。


2
那真的是非常危险的建议。你应该在更新之前始终进行选择,因为有许多情况下你可能没有更新数据库中的所有记录,这将导致覆盖为空值。 - Ben Scheirman
非常正确,那是一个愚蠢的回答,因为我没有仔细阅读问题! - Chris Canal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接