考虑使用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,但反之亦然,我总是面临错误。
答案 0 :(得分:8)
总的来说,这可能不是您要找的答案,但这是AutoMapper作者的引用:
我不能为我的生活理解为什么我想要转储DTO 直接回到模型对象。
我认为从ViewModel映射到Entity的最佳方法是不使用AutoMapper。 AutoMapper是一个很好的工具,用于映射对象而不使用静态以外的任何其他类。否则,每个添加的服务都会使代码变得更加混乱和混乱,并且在某些时候您无法跟踪导致您的字段更新,集合更新等的原因。
经常遇到的具体问题:
需要非静态类来为您的实体进行映射
您可能需要使用DbContext来加载和引用实体,您可能还需要其他类 - 一些将图像上传到您的文件存储的工具,一些用于密码的哈希/盐等的非静态类等。你要么必须以某种方式将它传递给automapper,在AutoMapper配置文件中注入或创建,这两种做法都非常麻烦。
可能需要在同一ViewModel(Dto)上进行多次映射 - >实体对
对于相同的viewmodel-entity对,您可能需要不同的映射,具体取决于此实体是否为聚合,或者不是+基于您是否需要引用此实体或引用和更新。总体而言,这是可以解决的,但在代码中会产生许多不需要的噪音,甚至更难维护。
非常脏的代码很难维护。
这个是关于基元(字符串,整数等)和手动映射引用,转换值等的自动映射。对于automapper,代码看起来很奇怪,你必须定义属性的映射(或者不是,如果你更喜欢隐式自动映射 - 当与ORM配对时也具有破坏性)并使用AfterMap,BeforeMap,Conventions,ConstructUsing等来映射其他属性,这会使事情变得更复杂。
复杂映射
当你必须进行复杂的映射时,比如从2个以上的源类到1个目标类的映射,你将不得不进一步复杂化,可能会调用类似的代码:
var target = new Target();
Mapper.Map(source1, target);
Mapper.Map(source2, target);
//etc..
该代码导致错误,因为您无法将source1和source2映射到一起,并且映射可能取决于将源类映射到目标的顺序。如果您忘记进行1次映射,或者您的地图与1个属性存在冲突的映射,并且相互覆盖,我就不会说话。
这些问题可能看起来很小,但是在我使用自动化库来将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比我认为的更擅长讨人喜欢。尽管我不会推荐一种方式,但是如果您使用它,则可以利用诸如“构造和配置验证”之类的功能来帮助防止静默故障。