AutoMapper objects with different property types - c#

I want to map my Entity Framework entities (generated from a legacy database) to custom DTO objects (which should be nice and clean).
My legacy DB has entities looking a bit like this:
internal class Order {
int id;
string Shipping_date;
string quantity;
}
And I want to map it to a nicer DTO object:
public class OrderDto {
int id;
DateTime? ShippingDate;
int Quantity;
}
I have written an "entity container" to provide dependency injection, which returns values this way:
public IEnumerable<OrderDto> GetPaginatedOrders(int page, int pageSize)
{
return this.db.Orders
.OrderByDescending(c => c.id)
.Paginate(page, pageSize)
.Project()
.To<OrderDto>()
.AsEnumerable();
}
So: change of types, and change of property names.
Were it only change of property names, it would be easy-but-tedious:
Mapper.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.quantity))
.ForMember(dest => dest.ShippingDate, opt => opt.MapFrom(src => src.Shipping_date));
This is not enough with type changes. I tried a whole bunch of stuff:
Parsing the properties at the mapping declaration, like src => int.Parse(src.quantity) but Linq doesn't like it.
Extending the EF entities with custom properties like QuantityInt { get { return int.Parse(this.quantity) } } and using these in the mapping, but AutoMapper doesn't like it, and explicitly don't support them.
Mapping system types one to another like Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32) but I still get Unable to create a map expression from System.String to System.Int32 errors.
Using custom converters for my class, but I always get empty values from ResolutionContext.SourceValues at run-time from my entities (I'm guessing that they are disposed before AutoMapper gets them or something like this).
I'm realizing that AutoMapper is convention-based, so maybe I should use another tool, but which one exist?
Thanks for your help!

.Project() uses Linq to entities, which generates SQL and naturally only understands a very limited set of functions.
If you use
Mapper.Map<IEnumerable<Order>, IEnumerable<OrderDto>>(src)
your conversions will work fine.

Related

AutoMapper > Using Queryable Extensions and some mapping fails

Is there any way to map these EntityFramework entities with AutoMapper?
I'm getting an error when trying to map a DateTime object (from my DBContext entity) to a TimeSpan property on my DTO.
This is the exception
----------- Exception #0 -----------
Type: System.NotSupportedException
Message: The specified type member 'TimeOfDay' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Source: EntityFramework
It used to work when mapping the Entity after ToList() call, but now using AutoMapper's ProjectTo<> IQueryable extension it is obviously trying to convert the expression to an expression that SQL can understand.
My question - is it possible to configure mapping on certain objects to take place after the query has been executed on the server? (e.g. after the ToList() call)
CloseTime property on the DB entity is a DateTime object but we are mapping to a TimeSpan object
For Now - I'm just ignoring the properties like so
cfg.CreateMap<CustomerShipTo, ShipToBase>()
.ForMember(desc => desc.OpenTime, src => src.Ignore())
//.ForMember(desc => desc.OpenTime, src => src.CloseTime.TimeOfDay)
.ReverseMap();
.ProjectTo() needs to be able to ultimately map down to SQL so there are limitations to what the mappings can do as compared to using .Map(). A work-around for issues like this is to map a "raw" data value then use a translation property in the DTO. In your case since this is a data type conversion you could call the raw value "OpenDateTime", normally if I want to use the same name I will use a prefix of "Raw" to reflect the raw DB value. (I.e. RawOpenTime)
[Serializable]
public class ShipToBase
{
// ...
public DateTime OpenDateTime { get; set; }
public Timespan OpenTime
{
get { OpenDateTime.TimeOfDay; }
set { OpenDateTime = DateTime.MinValue().Add(value); } // Setter if needed.
}
}
Then in the mappings:
cfg.CreateMap<CustomerShipTo, ShipToBase>()
.ForMember(desc => desc.OpenDateTime, src => src.OpenTime)
.ForMember(desc => desc.OpenTime, src => src.Ignore())
.ReverseMap();

AutoMapper - Use method to update IEnumerable property with no setter?

I'm having some trouble using AutoMapper to map from a data transfer object to a database entity model. The entity has a few properties that are custom array types, derived from IEnumerable. There's no setter for those properties, but there is a method called SetFromString() that is available. I can't seem to configure my map properly to use it though. Does AutoMapper support this kind of thing? If anyone could point me in the right direction, I'd appreciate it.
Below is a boiled-down version of the key classes I'm working with. (The mapping works just fine going from entity to DTO, but I need it to work in the reverse direction as well.)
// The database entity
public class ContactEntity
{
public CustomArray<String> CustomerNumbers { get; }
}
// The data transfer object
public class ContactDto
{
public List<String> CustomerNumbers { get; set; }
}
// CustomArray definition
public abstract class CustomArray<DataType> : IEnumerable<DataType>, IDisposable
{
protected CustomArray();
public abstract void SetFromString(string Value);
}
My mapping profile is still pretty vanilla since I couldn't wrap my head around the proper ForMember syntax.
public class ContactMappingProfile : Profile
{
public ContactMappingProfile()
{
// This map works fine
CreateMap<ContactEntity, ContactDto>();
// Map from DTO to Entity
CreateMap<ContactDto, ContactEntity>()
.ForMember(dest => dest.CustomerNumbers,
opt => opt.ResolveUsing(src => src.CustomerNumbers));
}
}
Thanks again for any help you can provide!
You could use either UseDestinationValue or Ignore for destination entity CustomerNumbers member and perform the actual mapping in AfterMap:
cfg.CreateMap<ContactDto, ContactEntity>()
.ForMember(dest => dest.CustomerNumbers, opt => opt.Ignore())
.AfterMap((src, dest) => dest.CustomerNumbers.SetFromString(string.Join(",", src.CustomerNumbers)));

How to map empty strings as null with automapper?

I need to map empty strings from source model as null to destination model.
At first I used next profile for this:
public class MyProfile:Profile
{
public MyProfile()
{
CreateMap<SrcModel, DestModel>()
.ForMember(dst => dst.Field1, opt =>
{
opt.Condition(src => !string.IsNullOrEmpty(src.src_Field1));
opt.MapFrom(src => src.src_Field1)
})
//.......
//same for other 15 fields
}
}
But duplicating same logic looks not very good and it's hard to modify it.
Also I have tried to create special map for string like this:
CreateMap<string, string>().ConvertUsing(src => string.IsNullOrEmpty(src) ?
null : src)
But such string map has impact on all my maps, but I need such logic only for several maps, not for all.
I also have tried to use ForAllMembers method:
... .ForAllMembers(opt => opt.Condition();
But there is no way do define type of source member, to cpecify some condition for strings.
What is the best way to define some common mapping logic for several members of same type for one map?
Just duplicate the logic, the most I'd do is extract the Condition part into an extension method you can call.

Collate two collections using Automapper

I am looking around for a tool to automate the collation of two collections into each other and I think Automapper should work for this. We have many instances of this operation and I would like to centralize this logic into a single area.
I have the following two classes:
public class Product
{
public IEnumerable<Order> CurrentCustomerOrders { get;set; }
}
public class Order
{
order properties
}
And they are retrieved via the following calls:
_repo.GetTable<Product>();
_repo.GetTable<Order>().Where(n => n.CustomerId = _customerId);
What I want is to put all the Orders into the Products or something like this:
Mapper.CreateMap<IEnumerable<Order>, IEnumerable<Product>>()
.ForEachMember(n => n.CurrentCustomerOrders), opt => opt.MapFrom(p => p.Where(Order.ProductId == Product.ProductId))
How would I go about doing this using Automapper? Or do you know of a better tool to do this?
Thanks!
Usually you would do the filtering first using Linq or something and then do the mapping. Automapper is just a mapping tool.
e.g.
Product.CurrentCustomerOrders =
Mapper.Map<OrderDO, Order>(Orders.Where(o => o.ProductId == Product.ProductId));

Entity Framework + AutoMapper ( Entity to DTO and DTO to Entity )

I've got some problems using EF with AutoMapper. =/
for example :
I've got 2 related entities ( Customers and Orders )
and they're DTO classes :
class CustomerDTO
{
public string CustomerID {get;set;}
public string CustomerName {get;set;}
public IList< OrderDTO > Orders {get;set;}
}
class OrderDTO
{
public string OrderID {get;set;}
public string OrderDetails {get;set;}
public CustomerDTO Customers {get;set;}
}
//when mapping Entity to DTO the code works
Customers cust = getCustomer(id);
Mapper.CreateMap< Customers, CustomerDTO >();
Mapper.CreateMap< Orders, OrderDTO >();
CustomerDTO custDTO = Mapper.Map(cust);
//but when i try to map back from DTO to Entity it fails with AutoMapperMappingException.
Mapper.Reset();
Mapper.CreateMap< CustomerDTO , Customers >();
Mapper.CreateMap< OrderDTO , Orders >();
Customers customerModel = Mapper.Map< CustomerDTO ,Customers >(custDTO); // exception is thrown here
Am I doing something wrong?
Thanks in Advance !
The problem I had was related to updates to EntityCollection references. AutoMapper creates a new instance of the relation when mapping from the DTO to the Entity, and that doesn't please the EF.
What solved my problem was configuring AutoMapper to use the destination value for my EntityCollection properties. In your case:
Mapper.CreateMap< CustomerDTO , Customers >().ForMember(c => c.Orders, o => o.UseDestinationValue());
That way AM will not create a new EntityCollection instance, and will use that wich came with the original Customer entity.
I'm still working for a way to automate this, but for now it solves my problem.
Try mapping to an existing object:
entity = Mapper.Map<MyDTO, NyEntity>(dto, entity);
And keep the Ignore()'s in place.
http://groups.google.com/group/automapper-users/browse_thread/thread/24a90f22323a27bc?fwc=1&pli=1
Your problem is because Automapper loses the EntityKey associated with the record. As the EntityFramework does not by default handle POCO's (Plain Old CLR Object)
Jay Zimmerman has a good example here of how to handle this from is. gd /4NIcj
Also from Jaroslaw Kowalski (part of the EF team I believe ) has this example for using POCO's within EF, which may translate well to use with Automapper (I've not yet had a chance to try it) : http://blogs.msdn.com/jkowalski/archive/2008/09/09/persistence-ignorance-poco-adapter-for-entity-framework-v1.aspx
I'm not sure what your problem is, but - when i wanted to use LINQToEntities (switched to NHibernate),
i managed to use automapper with success.
Take a look at code:
public class SimpleMapper<TFrom, TTo>
{
public static TTo Map(TFrom fromModel)
{
Mapper.CreateMap<TFrom, TTo>();
return Mapper.Map<TFrom, TTo>(fromModel);
}
public static IList<TTo> MapList(IList<TFrom> fromModel)
{
Mapper.CreateMap<TFrom, TTo>();
return Mapper.Map<IList<TFrom>, IList<TTo>>(fromModel);
}
}
public class RepositoryBase<TModel, TLINQModel>
{
public IList<TModel> Map<TCustom>(IList<TCustom> model)
{
return SimpleMapper<TCustom, TModel>.MapList(model);
}
public TModel Map(TLINQModel model)
{
return SimpleMapper<TLINQModel, TModel>.Map(model);
}
public TLINQModel Map(TModel model)
{
return SimpleMapper<TModel, TLINQModel>.Map(model);
}
public IList<TModel> Map(IList<TLINQModel> model)
{
return SimpleMapper<TLINQModel, TModel>.MapList(model);
}
public IList<TLINQModel> Map(IList<TModel> model)
{
return SimpleMapper<TModel, TLINQModel>.MapList(model);
}
}
It's quite cryptic, always recreates mappings, but it worked. I hope it helps somehow. :)
Now, with new version of AutoMapper, the recommended way is using Queryable-Extensions:
When using an ORM such as NHibernate or Entity Framework with
AutoMapper's standard Mapper.Map functions, you may notice that the
ORM will query all the fields of all the objects within a graph when
AutoMapper is attempting to map the results to a destination type.
If your ORM exposes IQueryables, you can use AutoMapper's
QueryableExtensions helper methods to address this key pain.
The .ProjectTo() will tell AutoMapper's mapping engine
to emit a select clause to the IQueryable that will inform entity
framework that it only needs to query the Name column of the Item
table, same as if you manually projected your IQueryable to an
OrderLineDTO with a Select clause.
Create a mapping:
Mapper.CreateMap<Customer, CustomerDto>();
And project query to dto:
var customerDto =
session.Query<Customer>().Where(customer => customer.Id == id)
.Project().To<CustomerDto>()
.Single();
AutoMapper is very expressive when it comes to mapping error. read the exception message carefully.
another important thing is to remember to call Mapper.AssertConfigurationIsValid(); after creating the mappings. it gives an error if the mapping is wrong, thus preventing an exception later in the application runtime.
You should ignore mapping of some entity properties like so:
Mapper.CreateMap<CustomerDto, Customer>()
.ForMember(dest => dest.EntityKey, opt => opt.Ignore())
.ForMember(dest => dest.Licenses, opt => opt.Ignore())
.ForMember(dest => dest.AccessCodes, opt => opt.Ignore());
If you examine the message from the exception thrown by Automapper, you should see the entity properties that cannot be mapped and ignore them as above.
As you can read here you need to do the following
You can update entities with AutoMapper. Here's how: pass both the DTO and the entity object to AutoMapper's Map method. That's what this code does:
custExisting = Mapper.Map(Of CustomerDTO, Customer)(custDTO, custExisting)
Also beware of mapping issues like the one described here
These tips worked for me.

Categories

Resources