Automapper: Auto map collection property for a dto object - automapper

I have a domain object
public class ProductModel
{
public long Id {get;set;}
public string Name {get;set;}
public string SerialNumber {get;set;}
}
Single Dto class:
public class ProductDto
{
public long Id {get;set;}
public string Name {get;set;}
public string SerialNumber {get;set;}
}
Single Dto class that is a list of Dto object:
public class ProductListDto : List<ProductDto>
{
public List<ProductDto> Products;
public ProductListDto()
{
Products = new List<ProductDto>();
}
}
And I'd like to map a list of domain objects to list of Dto objects such that the "Products" property of ProductListDto object AUTOMATICALLY is mapped with a list of ProductModel objects:
ProductListDto dto = new ProductListDto();
Mapper.CreateMap<ProductModel, ProductDto>();
/* dto = (ProductListDto) Mapper.Map<List<ProductModel>, List<ProductDto>>((List<ProductModel>)model); this code line causes error. It is commented out. */
dto.Products = Mapper.Map<List<ProductModel>, List<ProductDto>>((List<ProductModel>)model); // (*) works OK but need to specify "Products" property
The code line (*) works OK, but I'd like to know if there is another way to AUTOMATICALLY (implicitly) map that "Products" property of dto object other than the code line (*)?
That means I do not have to write code like the left hand side of code line (*).

You will need to create a mapping for it. Something like this should work:
namespace StackOverflow
{
using System.Collections.Generic;
using AutoMapper;
public class MyProfile : Profile
{
public override string ProfileName
{
get
{
return "MyProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<ProductModel, ProductDto>();
Mapper.CreateMap<List<ProductModel>, ProductListDto>()
.ForMember(dest => dest.Products,
opt => opt.MapFrom(
src => Mapper.Map<List<ProductModel>,
List<ProductDto>>(src)));
}
}
}
Then in your code you can do:
dto = Mapper.Map<List<ProductModel>, ProductListDto>((List<ProductModel>)model);
Here are a couple of unit tests to show how it works:
namespace StackOverflow
{
using System.Collections.Generic;
using AutoMapper;
using NUnit.Framework;
[TestFixture]
public class MappingTests
{
[Test]
public void AutoMapper_Configuration_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
}
[Test]
public void AutoMapper_DriverMapping_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
var products = new List<ProductModel>
{
new ProductModel
{
Id = 1,
Name = "StackOverflow Rocks",
SerialNumber = "1234"
},
new ProductModel
{
Id = 2,
Name = "I Also Rock",
SerialNumber = "4321"
}
};
var productsDto =
Mapper.Map<List<ProductModel>, ProductListDto>(products);
Assert.That(productsDto, Is.Not.Null);
Assert.That(productsDto.Products, Is.Not.Null);
Assert.That(productsDto.Products.Count, Is.EqualTo(2));
}
}
}

Related

How to configure map of List attribute in AutoMapper?

I'm pretty newbie to AutoMapper. I have four classes:
public class Source
{
public int Id {get;set;}
public List<SourceItem> items {get;set;}
}
public class SourceItem
{
public int Id {get;set;}
public string firstName {get;set;}
public string lastName {get;set;}
}
public class Destination
{
public int Id {get;set;}
public List<SourceItemDestination> items {get;set;}
}
public class SourceItemDestination
{
public int Id {get;set;}
public string firstName {get;set;}
}
Is it possible to create a mapping profile to map Source to Destination allowing to bring the list of SourceItemDestination when performing mapper.Map?
Done! Here are my classes:
using AutoMapper;
using AutoMapper.EquivalencyExpression;
using Infrastructure.Data.Context;
using Microsoft.Extensions.DependencyInjection;
using System;
using WebAPI.AutoMapper;
namespace WebAPI.Configurations
{
public static class AutoMapperConfig
{
public static void AddAutoMapperConfiguration(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.AddAutoMapper((serviceProvider, automapper) =>
{
automapper.AddCollectionMappers();
automapper.UseEntityFrameworkCoreModel<CellLabDbContext>(serviceProvider);
}, typeof(MappingProfile).Assembly);
}
}
}
using ApplicationCore.Domain;
using AutoMapper;
namespace WebAPI.AutoMapper
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<SourceItem, SourceItemDestination>().ReverseMap();
CreateMap<Source, Destination>().ReverseMap();
}
}
}
Important tip: I had problems when mapping the lists because the property items of Source class were read only:
public class Source
{
public int Id {get;set;}
public List<SourceItem> items {get;}
}
By doing this the mapper does not map the list. However, it does not raise any error or warning.

inherited class AutoMapper.AutoMapperMappingException

