Who doesn't like Automapper, it makes our life easier. But it is not recommended to use Automapper with entity framework (again you can use Queryable-Extensions as well). What Automapper does is, it going through each and every property and mapping your data from source object to the destination object, when we dealing with entity framework (if you using navigation properties) Automapper tries to map navigation properties as well. Of course you can specify the properties you want to map and not to map but that sucks.
So what we going to do is, skip all the mapping for the navigation properties. How we are going to do that ? yes all the navigation properties are virtual by default. So we can skip mapping all the virtual properties for that we can write an extension, here it goes ,
public static class IgnoreVirtualAndUnMappedExtensions
{
public static IMappingExpression<TSource, TDestination>
IgnoreAllVirtual<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression, string[] includeList)
{
var desType = typeof(TDestination);
var sourceType = typeof(TSource);
List<string> desTypepropNames = desType.GetProperties().Select(a => a.Name).ToList();
foreach (var property in sourceType.GetProperties().Where(p =>
p.GetGetMethod().IsVirtual))
{
if (!desTypepropNames.Contains(property.Name))
expression.ForSourceMember(property.Name, opt => opt.Ignore());
else if (!includeList.Contains(property.Name))
expression.ForMember(property.Name, opt => opt.Ignore());
}
return expression;
}
}
(still I'm working on with include list as well but it is not working with circular reference )
So my full AutoMapperHelper class goes like this,
public static class AutoMapperHelper
{
private static IMapper GetMapper<Source, Destination>(string[] includeList)
{
if (includeList == null)
includeList = new string[0];
var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>().IgnoreAllVirtual(includeList));
return config.CreateMapper();
}
public static Destination Map<Source, Destination>(Source data, params string[] includeList)
{
var mapper = GetMapper<Source, Destination>(includeList);
return mapper.Map<Destination>(data);
}
public static void Map<Source, Destination>(Source data, Destination toData, params string[] includeList) // when mapping single objects
{
var mapper = GetMapper<Source, Destination>(includeList);
toData = mapper.Map(data, toData);
}
public static List<Destination> MapToList<Source, Destination>(ICollection<Source> data, params string[] includeList) // when mapping list of objects
{
var mapper = GetMapper<Source, Destination>(includeList);
return mapper.Map<List<Destination>>(data);
}
public static void MapToList<Source, Destination>(ICollection<Source> data, ICollection<Destination> todata, params string[] includeList) // when mapping list of objects
{
var mapper = GetMapper<Source, Destination>(includeList);
todata = mapper.Map(data, todata);
}
}
Below is an example of how we can use this helper class, with this scenario I'm getting a user object by id
EF user class goes like this :
public partial class User
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public User()
{
}
public long Id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public Nullable<int> group_id { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
DTO user class goes like this :
public class UserDTO{
public User()
{
}
public long Id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public int group_id { get; set; }
public UserGroup UserGroup { get; set; }
}
And the function goes like this :
{
var userDataService = new UserDataService();// data access service
var user = userDataService.GetUserByID(id); // getting the data (returns EF's User class)
var dtoUser = AutomaticMapperHelper.Map<DAL.User, UserDTO >(user);
// DAL.User = EF's User class ,User = DTO class for user
if (dtoUser != null)
dtoUser .UserGroup = AutomaticMapperHelper.Map<DAL.UserGroup, UserGroup>(user.UserGroup); // we skip the mapping for virtual properties so we have to map the property again. (this is getting from the user object so we don't have to query the DB again.)
return dtoUser ;
}
Here is the data access code for your reference,
public User GetUserByID(long id)
{
using (Unixmo_MainEntities dbcontext = new Unixmo_MainEntities())
{
return dbcontext.Users.Include(a=>a.UserGroup).SingleOrDefault(a => a.Id == id);
}
}
Hope you find this useful, Happy coding.