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

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)));

Related

Using private fields with the Cassandra C# driver

I'm trying to migrate one of my modules from Postgres (with EF) to Cassandra.
Here is my best try for Cassandra mappings:
internal sealed class UserMappings : Mappings
{
public UserMappings()
{
For<User>().TableName("users")
.PartitionKey(x => x.Id)
.ClusteringKey(x => x.Id)
.Column(x => x.Id, x => x.WithDbType<Guid>().WithName("id"))
// I want to add mappings for password Hash here
}
}
The first problem is that I use VO for completive safety but want to store primitives in database.
Example VO for entity id:
public record UserId
{
public Guid Value { get; }
public UserId(Guid value)
{
if (value == Guid.Empty) throw new Exception("Invalid UserId");
Value = value;
}
public static implicit operator Guid(UserId id) => id.Value;
public static implicit operator UserId(Guid id) => new(id);
}
Secondly, my entity has private fields and I don't know how to map them to the database.
internal class User
{
private User()
{
}
public User(/*...*/)
{
//...
}
private string _passwordHash;
public UserId Id { get; }
//...
}
Also is public parameterless constructor required?
It sounds like you want to have some business logic in the classes that you are using to map to your database. I would recommend creating new classes that have only public properties and no logic whatsoever (i.e. POCOs) and then mapping these objects into your domain objects either "manually" or with a library like AutoMapper. This has the benefit of keeping your domain objects separate from the database schema.
The DataStax C# driver mapper will not be able to map private fields or properties that don't have a setter. It is able to map properties with a private setter though so you might want to leverage that instead.
Also keep in mind that you will need to provide a custom TypeConverter to the Mapper or Table objects if you use custom types in your mapping. You might get away with not implementing a TypeConveter if you have implicit operators to convert these types (like your UserId class) but I'm not 100% sure.
On the constructor issue, I think having a private empty constructor is enough.

Automapper Dynamic Resolver's

