如何使用嵌套类从特定模型到ViewModel,反之亦然?

Lea*_*ner 1 c# asp.net-mvc entity-framework model viewmodel

我想知道转换的一个好办法ModelViewModel,并ViewModelModel没有AutoMapper或类似的东西,因为我想知道背后是什么,并学习如何做我自己.当然,模型I指的是EF生成的类.

到目前为止,我做了类似的事情,但是当涉及到嵌套类时会遇到一些问题:

    // to VM
    public static Author ToViewModel(EntityAuthor author)
    {
        if (author == null)
            return null;

        Author result = new Author();
        result.Id = author.ATH_ID;
        result.FirstName = author.ATH_FirstName;
        result.LastName = author.ATH_LastName;
        return result;
    }
    public static BlogPost ToViewModel(EntityBlogPost post)
    {
        if (post == null)
            return null;

        Experiment result = new Experiment();
        result.Id = post.BP_ID;
        result.Title = post.BP_Title;
        result.Url = post.BP_Url;
        result.Description = post.BP_Description;
        result.Author = ToViewModel(post.Author);
        return result;
    }


    // from VM 
    public static EntityAuthor ToModel(Author author)
    {
        if (author == null)
            return null;

        EntityAuthor result = new EntityAuthor();
        result.ATH_ID= author.Id;
        result.ATH_FirstName = author.FirstName;
        result.ATH_LastName = author.LastName;
        return result;
    }
    public static EntityBlogPost ToModel(BlogPost post)
    {
        if (post == null)
            return null;

        EntityBlogPost result = new EntityBlogPost();
        result.BP_ID = post.Id;
        result.BP_Title = post.Title;
        result.BP_Url = post.Url;
        result.BP_Description = post.Description;
        result.Author = ToModel(post.Author);
        return result;
    }
Run Code Online (Sandbox Code Playgroud)

注意:将EntityBlogPost外键保存到EntityAuthor.我现在面临的一个问题是,当我想编辑BlogPost时,其对应的实体需要作者的外键:"BP_ATH_ID"要设置,但由于编辑后的帖子的作者为'null',因此为'0',因为我不想http发布作者.仍然,作者需要在视图模型中,因为我想显示它(在http-get期间).这是我的控制器更好地理解(视图不重要):

    // GET: I make use of Author for this
    public ActionResult Edit(int id)
    {
        return View(VMConverter.ToViewModel(new BlogPostService().GetByID(id)));
    }

    //
    // POST: I don't make use of Author for this
    [HttpPost]
    public ActionResult Edit(BlogPost input)
    {
        if (ModelState.IsValid)
        {                
            new BlogPostService().Update(VMConverter.ToModel(input));
            return RedirectToAction("List");
        }
        return View(input);
    }
Run Code Online (Sandbox Code Playgroud)

目前,我的控制器后面有一些服务,只能在Model你的代码中查看(正如你在我的代码中看到的那样).目的是将这个"服务层"重用于其他应用程序.

    public void Update(EntityBlogPost post)
    {
        // let's keep it simple for now
        this.dbContext.Entry(post).State = EntityState.Modified;
        this.dbContext.SaveChanges();
    }
Run Code Online (Sandbox Code Playgroud)

好的,回到我的问题.什么是一个很好的方法来处理这个过渡模型 - > ViewModel并返回?

Sla*_*uma 5

