Asp.net mvc MVC和NOSQL:将视图模型直接保存到MongoDB?

Asp.net mvc MVC和NOSQL:将视图模型直接保存到MongoDB?,asp.net-mvc,mongodb,separation-of-concerns,Asp.net Mvc,Mongodb,Separation Of Concerns,我理解,MVC中分离关注点的“适当”结构是使用视图模型来构建视图,使用单独的数据模型来持久化所选的存储库。我开始尝试MongoDB,我开始认为这在使用无模式、无SQL风格的数据库时可能不适用。我想向stackoverflow社区展示这个场景,看看每个人的想法。我是MVC新手,所以这对我来说很有意义,但也许我忽略了一些东西 下面是我的讨论示例:当用户想要编辑他们的配置文件时,他们会转到UserEdit视图,该视图使用下面的UserEdit模型 public class UserEditModel

我理解,MVC中分离关注点的“适当”结构是使用视图模型来构建视图,使用单独的数据模型来持久化所选的存储库。我开始尝试MongoDB,我开始认为这在使用无模式、无SQL风格的数据库时可能不适用。我想向stackoverflow社区展示这个场景,看看每个人的想法。我是MVC新手,所以这对我来说很有意义,但也许我忽略了一些东西

下面是我的讨论示例:当用户想要编辑他们的配置文件时,他们会转到UserEdit视图,该视图使用下面的UserEdit模型

public class UserEditModel
{
    public string Username
    {
        get { return Info.Username; }
        set { Info.Username = value; }
    }