I'm trying to create an abstract layer on top of automapper which enables users to dynamically add custom rules to each property they map.
Given the Model
public class Entity
{
public int Index { get; set; }
}
public class DTO
{
public int Count { get; set; }
}
we may configure Automapper to map the entities like so:
//sorry this is pseudo coded
cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Index,
opt => opt.ResolveUsing<IndexResolver>());
public class IndexResolver: ValueResolver<DTO, int>,
{
protected override string ResolveCore(DTO source)
{
return source.Count - 1;
}
}
This works since we map the rule to a Value resolver, However if I wanted to create a rule at run time is that possible. I would like to be able to configure things like so:
cfg.CreateMap<Entity, DTO>()
.ForMember(dest => dest.Index,
opt => opt.Resolver(d => d.Count - 1);
Is there a way I can Add a resolver with an expression so I do not need to inherit from ValueResolver?
My first thought is to create a generic custom resolver that takes an expression in its constructor. You should then be able to do:
.ResolveUsing(new LambdaResolver(d => d.Count - 1))

Automapper Mapping of simple object to more complex nested type

I am in the process of a refactor and utilizing AutoMapper (6.2.1) to help us in formatting of API returns that conform to a specific contract. The DTO objects we are using internally are meant to simplify our understanding of the data before returning the data in the more complex type.
The Issue:
I have a DTO with a List<T> where I need one of the properties of <T> to be mapped to the collection in the more complex type. This is actually pretty straight forward but the problem is, what if the collection I am trying to map to in the more complex type is in fact inside another "higher" collection. Essentially I am in a little bit of a collection inside a collection problem.
Ex: DTO
public class ItemDTO
{
List<ItemDescriptionDTO> ItemDescriptions { get; set; }
}
public class ItemDescriptionDTO
{
public string Description { get; set; }
}
More Complex object I need to map to and do not have control over
public class ComplexThing // This is the object I need (It's ugly, I hate it too)
{
public ComplexItemDescriptions { get; set; }
}
public class ComplexItemDescriptions
{
public List<ComplexItemDescription> ComplexItemDescription { get; set; }
}
public class ComplexItemDescription
{
public UnparsedItemDescriptions UnparsedItemDescriptions { get; set; }
}
public class UnparsedItemDescriptions
{
public List<UnparsedItemDescription> UnparsedItemDescription { get; set; }
}
public class UnparsedItemDescription
{
public string UnparsedItemDescription { get; set; }
}
In essence I need to take the Description in my simple ItemDescriptionDTOand map that through this awful chain of nested objects to set the UnparsedItemDescription
I am able to properly map from UnparsedItemDescription all the way to the ComplexItemDescription but going higher than that to the Complex thing is giving me some trouble.
This is the mapping I have so far:
config.CreateMap<ItemDescriptionDTO, UnparsedItemDescription>()
.ForMember(dest => dest.UnparsedItemDescription, map => map.MapFrom(src => src.Description));
config.CreateMap<ItemDTO, UnparsedItemDescriptions>()
.ForMember(dest => dest.UnparsedItemDescription, map => map.MapFrom(src => src.ItemDescritpions));
config.CreateMap<ItemDTO, ComplexItemDescription>()
.ForPath(dest => dest.UnparsedItemDescriptions.UnparsedItemDescription, map => map.MapFrom(src => src.ItemDescriptions));
// This is where we start failing (I think I am just not understanding something fundamental to how Automapper does things or I am up against some silly edge case
config.CreateMap<ItemDTO, ComplexItemDescriptions>()
.ForMember(dest => dest.ComplexItemDescription, map => map.MapFrom(src => src.ItemDescriptions));
I need the ComplexThing because there are other properties in that class that are returned so I can't just get away with returning say a ComplexItemDescription
I would appreciate an assistance you could give. I admittedly have a base understanding on how Automapper works (which I am in the process of trying to get better at) but this is really throwing me at the moment.

Automapper projection (EF) with encapsulated child collections

I use Automapper to map from EF entities to view models.
I now have this entity
public class MenuGroup : IEntity
{
public int MenuGroupId { get; set; }
protected ICollection<MenuGroupItem> _menuGroupItems { get; set; }
public IEnumerable<MenuGroupItem> MenuGroupItems { get { return _menuGroupItems; } }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
_menuGroupItems.Add(menuGroupItem);
}
}
That is an encapsulated collection, I followed instructions here to make this work: http://lostechies.com/jimmybogard/2014/05/09/missing-ef-feature-workarounds-encapsulated-collections/
So I configure it like so this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
Now the problem I get is when I try to use automapper to map my MenuGroup into a viewmodel.
I run this code: menuGroup = _context.MenuGroups.Project().To<MenuGroupEditModel>().Single(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName);
and get this error: The specified type member 'MenuGroupItems' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Now I can work with the collection, it saves correctly to the database and all is well there it's only when i want to user automapper here that it fails.
If I replace the protected ICollection and public IEnumerable with simply: public ICollection<MenuGroupItem> MenuGroupItems { get; set; } it works right away so the problem lies in automapping with my encapsulated collection.
Update: I also tried this menuGroup = _context.MenuGroups.Include(x => x.MenuGroupItems).Where(x => x.UniqueUrlFriendlyName == request.UniqueUrlFriendlyName).Project().ToSingleOrDefault<MenuGroupEditModel>(); with no difference other than that it errored in the ToSingleOrDefault instead.
Your problem is that Automapper can't modify MenuGroupItems because there is no public setter.
Your solution is changing it to this:
public IEnumerable<MenuGroupItem> MenuGroupItems { get; set; }
public void AddMenuItem(MenuGroupItem menuGroupItem)
{
MenuGroupItems.Add(menuGroupItem);
}
After some more debugging I figured out the Config file looking like this
public MenuGroupConfiguration()
{
this.HasMany(x => x.MenuGroupAssigments).WithRequired(x => x.BelongTo).WillCascadeOnDelete(true);
this.HasMany(x => x.MenuGroupItems).WithRequired(x => x.BelongsTo).WillCascadeOnDelete(true);
}
had not been included leading to that error that now makes sense.
I can add as a general tip that if you don't use auto-mapper for a query but still use your encapsulated collection remember that you have to call decompile for it to work.
like so
var menuGroupsWithType =
_context.MenuGroups.Include(x => x.MenuGroupItems).Include(x => x.MenuGroupAssigments).Where(x => x.MenuGroupAssigments.Any(y => y.AssignToAll == selectedStructureType))
.OrderBy(x => x.Name).Decompile().ToList();

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