I am new at automapper and it is a very good stuff easy to use, but now I have a problem with it. Trying to convert my derived class to base and it gives me
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types: ClientEventDb -> EventId
Database.ClientEventDb -> EventId
Destination path: ClientEvent
Source value:
Event:Login
Automapper wants to convert ClientEventDb to EventId? I don't understand why. EventId is an enum...
Please help me I have run out of ideas.
Here is the code which I run:
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>();
Console.WriteLine("hello");
return edbl.Select(edb => Mapper.Map<ClientEvent>(edb)).ToArray();
Here are my classes
[Table("events", Schema = "public")]
public class ClientEventDb : ClientEvent
{
public ClientEventDb(string userName, EventId happening, object userObject = null)
: base(userName, happening, userObject)
{
}
public ClientEventDb()
{
}
}
[ProtoContract]
[Table("events", Schema = "public")]
public class ClientEvent : ClientEventBase
{
[ProtoMember(1)]
[Column("username")]
public string UserName { get; private set; }
[ProtoMember(2)]
[Column("time")]
public DateTime DateTime { get; private set; }
[ProtoMember(3)]
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; private set; }
[ProtoMember(4)]
[Column("data")]
public byte[] UserObject { get; set; }
public ClientEvent(string userName,EventId happening, object userObject=null) : base(happening)
{
UserName = userName;
DateTime = DateTime.Now;
//UserObject = null;
if (userObject!=null) throw new NotImplementedException();
}
public ClientEvent()
{
}
protected ClientEvent Clone()
{
return (ClientEvent)MemberwiseClone();
}
}
[ProtoContract]
[ProtoInclude(10, typeof(ClientEvent))]
public class ClientEventBase
{
[Column("eventid")]
[ProtoMember(1)]
public int EventIdValue { get; set; } //must be public because of entity framework
[NotMapped]
public EventId EventId
{
get { return (EventId) EventIdValue; }
set { EventIdValue = (int) value; }
}
public ClientEventBase(EventId eventId)
{
EventId = eventId;
}
public ClientEventBase()
{
}
public override string ToString()
{
return String.Format("Event:{0}",EventId);
}
}
public enum EventId
{
Login = 1,
Logout,
ExitApplication,
}
UPDATE
bugfix: ClientEvent [Key] attribute moved to id property
Solution was this (thx to stuartd):
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>().ConstructUsing((ClientEventDb src) => new ClientEvent());
return edbl.Select(Mapper.Map<ClientEvent>).ToArray();
AutoMapper is confused as its made to map between similar properties in different classes, you are using it incorrectly - you just need to go from the derived class to the base which does not require AutoMapper. You could use this to do what you need....
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
return edbl.Cast<ClientEvent>().ToList();
I'd be looking at why you even feel you need a derived ClientEventDb though - understand we dont have the whole picture here but it seems to do nothing in addition to what the base class already does.
The issue is that ClientEvent has two constructors but you have not told AutoMapper which to use.
If you want it to use your constructor with parameters, change your mapping code to this and it will work:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing(src => new ClientEvent(src.UserName, src.EventId));
Or to make AutoMapper use the default constructor:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing((ClientEventDb src) => new ClientEvent());

create sort on list for web API controller

I am writing my first web API controller so I am a bit of a noob in this area. I am trying to retrieve a list of data through a static class called CustomerDataSource:
public static class CustomerDataSource
{
public static List<Customer> customerData
{
get
{
Customer customer1 = new Customer() { name = "Bert", address = "London" };
Customer customer2 = new Customer() { name = "Jon", address = "New York" };
List<Customer> listCustomers = new List<Customer>();
listCustomers.Add(customer1);
listCustomers.Add(customer2);
return listCustomers;
}
}
}
public class Customer
{
public string name { get; set; }
public string address { get; set; }
}
I am a bit stuck with my ApiController because I am trying to sort the list either on 'name' or 'address' but using a string called 'field' does not compile. What would be a good implementation for a WebAPI controller GETmethod which provides for sorting on one of the Customer properties ?
public class ValuesController : ApiController
{
// GET api/values
public List<Customer> Get(string field)
{
var list = CustomerDataSource.customerData.OrderBy(field);
}
}
Create an extension method like below, then you can use it anywhere within the same namespace in your project.
public static class extensionmethods
{
public static IQueryable<T> OrderByPropertyName<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var rs = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(rs);
}
}
Then you can use it like:
public List<Customer> Get(string PropertyName)
{
var list = CustomerDataSource.customerData.AsQueryable().OrderByPropertyName("PropertyName",true).ToList();
}
Note:
Because the extension method uses IQueryable and returns IQuerybale, so you need to convert your List to IQueryable. Also you can order the list by ascending and descending order, just pass the boolean type value to the second parameter. The default is ascending.
You need to use a lambda expression.
if (field == "name")
var list = CustomerDataSource.customerData.OrderBy(d => d.name);
else if (field == "address")
var list = CustomerDataSource.customerData.OrderBy(d => d.address);

Automapper: How to leverage a custom INamingConvention?

