Patterns for mapping data between domain models

2019-05-13 21:11发布

This is a common thing that I have been needing to do recently and I was looking for any common patterns to make this a little easier.

The main gist of it all is that I have some data models, which are modelled to satisfy the ORM and purely do CRUD operations to objects. These models are currently exposed via repositories/factories (dependant upon if its C or RUD).

I then have a view model, which is a bit more readable, and is sprinkled with UI concerns, such as validation and mapping data between the view (this is an ASP.MVC scenario but this situation can be abstracted to most situations).

So lets say I go to localhost/user/1, that should go and get me the user with an Id 1 in the DB and then display it on the UI. Ultimately this has to pull data down from the data domain and then map it to a ui model for display purposes.

Here is an example scenario:

public class OrmUser
{
    public int Id {get;set;}
    public string Name {get;set;}
    public IList<Permission> Permissions {get;set;}
}

public class UiUser
{
    [Required]
    public int Id {get;set;}
    [Required]
    public string Name {get;set;}
    public bool IsUserAdmin {get;set;}
}

public class UserMapper : IMapper<UiUser>
{
    public UiUser Get(int id)
    {
        var ormUser = UserRepository.Get(id);
        var uiUser = new UiUser
        {
            Id = ormUser.Id,
            Name = ormUser.Name,
            IsUserAdmin = IsUserAdmin(ormUser.Permissions)
        }
    }

    private bool IsUserAdmin(IList<Permission> permissions)
    {
        return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
    }
}

This is a simple example but shows how a data model, contains a lot of the same sort of information, but at this view in question you do not care about all the information just a subset of it. This way having a mapper you get to abstract not only the mapping but the communication with the data domain, however you need to write a mapper class for each type, and the above assumes it is a one way mapping, not a 2 way mapping which would require some more code.

So how do you all go about carrying out this mapping? As currently I have just been writing abstraction mappers which basically allow the UI layer to run a query, and get back a view model, abstracting the repositories and the copying data from one model to another, and it just feels like there should be a better way to do this.

3条回答
劳资没心,怎么记你
2楼-- · 2019-05-13 21:48

Yes. AutoMapper is the tool for you. There are ton's of article about this, but go to the source - Jimmy Bogard. He's the master mind behind AutoMapper.

Look at this blog article http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

AutoMapper now days support MVC in a very efficient way where you can decorate your classes with attributes on what mapping should be done.

/Best Regards Magnus

查看更多
Deceive 欺骗
3楼-- · 2019-05-13 21:49

In this particular case, I might just write an extension method for User in my presentation layer.

public static class UserPresentationExtensions{
 public bool IsAdmin(this User user){
  return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
 }
}

View would look something like this:

@model User
<fieldset>
 <legend>User: @Model.Name</legend>
 @if(user.IsAdmin()){
  User is an admin
 }
</fieldset>

To avoid importing namespace repeatedly, would use web.config for that:

<configuration>
 <system.web.webPages.razor>
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
   <namespaces>
    <add namespace="MyProject.PresentationExtensions" />
   ...

You have a point, however lets say you want to add validation to your models, so you decide to add a [Required] attribute to your Name property. Although then if you use a shared model then your data layer needs to know about the annotations, and if your data layer had to have some attributes your UI layer would need to know about them. In the simplest of situations you are right, sometimes it is easier to just have 1 model being the truth, and just access it in a different way, but for most complex projects you will find yourself dirtying both sides to try and save a few lines of code.

I said - in this particular case. This approach applies only if mapping isn't really worth it and there's only 1-way communication (you just need to render it).

When it comes to receiving posts, it gets bit different. Most "one size fits all" approach would be applying so called Thunderdome principle. That is:

All Controller methods take in one ViewModel object (or zero objects in some cases) and return a single ViewModel object (one object enters, one object leaves).

However, quite often I prefer going extension/html helper way and just pass according arguments to action like this:

public void BatheCat(int id /* cat id */, int bathId, string shampoo){
 ...
}

If parameter count gets out of control (I don't bother while it's <= 3), I just encapsulate them (here's an example from my project).

查看更多
三岁会撩人
4楼-- · 2019-05-13 21:56

In .net you should probably take a look at Automapper

https://github.com/AutoMapper/AutoMapper

It essentially allows you to do:

CreateMap<Domain.Customer, ViewModel.Customer>()

And you can then map between the two by saying:

var vmCustomer = Mapper.Map<Domain.Customer, ViewModel.Customer>(domainCustomer);

It will likely save you a ton of boiler plate code.

查看更多
登录 后发表回答