I have the following Entity-Models
public class Blog
{
public int Id { get; set;}
public string Title { get; set; }
public string Body { get; set; }
[ForeignKey("Category")]
public int? CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Category
{
public int Id { get; set;}
public string Name { get; set; }
}
public class Comment
{
public int Id { get; set;}
public string Title { get; set; }
public string Body { get; set; }
[ForeignKey("Blog")]
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
Then I have the following view-model in which I like to tell AutoMapper to map the Blog object into the BlogViewModel notice the CategoryName property will need to come from Blog.Category.Name and each Comment in the Blog.Comments need to be converter to CommentViewModel using the organic convention.
I currently set the mapping at run time using reflection for any class that implements the ICustomMap interface. Please read the comment in the code over the Transfer(IMapper mapper) method.
public class BlogViewModel : ICustomMapFrom
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string MyCatName { get; set; }
public IEnumerable<CommentViewModel> Comments { get; set; }
// **IMPORTANT NOTE**
// This method is called using reflection when the on Application_Start() method.
// If IMapper is the wrong Interface to pass, I can change
// the implementation of ICustomMap
// I assumed that `IMapper` is what is needed to add configuration at runtime.
public void Transfer(IConfigurationProvider config)
{
// How to I do the custom mapping for my MyCatName and Comments?
// I need to use the config to create the complex mapping
// AutoMapper.Mapper.Map(typeof(Blog), typeof(BlogViewModel));
}
}
Finally here is my CommentViewModel
public class CommentViewModel : IMapFrom<Comment>
{
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}
How can I tell AutoMapper how to map the CategoryName and the Comments?
Updated
Here is how I would create the mapping. I would have the following 3 interfaces
public interface IMap
{
}
public interface IMapFrom<T> : IMap
{
}
public interface ICustomMapFrom : IMap
{
void Map(IConfigurationProvider config);
}
Then in the Global.cs file
I would execute the Run method on startup. Basically this method will scan assemblies and register the classes that I would want to register using the interfaces.
public class ConfigureAutoMapper
{
public void Run()
{
var types = AssemblyHelpers.GetInternalAssemblies()
.SelectMany(x => x.GetTypes())
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && typeof(IMap).IsAssignableFrom(x))
.ToList();
RegisterStandardMappings(types);
RegisterCustomMappings(types);
}
private static void RegisterStandardMappings(IEnumerable<Type> types)
{
foreach (Type type in types)
{
if(type.IsGenericType && typeof(IMapFrom<>).IsAssignableFrom(type))
{
AutoMapper.Mapper.Map(type.GetGenericArguments()[0], type);
}
}
}
private static void RegisterCustomMappings(IEnumerable<Type> types)
{
foreach (Type type in types)
{
if (typeof(ICustomMapFrom).IsAssignableFrom(type))
{
ICustomMapFrom map = (ICustomMapFrom)Activator.CreateInstance(type);
var t = AutoMapper.Mapper.Configuration;
map.Map(Mapper.Configuration);
}
}
}
}
I wrote an NUnit test which sets up AutoMapper with your classes. AutoMapper supports the mapping of CategoryName out of the box.
[TestFixture]
public class TestClass
{
[Test]
public void Test1()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Blog, BlogViewModel>();
});
config.AssertConfigurationIsValid();
var blog = new Blog()
{
Body = "Blog body",
Category = new Category { Name = "My Category" },
Comments = new List<Comment>() {
new Comment { Body = "Comment body 1" },
new Comment { Body = "Comment body 2" }
}
};
var mapper = config.CreateMapper();
var result = mapper.Map<Blog, BlogViewModel>(blog);
Assert.AreEqual(blog.Body, "Blog body");
Assert.AreEqual(blog.Category.Name, result.CategoryName);
List<CommentViewModel> comments = result.Comments.ToList();
Assert.That(comments.Any(c => c.Body == "Comment body 1"));
Assert.That(comments.Any(c => c.Body == "Comment body 2"));
}
}
Related
I have a problem when using AutoMapper and EF Core together to map navigation properties from the model to the DTO. My EF classes are:
public class Meal
{
public DateTime Day { get; set; }
public MealType MealType { get; set; }
public int MealId { get; set; }
}
public class MealType
{
public string Name { get; set; }
public int MealTypeId { get; set; }
}
And the corresponding DTO classes:
public class ExistingMealDto
{
public DateTime Day { get; set; }
public ExistingMealTypeDto MealType { get; set; }
public int MealId { get; set; }
public string MealTypeName { get; set; }
}
public class ExistingMealTypeDto
{
public string Name { get; set; }
public int MealTypeId { get; set; }
}
This is my AutoMapper mapping:
config.CreateMap<DataLayer.EfClasses.MealType, ExistingMealTypeDto>();
config.CreateMap<DataLayer.EfClasses.Meal, ExistingMealDto>()
.ForMember(x => x.MealType, x => x.MapFrom(x=>x.MealType))
.ForMember(x => x.MealTypeName, x => x.MapFrom(y => y.MealType.Name));
I'm loading the data within a generic method that looks like this:
public IEnumerable<TDtoOut> GetAllAsDto<TIn, TDtoOut>()
where TIn : class
{
var allEntities = DbContext.Set<TIn>();
return Mapper.Map<IEnumerable<TDtoOut>>(allEntities);
}
When calling this code, all Meal instances are loaded from the database and MealId and Day are filled correctly. However, MealType is null and therefore ExistingMealDto.MealType is null as well. I can work around this problem by explicitly calling DbContext.MealTypes.ToList(), but since the method should be generic for TIn, this is not a production solution.
How can I solve this issue? Thanks!
For getting the related data in generic method , you can judge the Type of the passed type. The following is a test demo , you could refer to:
public IEnumerable<TIn> GetAllAsDto<TIn>()
where TIn : class
{
Type typeParameterType = typeof(TIn);
if (typeParameterType == typeof(User))
{
var Entities = _context.Set<User>().Include(u=>u.Orders);
return (IEnumerable<TIn>)Entities;
}
else
{
var allEntities = _context.Set<TIn>();
return allEntities;
}
}
public void Test()
{
var data = GetAllAsDto<User>();
var data1 = GetAllAsDto<Status>();
}
Result
I have defined a mapping from one type to DTO. Another type references the first type as an property, but the output should be an flattened DTO that should use the already defined mapping for the first type.
class Program {
static void Main(string[] args) {
var mapperConfiguration = new MapperConfiguration(cfg => {
cfg.CreateMap<FirstDataType,
FirstTypeDto>().ForMember(d => d.TypeResult, opt => opt.MapFrom(s => s.ToString()));
/* HOW TO CONFIGURE MAPPING OF THE 'FirstData' PROPERTY TO USE THE ABOVE DEFINED MAPPING
cfg.CreateMap<SecondDataType, SecondTypeDto>()
*/
});
var firstData = new FirstDataType {
TypeName = "TestType",
TypeValue = "TestValue"
};
var secondData = new SecondDataType {
Id = 1,
Name = "Second type",
FirstData = firstData
};
var mapper = mapperConfiguration.CreateMapper();
var firstDto = mapper.Map<FirstTypeDto>(firstData);
var secondDto = mapper.Map<SecondTypeDto>(secondData);
Console.ReadKey(true);
}
}
public class FirstDataType {
public string TypeName {
get;
set;
}
public string TypeValue {
get;
set;
}
public override string ToString() {
return $ "{TypeName}: {TypeValue}";
}
}
public class SecondDataType {
public int Id {
get;
set;
}
public string Name {
get;
set;
}
public FirstDataType FirstData {
get;
set;
}
}
public class FirstTypeDto {
public string TypeName {
get;
set;
}
public string TypeValue {
get;
set;
}
public string TypeResult {
get;
set;
}
}
public class SecondTypeDto: FirstTypeDto {
public int Id {
get;
set;
}
public string Name {
get;
set;
}
}
How should I configure mapping for the second type to use the defined mapping for the property 'FirstData'?
Thanks!
First, credit goes to Lucian Bargaoanu for leading me in the right direction.
Basically, you need to create a mapping from the source to the destination derived type, but just include the existing mapping.
cfg.CreateMap<FirstDataType, SecondTypeDto>()
.IncludeBase<FirstDataType, FirstTypeDto>()
.ReverseMap();
cfg.CreateMap<SecondDataType, SecondTypeDto>()
.IncludeMembers(s => s.FirstData)
.ReverseMap();
The error message that I'm receiving at runtime is:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
List'1 -> MobileRoot (Destination member list)
System.Collections.Generic.List'1[[Strata.CS.Jazz.Biz.Dashboard.MobileInfo, Strata.CS.Jazz.Biz, Version=2019.10.0.0, Culture=neutral, PublicKeyToken=null]] -> Strata.Jazz.Web.Controllers.MobileController+MobileRoot (Destination member list)
Unmapped properties:
Users
From what I can tell from the error message is that AutoMapper needs to know how to handle the ForMember Users create in the MobileRoot, and then propagate that for each of the subsequent lists down the chain. Can anyone tell me how to do this efficiently using AutoMapper? I know how to do this with Linq using GroupBy and Select, so it is my thought that this should be do-able with AutoMapper.
The query I have returns this class:
public class MobileInfo
{
public string NameFull { get; set; }
public string EmailAddress { get; set; }
public string SolutionName { get; set; }
public int SortOrder { get; set; }
public string Name { get; set; }
public bool IsLegacy { get; set; }
public string Description { get; set; }
public string WidgetName { get; set; }
public int Row { get; set; }
public int Col { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public string WidgetClassName { get; set; }
public string Data { get; set; }
}
I would like to use Automapper with profiles to have it return this:
internal class MobileRoot
{
public IEnumerable<MobileUser> Users { get; set; }
}
internal class MobileUser
{
public string NameFull { get; set; }
public string EmailAddress { get; set; }
public IEnumerable<MobileSolution> Solutions { get; set; }
}
internal class MobileSolution
{
public string Solution { get; set; } // MobileInfo.SolutionName
public int SortOrder { get; set; }
public IEnumerable<MobileDashboard> Dashboards { get; set; }
}
internal class MobileDashboard
{
public string Dashboard { get; set; } // MobileInfo.Name
public bool IsLegacy { get; set; }
public string Description { get; set; }
public IEnumerable<MobileWidget> Widgets { get; set; }
}
internal class MobileWidget
{
public string Widget { get; set; } // MobileInfo.WidgetName
public int Row { get; set; }
public int Col { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public string WidgetClassName { get; set; }
public string Data { get; set; }
}
The Profiles I have defined so far are:
public class ProfileMobileRoot : Profile
{
public ProfileMobileRoot()
{
CreateMap<MobileInfo, MobileRoot>();
}
}
public class ProfileMobileUser : Profile
{
public ProfileMobileUser()
{
CreateMap<MobileInfo, MobileUser>();
}
}
public class ProfileMobileSolution : Profile
{
public ProfileMobileSolution()
{
CreateMap<MobileInfo, MobileSolution>();
}
}
public class ProfileMobileDashboard : Profile
{
public ProfileMobileDashboard()
{
CreateMap<MobileInfo, MobileRoot>();
}
}
public class ProfileMobileWidget : Profile
{
public ProfileMobileWidget()
{
CreateMap<MobileInfo, MobileWidget>();
}
}
You can do something like below. It's a little late so my solution isn't so sophisticated... but it works ;)
public class ProfileMobileRoot : Profile
{
public ProfileMobileRoot()
{
CreateMap<MobileInfo, MobileWidget>()
.ForMember(x=>x.Name, opt=>opt.MapFrom(x=>x.WidgetName));
CreateMap<IEnumerable<MobileInfo>, IEnumerable<MobileDashboard>>()
.ConvertUsing<DashboardConverter>();
CreateMap<IEnumerable<MobileInfo>, IEnumerable<MobileSolution>>()
.ConvertUsing<SolutionConverter>();
CreateMap<IEnumerable<MobileInfo>, IEnumerable<MobileUser>>()
.ConvertUsing<UserConverter>();
CreateMap<IEnumerable<MobileInfo>, MobileRoot>()
.ForMember(x => x.Users, opt => opt.MapFrom(x => x.ToList()));
}
}
class UserConverter : ITypeConverter<IEnumerable<MobileInfo>, IEnumerable<MobileUser>>
{
public IEnumerable<MobileUser> Convert(IEnumerable<MobileInfo> source, IEnumerable<MobileUser> destination, ResolutionContext context)
{
var groups = source.GroupBy(x => new { x.NameFull, x.EmailAddress});
foreach (var v in groups)
{
yield return new MobileUser()
{
EmailAddress = v.Key.EmailAddress,
NameFull = v.Key.NameFull,
Solutions = context.Mapper.Map<IEnumerable<MobileSolution>>(source.Where(x =>
v.Key.NameFull == x.NameFull && v.Key.EmailAddress== x.EmailAddress)).ToList()
};
}
}
}
class SolutionConverter : ITypeConverter<IEnumerable<MobileInfo>, IEnumerable<MobileSolution>>
{
public IEnumerable<MobileSolution> Convert(IEnumerable<MobileInfo> source, IEnumerable<MobileSolution> destination, ResolutionContext context)
{
var groups = source.GroupBy(x => new { x.SolutionName, x.SortOrder});
foreach (var v in groups)
{
yield return new MobileSolution()
{
Solution = v.Key.SolutionName,
SortOrder = v.Key.SortOrder,
Dashboards= context.Mapper.Map<IEnumerable<MobileDashboard>>(source.Where(x =>
v.Key.SolutionName== x.SolutionName&& v.Key.SortOrder== x.SortOrder)).ToList()
};
}
}
}
class DashboardConverter : ITypeConverter<IEnumerable<MobileInfo>, IEnumerable<MobileDashboard>>
{
public IEnumerable<MobileDashboard> Convert(IEnumerable<MobileInfo> source, IEnumerable<MobileDashboard> destination, ResolutionContext context)
{
var groups = source.GroupBy(x => new {x.Name, x.IsLegacy, x.Description});
foreach (var v in groups)
{
yield return new MobileDashboard()
{
Dashboard = v.Key.Name,
Description = v.Key.Description,
IsLegacy = v.Key.IsLegacy,
Widgets = context.Mapper.Map<IEnumerable<MobileWidget>>(source.Where(x =>
v.Key.IsLegacy == x.IsLegacy && v.Key.Name == x.Name && v.Key.Description == x.Description))
};
}
}
}
I'm at loss here. I want to refactor a part of the code that uses no abstract classes. I'm familiar with json2csharp. That converts a JSON file to the C# classes so it can be easily deserialized.
Is there a similar site/tool that accepts as input several C# classes and generates basic abstract classes based on those?
This would make the refactoring easier as I don't need to create all the different abstract classes.
Very simple example:
Input:
public class TestClass1
{
public string TestID { get; set; }
public string TestName { get; set; }
public int TestValue1 { get; set; }
public TestClass1()
{
}
}
public class TestClass2
{
public string TestID { get; set; }
public string TestName { get; set; }
public int TestValue2 { get; set; }
public TestClass2()
{
}
}
Output:
public abstract class ATestClass
{
public string TestID { get; set; }
public string TestName { get; set; }
protected ATestClass()
{
}
}
You can get something working pretty quickly if you use the Roslyn code analysis and code generation. Here’s a quick example how that could work. Note that this is somewhat fragile with detecting common properties since its based on the syntax instead of the actual semantics (making string Foo and String Foo incompatible properties). But for code that is actually generated by another code generator, this should work fine since the input should be consistent.
var input = #"
public class TestClass1
{
public string TestID { get; set; }
public string TestName { get; set; }
public string OtherTest { get; set; }
public int TestValue1 { get; set; }
public TestClass1()
{
}
}
public class TestClass2
{
public string TestID { get; set; }
public string TestName { get; set; }
public int OtherTest { get; set; }
public int TestValue2 { get; set; }
public TestClass2()
{
}
}";
// parse input
var tree = CSharpSyntaxTree.ParseText(input);
// find class declarations and look up properties
var classes = tree.GetCompilationUnitRoot().ChildNodes()
.OfType<ClassDeclarationSyntax>()
.Select(cls => (declaration: cls, properties: cls.ChildNodes().OfType<PropertyDeclarationSyntax>().ToDictionary(pd => pd.Identifier.ValueText)))
.ToList();
// find common property names
var propertySets = classes.Select(x => new HashSet<string>(x.properties.Keys));
var commonPropertyNames = propertySets.First();
foreach (var propertySet in propertySets.Skip(1))
{
commonPropertyNames.IntersectWith(propertySet);
}
// verify that the property declarations are equivalent
var commonProperties = commonPropertyNames
.Select(name => (name, prop: classes[0].properties[name]))
.Where(cp =>
{
foreach (var cls in classes)
{
// this is not really a good way since this just syntactically compares the values
if (!cls.properties[cp.name].IsEquivalentTo(cp.prop))
return false;
}
return true;
}).ToList();
// start code generation
var workspace = new AdhocWorkspace();
var syntaxGenerator = SyntaxGenerator.GetGenerator(workspace, LanguageNames.CSharp);
// create base class with common properties
var baseClassDeclaration = syntaxGenerator.ClassDeclaration("BaseClass",
accessibility: Accessibility.Public,
modifiers: DeclarationModifiers.Abstract,
members: commonProperties.Select(cp => cp.prop));
var declarations = new List<SyntaxNode> { baseClassDeclaration };
// adjust input class declarations
commonPropertyNames = new HashSet<string>(commonProperties.Select(cp => cp.name));
foreach (var cls in classes)
{
var propertiesToRemove = cls.properties.Where(prop => commonPropertyNames.Contains(prop.Key)).Select(prop => prop.Value);
var declaration = cls.declaration.RemoveNodes(propertiesToRemove, SyntaxRemoveOptions.KeepNoTrivia);
declarations.Add(syntaxGenerator.AddBaseType(declaration, syntaxGenerator.IdentifierName("BaseClass")));
}
// create output
var compilationUnit = syntaxGenerator.CompilationUnit(declarations);
using (var writer = new StringWriter())
{
compilationUnit.NormalizeWhitespace().WriteTo(writer);
Console.WriteLine(writer.ToString());
}
This would generate the following output:
public abstract class BaseClass
{
public string TestID
{
get;
set;
}
public string TestName
{
get;
set;
}
}
public class TestClass1 : BaseClass
{
public string OtherTest
{
get;
set;
}
public int TestValue1
{
get;
set;
}
public TestClass1()
{
}
}
public class TestClass2 : BaseClass
{
public int OtherTest
{
get;
set;
}
public int TestValue2
{
get;
set;
}
public TestClass2()
{
}
}
I have created classes using EF Code First that have collections of each other.
Entities:
public class Field
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<AppUser> Teachers { get; set; }
public Field()
{
Teachers = new List<AppUser>();
}
}
public class AppUser
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public virtual List<Field> Fields { get; set; }
public AppUser()
{
Fields = new List<FieldDTO>();
}
}
DTOs:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<AppUserDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<AppUserDTO>();
}
}
public class AppUserDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
Mappings:
Mapper.CreateMap<Field, FieldDTO>();
Mapper.CreateMap<FieldDTO, Field>();
Mapper.CreateMap<AppUserDTO, AppUser>();
Mapper.CreateMap<AppUser, AppUserDTO>();
And I am getting StackOverflowException when calling this code (Context is my dbContext):
protected override IQueryable<FieldDTO> GetQueryable()
{
IQueryable<Field> query = Context.Fields;
return query.ProjectTo<FieldDTO>();//exception thrown here
}
I guess this happens because it loops in Lists calling each other endlessly. But I do not understand why this happens. Are my mappings wrong?
You have self-referencing entities AND self-referencing DTOs. Generally speaking self-referencing DTOs are a bad idea. Especially when doing a projection - EF does not know how to join together and join together and join together a hierarchy of items.
You have two choices.
First, you can force a specific depth of hierarchy by explicitly modeling your DTOs with a hierarchy in mind:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<TeacherDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<TeacherDTO>();
}
}
public class TeacherDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
}
public class AppUserDTO : TeacherDTO
{
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
This is the preferred way, as it's the most obvious and explicit.
The less obvious, less explicit way is to configure AutoMapper to have a maximum depth it will go to traverse hierarchical relationships:
CreateMap<AppUser, AppUserDTO>().MaxDepth(3);
I prefer to go #1 because it's the most easily understood, but #2 works as well.
Other option is using PreserveReferences() method.
CreateMap<AppUser, AppUserDTO>().PreserveReferences();
I use this generic method:
public static TTarget Convert<TSource, TTarget>(TSource sourceItem)
{
if (null == sourceItem)
{
return default(TTarget);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings);
return JsonConvert.DeserializeObject<TTarget>(serializedObject);
}
...
MapperConfiguration(cfg =>
{
cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...
When you giving 1 navigation_property to 2nd entity and visa-versa it go in an infinite loop state. So, the compiler automatically throws a Stackoverflow exception.
So, to avoid that, you just need to remove one navigation_property from any of the entities.