I am working with a database where the designers really seemed to enjoy capital letters and the underscore key. Since I have a simple ORM, my data models use these names as well. I need to build DTOs and I would prefer to give them standard names since we are exposing them through services.
The code below is now corrected! The test passes so use this as a reference if you need to use multiple naming conventions
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using AutoMapper;
using NUnit.Framework;
namespace AutomapperTest
{
public class DATAMODEL
{
public Guid ID { get; set; }
public string FIRST_NAME { get; set; }
public List<CHILD_DATAMODEL> CHILDREN { get; set; }
}
public class CHILD_DATAMODEL
{
public Guid ID { get; set; }
public int ORDER_ID { get; set; }
}
public class DataModelDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public List<ChildDataModelDto> Children { get; set; }
}
public class ChildDataModelDto
{
public Guid Id { get; set; }
public int OrderId { get; set; }
}
public class UpperUnderscoreNamingConvention : INamingConvention
{
private readonly Regex _splittingExpression = new Regex(#"[\p{Lu}0-9]+(?=_?)");
public Regex SplittingExpression { get { return _splittingExpression; } }
public string SeparatorCharacter { get { return "_"; } }
}
public class Profile1 : Profile
{
protected override void Configure()
{
SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
DestinationMemberNamingConvention = new PascalCaseNamingConvention();
CreateMap<DATAMODEL, DataModelDto>();
CreateMap<CHILD_DATAMODEL, ChildDataModelDto>();
}
}
[TestFixture]
public class Tests
{
[Test]
public void CanMap()
{
//tell automapper to use my convention
Mapper.Initialize(x => x.AddProfile<Profile1>());
//make a dummy source object
var src = new DATAMODEL();
src.ID = Guid.NewGuid();
src.FIRST_NAME = "foobar";
src.CHILDREN = new List<CHILD_DATAMODEL>
{
new CHILD_DATAMODEL()
{
ID = Guid.NewGuid(),
ORDER_ID = 999
}
};
//map to destination
var dest = Mapper.Map<DATAMODEL, DataModelDto>(src);
Assert.AreEqual(src.ID, dest.Id);
Assert.AreEqual(src.FIRST_NAME, dest.FirstName);
Assert.AreEqual(src.CHILDREN.Count, dest.Children.Count);
Assert.AreEqual(src.CHILDREN[0].ID, dest.Children[0].Id);
Assert.AreEqual(src.CHILDREN[0].ORDER_ID, dest.Children[0].OrderId);
}
}
}
Create your mappings in profiles, and define the INamingConvention parameters as appropriate.
I don't like the global/static, so I prefer using Initialize and define all of my mappings together. This also has the added benefit of allowing a call to AssertConfiguration... which means if I've borked my mapping I'll get the exception at launch instead of whenever my code gets around to using the problematic mapping.
Mapper.Initialize(configuration =>
{
configuration.CreateProfile("Profile1", CreateProfile1);
configuration.CreateProfile("Profile2", CreateProfile2);
});
Mapper.AssertConfigurationIsValid();
in the same class with that initialization method:
public void CreateProfile1(IProfileExpression profile)
{
// this.CreateMap (not Mapper.CreateMap) statements that do the "normal" thing here
// equivalent to Mapper.CreateMap( ... ).WithProfile("Profile1");
}
public void CreateProfile2(IProfileExpression profile)
{
profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
profile.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
// this.CreateMap (not Mapper.CreateMap) statements that need your special conventions here
// equivalent to Mapper.CreateMap( ... ).WithProfile("Profile2");
}
if you do it this way, and don't define the same mapping in both profiles, I don't think you need anything to "fill in the blank" from the original question, it should already be setup to do the right thing.
What about
public class DATAMODELProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DATAMODEL, DATAMODEL>();
Mapper.CreateMap<DATAMODEL, SOMETHINGELSE>();
Mapper.CreateMap<DATAMODEL, DataModelDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FIRST_NAME))
.ForMember(dest => dest.ChildDataModels, opt => opt.MapFrom(src => src.CHILD_DATAMODELS));
}
}

Is AutoMapper able to auto resolve types base on existing maps

I have the following code:
[SetUp]
public void SetMeUp()
{
Mapper.CreateMap<SourceObject, DestinationObject>();
}
[Test]
public void Testing()
{
var source = new SourceObject {Id = 123};
var destination1 = Mapper.Map<SourceObject, DestinationObject>(source);
var destination2 = Mapper.Map<ObjectBase, ObjectBase>(source);
//Works
Assert.That(destination1.Id == source.Id);
//Fails, gives the same object back
Assert.That(destination2 is DestinationObject);
}
public class ObjectBase
{
public int Id { get; set; }
}
public class SourceObject : ObjectBase { }
public class DestinationObject : ObjectBase { }
So basically, I want AutoMapper to automatically resolve the destination type to "DestinationObject" based on the existing Maps set up in AutoMapper. Is there a way to achieve this?
You could try the following mapping with the latest version (1.1):
Mapper.CreateMap<ObjectBase,ObjectBase>()
.Include<SourceObject, DestinationObject>();
Mapper.CreateMap<SourceObject, DestinationObject>();

Resources