将ViewModel投影回Model的最佳方法

时间:2016-03-12 16:01:46

标签: c# asp.net-mvc asp.net-mvc-4 mvvm automapper

考虑使用ViewModel:

public class ViewModel
{
    public int id {get;set;}
    public int a {get;set;}
    public int b {get;set;}
}

和这样的原始模型:

public class Model
{
    public int id {get;set;}
    public int a {get;set;}
    public int b {get;set;}
    public int c {get;set;}
    public virtual Object d {get;set;}
}

每次我获得视图模型时,我必须将所有ViewModel属性逐个放入Model中。类似的东西:

var model = Db.Models.Find(viewModel.Id);
model.a = viewModel.a;
model.b = viewModel.b;
Db.SaveChanges();

这总会导致很多问题。我甚至有时会忘记提及一些属性然后发生灾难! 我正在寻找类似的东西:

Mapper.Map(model, viewModel);

BTW:我只使用automapper将Model转换为ViewModel,但反之亦然,我总是面临错误。

3 个答案:

答案 0 :(得分:8)

总的来说,这可能不是您要找的答案,但这是AutoMapper作者的引用:

  

我不能为我的生活理解为什么我想要转储DTO   直接回到模型对象。

我认为从ViewModel映射到Entity的最佳方法是不使用AutoMapper。 AutoMapper是一个很好的工具,用于映射对象而不使用静态以外的任何其他类。否则,每个添加的服务都会使代码变得更加混乱和混乱,并且在某些时候您无法跟踪导致您的字段更新,集合更新等的原因。

经常遇到的具体问题:

  1. 需要非静态类来为您的实体进行映射

    您可能需要使用DbContext来加载和引用实体,您可能还需要其他类 - 一些将图像上传到您的文件存储的工具,一些用于密码的哈希/盐等的非静态类等。你要么必须以某种方式将它传递给automapper,在AutoMapper配置文件中注入或创建,这两种做法都非常麻烦。

  2. 可能需要在同一ViewModel(Dto)上进行多次映射 - >实体对

    对于相同的viewmodel-entity对,您可能需要不同的映射,具体取决于此实体是否为聚合,或者不是+基于您是否需要引用此实体或引用和更新。总体而言,这是可以解决的,但在代码中会产生许多不需要的噪音,甚至更难维护。

  3. 非常脏的代码很难维护。

    这个是关于基元(字符串,整数等)和手动映射引用,转换值等的自动映射。对于automapper,代码看起来很奇怪,你必须定义属性的映射(或者不是,如果你更喜欢隐式自动映射 - 当与ORM配对时也具有破坏性)并使用AfterMap,BeforeMap,Conventions,ConstructUsing等来映射其他属性,这会使事情变得更复杂。

  4. 复杂映射

    当你必须进行复杂的映射时,比如从2个以上的源类到1个目标类的映射,你将不得不进一步复杂化,可能会调用类似的代码:

    var target = new Target();
    Mapper.Map(source1, target);
    Mapper.Map(source2, target);
    //etc..
    

    该代码导致错误,因为您无法将source1和source2映射到一起,并且映射可能取决于将源类映射到目标的顺序。如果您忘记进行1次映射,或者您的地图与1个属性存在冲突的映射,并且相互覆盖,我就不会说话。

  5. 这些问题可能看起来很小,但是在我使用自动化库来将ViewModel / Dto映射到Entity的几个项目中,它比从未使用过时更加痛苦。

    以下是一些链接:

答案 1 :(得分:2)

为此,我们编写了一个简单的映射器。它按名称映射并忽略虚拟属性(因此它适用于实体框架)。如果要忽略某些属性,请添加PropertyCopyIgnoreAttribute。

用法:

PropertyCopy.Copy<ViewModel, Model>(vm, dbmodel);
PropertyCopy.Copy<Model, ViewModel>(dbmodel, vm);

代码:

public static class PropertyCopy
{
    public static void Copy<TDest, TSource>(TDest destination, TSource source)
        where TSource : class
        where TDest : class
    {
        var destProperties = destination.GetType().GetProperties()
            .Where(x => !x.CustomAttributes.Any(y => y.AttributeType.Name == PropertyCopyIgnoreAttribute.Name) && x.CanRead && x.CanWrite && !x.GetGetMethod().IsVirtual);
        var sourceProperties = source.GetType().GetProperties()
            .Where(x => !x.CustomAttributes.Any(y => y.AttributeType.Name == PropertyCopyIgnoreAttribute.Name) && x.CanRead && x.CanWrite && !x.GetGetMethod().IsVirtual);
        var copyProperties = sourceProperties.Join(destProperties, x => x.Name, y => y.Name, (x, y) => x);
        foreach (var sourceProperty in copyProperties)
        {
            var prop = destProperties.FirstOrDefault(x => x.Name == sourceProperty.Name);
            prop.SetValue(destination, sourceProperty.GetValue(source));
        }
    }
}

答案 2 :(得分:-1)

我想解决您的问题中与“忘记一些财产并发生灾难”有关的特定问题。发生这种情况的原因是,您的模型上没有构造函数,而只有可以在任何地方设置(或不能设置)的设置器。这不是防御性编码的好方法。

我在所有模型上都使用构造函数,如下所示:

    public User(Person person, string email, string username, string password, bool isActive)
    {
        Person = person;
        Email = email;
        Username = username;
        Password = password;
        IsActive = isActive;                    
    }

    public Person Person { get; }          
    public string Email { get;  }
    public string Username { get; }
    public string Password { get; }
    public bool IsActive { get; }

如您所见,我没有设置器,因此对象构造必须通过构造函数来完成。如果尝试创建没有所有必需参数的对象,则编译器将抱怨。

通过这种方法,很明显,从ViewModel转到Model时,诸如AutoMapper之类的工具就没有意义,因为使用此模式进行的Model构造不再是简单的映射,而是构造对象。

随着模型变得越来越复杂,您会发现它们与ViewModel显着不同。 ViewModel趋于平坦,具有简单的属性,例如string,int,bool等。另一方面,模型通常包含自定义对象。您会在我的示例中注意到有一个Person对象,但是UserViewModel会像这样使用原语:

public class UserViewModel
{
   public int Id { get; set; }
   public string LastName { get; set; }
   public string FirstName { get; set; }
   public string Email { get; set; }
   public string Username { get; set; }
   public string Password { get; set; }
   public bool IsActive { get; set;}
}

因此从原语到复杂对象的映射限制了AutoMapper的用途。

我的方法始终是手动构建ViewModels到Model的方向。在另一方面,从模型到视图模型,我经常使用一种混合方法,我会手动将Person映射到FirstName,LastName,但我会为简单的属性使用映射器。

编辑:基于以下讨论,AutoMapper比我认为的更擅长讨人喜欢。尽管我不会推荐一种方式,但是如果您使用它,则可以利用诸如“构造和配置验证”之类的功能来帮助防止静默故障。