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();
Related
In my project I'm trying to use automapper to unflatten my command objects to my domain objects by convention as much as possible.
It works when I explicitly map the two members in the mapping profile, but according to the automapper documentation I think this should also work by convention.
I created a dotnetfiddle to demonstrate the minimal case.
Related questions end up with people explicitly adding the mapping, but that kind of goes against what Automapper is built for and contradicts the documentation, no?
It doesn't work with flattening either, so the reversemap is a red herring I think.
The mapping
public class Mapping: Profile
{
public Mapping()
{
this.CreateMap<CreateSelectionCommand, Selection>();
// .ForMember(selection => selection.Name, opt => opt.MapFrom(x => x.SelectionName))
.reverseMap()
}
}
What I expect to work
[Fact]
public void ShouldMapName()
{
var cmd = new CreateSelectionCommand {SelectionName = "selectionName"};
var selection = _mapper.Map<Selection>(cmd);
Assert.Equal(cmd.SelectionName, selection.Name); <== selection.Name == ""
}
Classes for context
public class Selection
{
public string Name { get; set; }
}
public class CreateSelectionCommand
{
public string SelectionName { get; set; }
}
Did I misread the docs or am I missing something?
Flattening is about mapping nested "complex" objects to properties on "higher" level i.e. in your case if CreateSelectionCommand had property Selection of type which had Name property it would be mapped to SelectionName in destination type (see this fiddle).
You can try to use prefixes by adding:
cfg.RecognizePrefixes("Selection");
to your configuration (see this fiddle) but I doubt that it is suitable option for convention based handling.
Also it seems that you can add custom name convention using ISourceToDestinationNameMapper and AddMemberConfiguration:
class TypeNamePrefixedSourceToDestinationNameMapper : ISourceToDestinationNameMapper
{
public MemberInfo GetMatchingMemberInfo(IGetTypeInfoMembers getTypeInfoMembers, TypeDetails typeInfo,
Type destType,
Type destMemberType, string nameToSearch)
{
return getTypeInfoMembers.GetMemberInfos(typeInfo)
.FirstOrDefault(mi => mi.Name == destType.Name + nameToSearch);
}
}
var config = new MapperConfiguration(cfg =>
{
cfg.AddMemberConfiguration().AddName<TypeNamePrefixedSourceToDestinationNameMapper>();
// ...
}
At least it works in this simple case, see this fiddle.
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.
TL;DR
Is AsEnumerable() on an IQueryable() safer to use (as in, does it already execute as ToList()) to have a workaround (see below) for the error automapper Only parameterless constructors and initializers are supported in LINQ to Entities.?
In other words what is the effect of using AsEnumerable() on IQueryable() especially when chaining a Where() to it for example.
Please read below for full context and info.
Long Version
I'm implementing an abstraction for my repository layer, as I have to be able to read data from JSON files, XML files and also EntityFramework (database).
Problem description
I was confronted when projecting my EF entities with the error automapper Only parameterless constructors and initializers are supported in LINQ to Entities. when executing code like this:
public IEnumerable<Person> All() {
return _dataContext
.People
.Select(p => new Person(p.Id, p.FirstName, p.LastName));
}
As a reference, this is my DbContext, so you see that the above _dataContext.People returns an IQueryable<EFPerson>:
public class EFDataContext : DbContext
{
public IDbSet<EFPerson> People { get; set; }
public EFDataContext()
: this(Settings.Default.EFDataContextConnectionString) { }
public EFDataContext(string nameOrConnectionString)
: this(() => nameOrConnectionString) { }
public EFDataContext(Func<string> connectionStringProvider)
: base(connectionStringProvider()) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<EFPerson>()
.HasKey(p => p.Id)
.ToTable(Settings.Default.PeopleTable);
}
}
My solution
I didn't want to use AutoMapper, I also didn't want to make my domain entities have setters - as they needed to be immutable/read-only for the business model I'm writing.
The solution I came up with was using .AsEnumerable() and then project with the constructor of my domain entity:
public IEnumerable<Person> All() {
return _dataContext
.People
.AsEnumerable()
.Select(p => new Person(p.Id, p.FirstName, p.LastName));
}
The code runs quick, and I can also do .Where projections afterwards on the domain entity. I think this is safe as my understanding is that .AsEnumerable isn't evaluated immediately like .ToList would be.
Question Recap
My Questions thus is, is my assumption true. Is it a safe workaround to do this, or should I model it differenly - either using AutoMapper or write lengthier logic in my EntityFramework implementation of the service layer / repository?
As your question is quite wide, I'll describe my solution:
Use your domain entities in Entity Framework:
public class EFDataContext : DbContext
{
public IDbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Person>()
.HasKey(p => p.Id)
.ToTable(Settings.Default.PeopleTable);
}
}
Design your entities as you need:
public class Person
{
private Person() //for EF
{
}
public Person(string name) //for me
{
Name = name;
}
public int Id { get; private set; }
public string Name { get; private set; }
public string LastName { get; private set; }
}
Query:
public IEnumerable<Person> All() {
return _dataContext
.People
.AsEnumerable();
}
Why I use AsEnumerable here? Just to hide my database and it's IQueryable.
As you can see, EF allows to work with domain objects.
I'm currently attempting to use Entity Framework's ChangeTracker for auditing purposes. I'm overriding the SaveChanges() method in my DbContext and creating logs for entities that have been added, modified, or deleted. Here is the code for that FWIW:
public override int SaveChanges()
{
var validStates = new EntityState[] { EntityState.Added, EntityState.Modified, EntityState.Deleted };
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && validStates.Contains(x.State));
var entriesToAudit = new Dictionary<object, EntityState>();
foreach (var entity in entities)
{
entriesToAudit.Add(entity.Entity, entity.State);
}
//Save entries first so the IDs of new records will be populated
var result = base.SaveChanges();
createAuditLogs(entriesToAudit, entityRelationshipsToAudit, changeUserId);
return result;
}
This works great for "normal" entities. For simple many-to-many relationships, however, I had to extend this implementation to include "Independent Associations" as described in this fantastic SO answer which accesses changes via the ObjectContext like so:
private static IEnumerable<EntityRelationship> GetRelationships(this DbContext context, EntityState relationshipState, Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => new EntityRelationship(
e.EntitySet.Name,
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
Once implemented, this also worked great, but only for many-to-many relationships that use a junction table. By this, I'm referring to a situation where the relationship is not represented by a class/entity, but only a database table with two columns - one for each foreign key.
There are certain many-to-many relationships in my data model, however, where the relationship has "behavior" (properties). In this example, ProgramGroup is the many-to-many relationship which has a Pin property:
public class Program
{
public int ProgramId { get; set; }
public List<ProgramGroup> ProgramGroups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public IList<ProgramGroup> ProgramGroups { get; set; }
}
public class ProgramGroup
{
public int ProgramGroupId { get; set; }
public int ProgramId { get; set; }
public int GroupId { get; set; }
public string Pin { get; set; }
}
In this situation, I'm not seeing a change to a ProgramGroup (eg. if the Pin is changed) in either the "normal" DbContext ChangeTracker, nor the ObjectContext relationship method. As I step through the code, though, I can see that the change is in the ObjectContext's StateEntries, but it's entry has IsRelationship=false which, of course, fails the .Where(e => e.IsRelationship) condition.
My question is why is a many-to-many relationship with behavior not appearing in the normal DbContext ChangeTracker since it's represented by an actual class/entity and why is it not marked as a relationship in the ObjectContext StateEntries? Also, what is the best practice for accessing these type of changes?
Thanks in advance.
EDIT:
In response to #FrancescCastells's comment that perhaps not explicitly defining a configuration for the ProgramGroup is cause of the problem, I added the following configuration:
public class ProgramGroupConfiguration : EntityTypeConfiguration<ProgramGroup>
{
public ProgramGroupConfiguration()
{
ToTable("ProgramGroups");
HasKey(p => p.ProgramGroupId);
Property(p => p.ProgramGroupId).IsRequired();
Property(p => p.ProgramId).IsRequired();
Property(p => p.GroupId).IsRequired();
Property(p => p.Pin).HasMaxLength(50).IsRequired();
}
And here are my other configurations:
public class ProgramConfiguration : EntityTypeConfiguration<Program>
{
public ProgramConfiguration()
{
ToTable("Programs");
HasKey(p => p.ProgramId);
Property(p => p.ProgramId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Program).HasForeignKey(p => p.ProgramId);
}
}
public class GroupConfiguration : EntityTypeConfiguration<Group>
{
public GroupConfiguration()
{
ToTable("Groups");
HasKey(p => p.GroupId);
Property(p => p.GroupId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Group).HasForeignKey(p => p.GroupId);
}
When these are implemented, EF still does not show the modified ProgramGroup in the ChangeTracker.
While the concept of "relationship with attributes" is mentioned in the theory of entity-relationship modelling, as far as Entity Framework is concerned, your ProgramGroup class is an entity. You're probably unwittingly filtering it out with the x.Entity is BaseEntity check in the first code snippet.
I believe the problem lies in the definition of your Program and Group class and overridden SaveChanges method. With the current definition of the classes the EF is unable to use change tracking proxies, that catch changes as they are being made. Instead of that the EF relies on the snapshot change detection, that is done as part of SaveChanges method. Since you call base.SaveChanges() at the end of the overridden method, the changes are not detected yet when you request them from ChangeTracker.
You have two options - you can either call ChangeTracker.DetectChanges(); at the beginning of the SaveChanges method or change definition of your classes to support change tracking proxies.
public class Program {
public int ProgramId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
public class Group {
public int GroupId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
The basic requirements for creating change tracking proxies are:
A class must be declared as public
A class must not be sealed
A class must not be abstract
A class must have a public or protected constructor that does not have parameters.
A navigation property that represents the "many" end of a relationship must have public virtual get and set accessors
A navigation property that represents the "many" end of a relationship must be defined as ICollection<T>
Entity Framework represents many-to-many relationships by not having entityset for the joining table in CSDL, instead it manages this through mapping.
Note: Entity framework supports many-to-many relationship only when the joining table does NOT include any columns other than PKs of both the tables
you should have to define navigation property yourself to coupe with this proplem.
this link can be of your help.
Given these two objects
public class UserModel
{
public string Name {get;set;}
public IList<RoleModel> Roles {get;set;}
}
public class UserViewModel
{
public string Name {get;set;}
public IList<RoleViewModel> Roles {get;set;} // notice the ViewModel
}
Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?
App Config
Mapper.CreateMap<UserModel, UserViewModel>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Mapper.CreateMap<UserViewModel, UserModel>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Implementation
_userRepository.Create(Mapper.Map<UserModel>(someUserViewModelWithRolesAttached);
Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?
If the property names are identical, you should not have to manually provide a mapping:
Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Just make sure the inner types are mapped as well (RoleViewModel ↔ RoleModel)
What this means, however, is that if you change a source or destination property name, AutoMapper mappings can fail silently and cause hard to track down problems (e.g., if you changed UserModel.Roles to UserModel.RolesCollection without changing UserViewModels.Roles).
AutoMapper provides a Mapper.AssertConfigurationIsValid() method that will check all of your mappings for errors and catch misconfigured mappings. It's useful to have a unit test that runs with the build that validates your mappings for this kind of problem.
You don't need to map the properties. Just make sure that the property names match and there is a mapping defined between them.
Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Mapper.CreateMap<RoleModel, RoleViewModel>();
Mapper.CreateMap<RoleViewModel, RoleModel>();
Or with the cooler way I just found out:
Mapper.CreateMap<UserModel, UserViewModel>().ReverseMap();
Mapper.CreateMap<RoleModel, RoleViewModel>().ReverseMap();
All the other answers, are much better (which I gave an upvote to each).
But what I wanted to post here is a quick playground that you could copy and past right into LinqPad in C# program mode and play your idea's without messing with your actual code.
Another awesome thing about moving all your conversions into a TyperConverter class is that your conversions are now Unit Testable. :)
Here you will notice that the model and viewmodel are almost identical except for one property. But through this process the right property is converted to the correct property in the destination object.
Copy this code into LinqPad and you can run it with the play button after switching to C# Program mode.
void Main()
{
AutoMapper.Mapper.CreateMap<UserModel, UserViewModel>().ConvertUsing(new UserModelToUserViewModelConverter());
AutoMapper.Mapper.AssertConfigurationIsValid();
var userModel = new UserModel
{
DifferentPropertyName = "Batman",
Name = "RockStar",
Roles = new[] {new RoleModel(), new RoleModel() }
};
var userViewModel = AutoMapper.Mapper.Map<UserViewModel>(userModel);
Console.WriteLine(userViewModel.ToString());
}
// Define other methods and classes here
public class UserModel
{
public string Name {get;set;}
public IEnumerable<RoleModel> Roles { get; set; }
public string DifferentPropertyName { get; set; }
}
public class UserViewModel
{
public string Name {get;set;}
public IEnumerable<RoleModel> Roles { get; set; } // notice the ViewModel
public string Thingy { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("Name: {0}", Name));
sb.AppendLine(string.Format("Thingy: {0}", Thingy));
sb.AppendLine(string.Format("Contains #{0} of roles", Roles.Count()));
return sb.ToString();
}
}
public class UserModelToUserViewModelConverter : TypeConverter<UserModel, UserViewModel>
{
protected override UserViewModel ConvertCore(UserModel source)
{
if(source == null)
{
return null;
}
//You can add logic here to deal with nulls, empty strings, empty objects etc
var userViewModel = new UserViewModel
{
Name = source.Name,
Roles = source.Roles,
Thingy = source.DifferentPropertyName
};
return userViewModel;
}
}
public class RoleModel
{
//no content for ease, plus this has it's own mapper in real life
}
Result from the Console.WriteLine(userViewModel.ToString());:
Name: RockStar
Thingy: Batman
Contains #2 of roles
Inside the Startup.cs in the Configure() method:
Mapper.Initialize(config => {
config.CreateMap<UserModel, UserViewModel>().ReverseMap();
// other maps you want to do.
});