在我看来,这种方法在两个方向都存在问题.

  • 模型到ViewModel(GET请求)

    如果您使用的是这样的方法......

    public static Author ToViewModel(EntityAuthor author)
    
    Run Code Online (Sandbox Code Playgroud)

    ......问题是:你EntityAuthor author从哪里得到的?当然,你使用的数据库加载FindSingle什么的.这实现了EntityAuthor具有所有属性的整个实体.你在视图中是否需要它们?也许是的,在这个简单的例子中.但想象一个大型Order实体,其中有很多对其他实体的引用 - 客户,送货地址,订单商品,联系人,发票地址等等 - 并且您希望显示仅包含某些属性的视图:截止日期,客户姓名,联系人电子邮件地址.

    要应用该ToViewModel方法,您必须EntityOrder使用视图不需要的一大堆属性加载,您甚至必须申请Include相关实体.这将再次加载这些实体的所有属性,但您只需要在视图中选择它们.

    仅加载视图所需属性的常用方法是投影,例如:

    var dto = context.Orders.Where(o => o.Id == someOrderId)
        .Select(o => new MyViewDto
        {
            DueDate = o.DueDate,
            CustomerName = o.Customer.Name,
            ContactPersonEmailAddress = o.ContactPerson.EmailAddress
        })
        .Single();
    
    Run Code Online (Sandbox Code Playgroud)

    如您所见,我已经介绍了一个新的帮助器类MyViewDto.现在您可以创建特定ToViewModel方法:

    public static OrderViewModel ToMyViewModel(MyViewDto dto)
    
    Run Code Online (Sandbox Code Playgroud)

    dto和viewModel之间的映射是AutoMapper的一个很好的候选者.(您无法在上面的投影步骤中使用AutoMapper.)

    另一种方法是直接投影到ViewModel中,即替换MyViewDto上面的OrderViewModel.你必须IQueryable<Order>在ViewModel所在的位置暴露给视图层.有些人不喜欢它,我个人正在使用这种方法.

    缺点是你需要很多不同的类型方法ToMyViewModel,基本上每个视图都有另一种方法.

  • ViewModel到Model(POST请求)

    这是您在示例中已经注意到的更大问题:许多视图不显示完整实体或显示应该是"仅查看"的实体数据,并且不会回发到服务器.

    如果您使用该方法(使用AutoMapper或不使用)...

    public static EntityAuthor ToModel(Author author)
    
    Run Code Online (Sandbox Code Playgroud)

    ... EntityAuthor在大多数情况下,您显然不会创建完整对象,因为视图模型所代表的视图Author author不会显示所有属性,并且至少不会将它们全部发回.使用这样的Update方法:

    this.dbContext.Entry(post).State = EntityState.Modified;
    
    Run Code Online (Sandbox Code Playgroud)

    ...会在数据库中部分销毁实体(或者在最好的情况下抛出异常,因为某些必需的FK或属性未正确设置).实现正确的更新您实际上必须合并存储在数据库中的值,并保持不变,并从视图中回发更改的值.

    可以使用Update为视图定制的特定方法:

    public void UpdateForMyView1(EntityBlogPost post)
    {
        this.dbContext.EntityBlogPosts.Attach(post);
        this.dbContext.Entry(post).Property(p => p.Title).IsModified = true;
        this.dbContext.Entry(post).Property(p => p.Description).IsModified = true;
        this.dbContext.SaveChanges();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这将是一个视图,其只允许进行编辑的方法TitleDescriptionEntityBlogPost.通过将特定属性标记为ModifiedEF,只会更新数据库中的那些列.

    另一种方法是再次引入DTO并在视图模型和那些DTO之间映射方法:

    public static MyUpdateAuthorDto ToMyUpdateAuthorDto(Author author)
    
    Run Code Online (Sandbox Code Playgroud)

    这只是属性复制或AutoMapper.更新可以通过以下方式完成:

    public void UpdateForMyView1(MyUpdateAuthorDto dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(dto.AuthorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这仅更新匹配in EntityAuthor和in 的属性,dto并将它们标记为Modified好像已更改.这将解决您丢失外键的问题,因为它不是dto的一部分,不会更新.数据库中的原始值保持不变.

    请注意,SetValues它采用objectas参数.因此,您可以使用某种可重用的Update方法:

    public void UpdateScalarAuthorProperties(int authorId, object dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(authorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    此方法仅适用于标量和复杂属性的更新.如果允许您的视图更改相关实体或实体之间的关系,则该过程并不那么容易.对于这种情况,我不知道为每种更新编写特定方法的另一种方法.

  • @Cristi:是的,这种方法导致了很多DTO.但是,考虑到您希望保持分层架构,尤其是服务层对视图和视图模型一无所知,那么替代方案是什么?不知何故,您必须告诉服务您在视图中更改了哪些内容才能创建正确的更新.如果您允许在视图层中使用EF上下文,例如在控制器操作中,DTO爆炸将消失,因为大多数MS示例都会显示它.我可以想象在可重用服务和视图之间引入"EntityViewService",但维护并不少. (2认同)