    [Required]
    [MembershipPassword]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [DisplayName("Confirm Password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

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

    public UserInfo Info { get; set; }
    public Dictionary<string, bool> Roles { get; set; }
}

public class UserInfo : IRepoData
{
    [ScaffoldColumn(false)]
    public Guid _id { get; set; }

    [ScaffoldColumn(false)]
    public DateTime Timestamp { get; set; }

    [Required]
    [DisplayName("Username")]
    [ScaffoldColumn(false)]
    public string Username { get; set; }

    [Required]
    [DisplayName("First Name")]
    public string FirstName { get; set; }

    [Required]
    [DisplayName("Last Name")]
    public string LastName { get; set; }

    [ScaffoldColumn(false)]
    public string Theme { get; set; }

    [ScaffoldColumn(false)]
    public bool IsADUser { get; set; }
}
公共类UserEditModel
{
公共字符串用户名
{
获取{return Info.Username;}
设置{Info.Username=value;}
}
[必需]
[会员身份密码]
[数据类型(数据类型.密码)]
公共字符串密码{get;set;}
[数据类型(数据类型.密码)]
[显示名称(“确认密码”)]
[比较(“密码”,ErrorMessage=“密码和确认密码不匹配。”)]
公共字符串ConfirmPassword{get;set;}
[必需]
[电邮]
公共字符串电子邮件{get;set;}
公共用户信息{get;set;}
公共字典角色{get;set;}
}
公共类用户信息:IRepoData
{
[脚手架立柱(假)]
公共Guid _id{get;set;}
[脚手架立柱(假)]
公共日期时间时间戳{get;set;}
[必需]
[显示名称(“用户名”)]
[脚手架立柱(假)]
公共字符串用户名{get;set;}
[必需]
[显示名称(“名字”)]
公共字符串名{get;set;}
[必需]
[显示名称(“姓氏”)]
公共字符串LastName{get;set;}
[脚手架立柱(假)]
公共字符串主题{get;set;}
[脚手架立柱(假)]
公共布尔IsADUser{get;set;}
}
请注意,UserEditModel类包含从IRepoData继承的UserInfo实例?UserInfo是保存到数据库中的内容。我有一个通用的repository类,它接受任何继承IRepoData格式的对象并保存它;所以我只需调用
Repository.Save(myUserInfo)
就可以了。IRepoData定义了_id(MongoDB命名约定)和时间戳,因此存储库可以基于_id进行升级,并根据时间戳以及对象刚刚保存到MongoDB的任何其他属性检查冲突。大多数情况下,视图只需要使用
@Html.EditorFor
,我们就可以开始了!基本上,只有视图需要的任何内容都会进入基本模型,只有存储库需要的任何内容都会得到
[ScaffoldColumn(false)]
注释,其他内容在这两者之间都是通用的。(顺便说一句,用户名、密码、角色和电子邮件将保存到.NET提供者,因此它们不在UserInfo对象中。)

这一情景的巨大优势有两个方面

  • 我可以使用更少的代码,因此更容易理解,开发速度更快,维护性更强(在我看来)

  • 我可以在几秒钟内重新计算。。。如果我需要添加第二个电子邮件地址,我只需将其添加到UserInfo对象中——只需将一个属性添加到该对象中,即可将其添加到视图中并保存到存储库中。因为我使用的是MongoDB,所以我不需要更改我的db模式或处理任何现有数据

  • 鉴于这种设置,是否需要为存储数据制作单独的模型?你们都认为这种方法的缺点是什么?我意识到显而易见的答案是标准和关注点的分离,但是你能想到任何真实世界的例子来证明这会引起一些头痛吗


    另外值得注意的是,我的团队总共有两名开发人员,因此很容易看到好处而忽略一些标准。你认为在一个较小的团队中工作会在这方面有所不同吗?

    MVC中视图模型的优势存在于使用的数据库系统之外(即使你不使用数据库系统,也会很糟糕)。在简单的CRUD情况下,您的业务模型实体将非常接近您在视图中显示的内容,但在基本CRUD以外的任何情况下,都不会出现这种情况

    其中一件大事是业务逻辑/数据完整性问题,即使用与视图中相同的类进行数据建模/持久化。假设您的用户类中有一个
    DateTime DateAdded
    属性,以指示添加用户的时间。如果您提供一个直接挂接到
    UserInfo
    类的表单,那么最终将得到一个如下所示的操作处理程序:

    [HttpPost]
    public ActionResult Edit(UserInfo model) { }
    
    很可能您不希望用户在添加到系统时能够进行更改,因此您的第一个想法是不在表单中提供字段

    然而,出于两个原因,你不能依赖它。首先是
    DateAdded
    的值将与执行
    new DateTime()
    时得到的值相同,或者是
    null
    (对于此用户,任何一种方式都不正确)

    第二个问题是,用户可以在表单请求中伪造此字段,并将
    &DateAdded=
    添加到POST数据中,现在您的应用程序会将DB中的DateAdded字段更改为用户输入的任何内容

    这是设计上的,因为MVC的模型绑定机制查看通过POST发送的数据,并尝试将它们与模型中的任何可用属性自动连接。它无法知道发送过来的属性不是原始形式的,因此它仍然会将其绑定到该属性

    ViewModels没有此问题,因为您的视图模型应该知道如何将自身转换为数据实体或从数据实体转换而来,并且它没有要欺骗的
    DateAdded
    字段,
    public DateTime? BannedDate { get; set; }
    public DateTime? ActivationDate { get; set; } // Date the account was activated via email link
    
    // In status column of the web page's data grid
    
    @if (user.BannedDate != null)
    {
        <span class="banned">Banned</span>
    }
    else if (user.ActivationDate != null)
    {
        <span class="Activated">Activated</span>
    }
    
    //.... Do some html to finish other columns in the table
    // In the Actions column of the web page's data grid
    @if (user.BannedDate != null)
    {
        // .. Add buttons for banned users
    }
    else if (user.ActivationDate != null)
    {
        // .. Add buttons for activated  users
    }
    
    // In status column of the web page's data grid
    
    @if (user.Status == UserStatuses.Banned)
    {
        <span class="banned">Banned</span>
    }
    else if (user.Status == UserStatuses.Activated)
    {
        <span class="Activated">Activated</span>
    }
    
    //.... Do some html to finish other columns in the table
    // In the Actions column of the web page's data grid
    @if (user.Status == UserStatuses.Banned)
    {
        // .. Add buttons for banned users
    }
    else if (user.Status == UserStatuses.Activated)
    {
        // .. Add buttons for activated  users
    }