نگاشت خودکار اشیاء توسط AutoMapper و Reflection - ایده شماره 2
نویسنده: محمد جواد ابراهیمی
تاریخ: ۱۳۹۷/۱۱/۰۳ ۱:۵۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
public interface IHaveCustomMapping
{
void CreateMappings(AutoMapper.Profile profile);
} public class PostDtoMapping : IHaveCustomMapping
{
public void CreateMappings(Profile profile)
{
profile.CreateMap<PostDto, Post>().ReverseMap();
}
} public class CustomMappingProfile : Profile
{
public CustomMappingProfile(IEnumerable<IHaveCustomMapping> haveCustomMappings)
{
foreach (var item in haveCustomMappings)
item.CreateMappings(this);
}
} public static class AutoMapperConfiguration
{
public static void InitializeAutoMapper()
{
Mapper.Initialize(config =>
{
config.AddCustomMappingProfile();
});
//Compile mapping after configuration to boost map speed
Mapper.Configuration.CompileMappings();
}
public static void AddCustomMappingProfile(this IMapperConfigurationExpression config)
{
config.AddCustomMappingProfile(Assembly.GetEntryAssembly());
}
public static void AddCustomMappingProfile(this IMapperConfigurationExpression config, params Assembly[] assemblies)
{
var allTypes = assemblies.SelectMany(a => a.ExportedTypes);
//Find all classes that implement IHaveCustomMapping inteface and create new instance of each
var list = allTypes.Where(type => type.IsClass && !type.IsAbstract &&
type.GetInterfaces().Contains(typeof(IHaveCustomMapping)))
.Select(type => (IHaveCustomMapping)Activator.CreateInstance(type));
//Create a new automapper Profile for this list to create mapping then add to the config
var profile = new CustomMappingProfile(list);
config.AddProfile(profile);
}
} public abstract class BaseDto<TDto, TEntity, TKey> : IHaveCustomMapping
where TEntity : BaseEntity<TKey>
{
[Display(Name = "ردیف")]
public TKey Id { get; set; }
/// <summary>
/// Maps this dto to a new entity object.
/// </summary>
public TEntity ToEntity()
{
return Mapper.Map<TEntity>(CastToDerivedClass(this));
}
/// <summary>
/// Maps this dto to an exist entity object.
/// </summary>
public TEntity ToEntity(TEntity entity)
{
return Mapper.Map(CastToDerivedClass(this), entity);
}
/// <summary>
/// Maps the specified entity to a new dto object.
/// </summary>
public static TDto FromEntity(TEntity model)
{
return Mapper.Map<TDto>(model);
}
protected TDto CastToDerivedClass(BaseDto<TDto, TEntity, TKey> baseInstance)
{
return Mapper.Map<TDto>(baseInstance);
}
//Get automapper Profile then create mapping and ignore unmapped properties
public void CreateMappings(Profile profile)
{
var mappingExpression = profile.CreateMap<TDto, TEntity>();
var dtoType = typeof(TDto);
var entityType = typeof(TEntity);
//Ignore mapping to any property of source (like Post.Categroy) that dose not contains in destination (like PostDto)
//To prevent from wrong mapping. for example in mapping of "PostDto -> Post", automapper create a new instance for Category (with null catgeoryName) because we have CategoryName property that has null value
foreach (var property in entityType.GetProperties())
{
if (dtoType.GetProperty(property.Name) == null)
mappingExpression.ForMember(property.Name, opt => opt.Ignore());
}
//Pass mapping expressin to customize mapping in concrete class
CustomMappings(mappingExpression.ReverseMap());
}
//Concrete class can override this method to customize mapping
public virtual void CustomMappings(IMappingExpression<TEntity, TDto> mapping)
{
}
} public class PostDto : BaseDto<PostDto, Post, long>
{
public string Title { get; set; }
public string Text { get; set; }
public int CategoryId { get; set; }
public string CategoryName { get; set; } //=> Category.Name
public string FullTitle { get; set; } //=> custom mapping for "Title (Category.Name)"
public override void CustomMappings(IMappingExpression<Post, PostDto> mapping)
{
mapping.ForMember(
dest => dest.FullTitle,
config => config.MapFrom(src => $"{src.Title} ({src.Category.Name})"));
}
} List<PostDto> list =
//ProjectTo method select only needed properties (of PostDto) not all properties
//Also select only needed property of navigations (like Post.Category.Name) not all unlike Include
//This ability called "Projection"
await _applicationDbContext.Posts.ProjectTo<PostDto>()
//We can also use Where on IQuerable<PostDto>
.Where(p => p.Title.Contains("test") || p.CategoryName.Contains("test"))
.ToListAsync();