Use IEntityTypeConfiguration with a base entity - c#

In EF Core 2.0, we have the ability to derive from IEntityTypeConfiguration for cleaner Fluent API mappings (source).
How can I extend this pattern to utilize a base entity? In the example below, how can I have a BaseEntityConfiguration to reduce duplication in LanguageConfiguration and MaintainerConfiguration, modifying properties that are in the BaseEntity only in the BaseEntityConfiguration? What would such a BaseEntityConfiguration look like; and how would it be used, if at all, in OnModelCreating()? See the TODOs in-code near the end of the example.
Example:
public abstract class BaseEntity
{
public long Id { get; set; }
public DateTime CreatedDateUtc { get; set; }
public DateTime? ModifiedDateUtc { get; set; }
}
public class Language : BaseEntity
{
public string Iso6392 { get; set; }
public string LocalName { get; set; }
public string Name { get; set; }
}
public class Maintainer : BaseEntity
{
public string Email { get; set; }
public string Name { get; set; }
}
public class FilterListsDbContext : DbContext
{
public FilterListsDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Language> Languages { get; set; }
public DbSet<Maintainer> Maintainers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//TODO: Possibly add something like BaseEntityConfiguration?
modelBuilder.ApplyConfiguration(new LanguageConfiguration());
modelBuilder.ApplyConfiguration(new MaintainerConfiguration());
}
}
public class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
public void Configure(EntityTypeBuilder<Language> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
public class MaintainerConfiguration : IEntityTypeConfiguration<Maintainer>
{
public void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}

Something like this could work (untested)?
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
}
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
base.Configure(entityTypeBuilder);
}
}

There is another way to solve the problem, and that is to use Template Method Design Pattern. Like this:
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
ConfigureOtherProperties(builder);
}
public abstract void ConfigureOtherProperties(EntityTypeBuilder<TEntity> builder);
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void ConfigureOtherProperties(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
With this way you don't need to write any single line in child configuration.

Another approach if you dont want to repeat the column Definitions for all of your Models that inherit from the same base Entity like this:
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Order>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
modelBuilder.Entity<Adress>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
// …
}
is to find all the Entites that inhert from the base Entity, loop over them and call the generic Method as shown below, in which the redundant Logic is placed:
protected override void OnModelCreating(ModelBuilder modelBuilder){
foreach (Type type in GetEntityTypes(typeof(BaseEntity))){
var method = SetGlobalQueryMethod.MakeGenericMethod(type);
method.Invoke(this, new object[] { modelBuilder });
}
}
static readonly MethodInfo SetGlobalQueryMethod = typeof(/*your*/Context)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(t => t.IsGenericMethod && t.Name == "SetGlobalQuery");
public void SetGlobalQuery<T>(ModelBuilder builder) where T : BaseEntity{
builder.Entity<T>().Property(o => o.CreatedDateTime).HasDefaultValueSql("CURRENT_TIMESTAMP");
// Additional Statements
}
For the "GetEntityTypes" Method you need the Nuget Package „Microsoft.Extensions.DependencyModel“
private static IList<Type> _entityTypeCache;
private static IList<Type> GetEntityTypes(Type type)
{
if (_entityTypeCache != null && _entityTypeCache.First().BaseType == type)
{
return _entityTypeCache.ToList();
}
_entityTypeCache = (from a in GetReferencingAssemblies()
from t in a.DefinedTypes
where t.BaseType == type
select t.AsType()).ToList();
return _entityTypeCache;
}
private static IEnumerable<Assembly> GetReferencingAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
catch (FileNotFoundException)
{ }
}
return assemblies;
}
Its a bit hacky in my opinion, but works fine for me!
The source with more details:
https://www.codingame.com/playgrounds/5514/multi-tenant-asp-net-core-4---applying-tenant-rules-to-all-enitites

I'm late to the party, but this is what I did in the OnModelCreating method to achieve similar results.
Basically, I have (4) properties that inherit from a BaseEntity. Two of those are dates why two are strings.
For the dates, I wanted the default to be SQL's GETUTCDATE and the string to be "SystemGenerated." Using a static helper that allows me to retrieve the property name from BaseEntity in a strongly-typed manner, I grab the (4) property names. Then, I iterate over all of the iterate over all of the ModelBuilder entities after my primary mappings are set-up. This allows modelBuilder.Model.GetEntityTypes to return the entities that the modelBuidler is aware of. Then it's a matter of looking at the ClrType.BaseType to see if the type inherits from my BaseEntity and setting the defaults on the PropertyBuilder.
I tested this directly and through EF Migrations which confirmed that the proper SQL was generated.
var createdAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedAtUtc);
var lastModifiedAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedAtUtc);
var createdBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedBy);
var lastModifiedBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedBy);
foreach (var t in modelBuilder.Model.GetEntityTypes())
{
if (t.ClrType.BaseType == typeof(BaseEntity))
{
modelBuilder.Entity(t.ClrType).Property(createdAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(lastModifiedAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(createdBy).HasDefaultValueSql("SystemGenerated");
modelBuilder.Entity(t.ClrType).Property(lastModifiedBy).HasDefaultValueSql("SystemGenerated");
}
}
Here is the the static helper for getting property names for a given type..
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
if (expression.Body is MemberExpression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
else
{
var op = ((UnaryExpression)expression.Body).Operand;
return ((MemberExpression)op).Member.Name;
}
}

Related

How to separate entity's primary key from base class?

I want to accomplish clean architecture with EF Core.
I trid to separate primary key from base class, but it failed with below exception.
System.InvalidOperationException: 'A key cannot be configured on 'ManagerEntity' because it is a derived type. The key must be configured on the root type 'Manager'. If you did not intend for 'Manager' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.'
Core
public class Manager
{
public Manager(Guid identifier, string email)
{
Identifier = identifier;
Email = email;
}
public Guid Identifier { get; }
public string Email { get; }
public void FixPrinter(Printer printer)
{
printer.IsOutOfControl = true;
}
}
public class Printer
{
public Printer(Guid token)
{
Token = token;
Manager = null;
IsOutOfControl = false;
}
public Guid Token { get; }
public Manager? Manager { get; set; }
public bool IsOutOfControl { get; set; }
}
Infrastructure
public class ApplicationContext
: DbContext
{
// ...
public DbSet<ManagerEntity> ManagerSet { get; set; }
public DbSet<PrinterEntity> PrinterSet { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new ManagerEntityConfiguration(Database));
modelBuilder.ApplyConfiguration(new PrinterEntityConfiguration(Database));
}
}
Configure Manager
public sealed class ManagerEntity
: Manager
{
public ManagerEntity(string email)
: base(Guid.Empty, email)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class ManagerEntityConfiguration
: IEntityTypeConfiguration<ManagerEntity>
{
private readonly DatabaseFacade _database;
public ManagerEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<ManagerEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
// The exception occurs here.
builder
.HasKey(e => e.Id);
// ...
}
}
Configure Printer
public sealed class PrinterEntity
: Printer
{
public PrinterEntity()
: base(Guid.Empty)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class PrinterEntityConfiguration
: IEntityTypeConfiguration<PrinterEntity>
{
private readonly DatabaseFacade _database;
public PrinterEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<PrinterEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
builder
.HasKey(e => e.Id);
// ...
}
}
Web API
app.MapPost("/printer", async (ApplicationContext context) =>
{
PrinterEntity printer = new()
{
Manager = new ManagerEntity("master#google.com"),
};
await context.PrinterSet.AddAsync(printer);
await context.SaveChangesAsync();
return printer;
});
Should I architect it by using interface, not inheritance?
At Core,
public interface IPrinter
{
public Manager? Manager { get; set; }
}
At Infrastructure,
public sealed class PrinterEntity : IPrinter
{
// ...
}
Github source code
Thanks, #roji!
https://github.com/dotnet/efcore/issues/27421#issuecomment-1034762908
This is likely happening because you're mapping both Manager and ManagerEntity in your model, which means you're configuring inheritance mapping (i.e. EF thinks you intend to store both Manager and ManagerEntity instances in the database). With inheritance mapping, the key must be specified at the root.
However, it seems like you only want the class separation on the .NET side, without needing any actual hierarchy, so make sure you are not mapping the base class (Manager in the above). See the minimal code sample below.
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
public class BlogContext : DbContext
{
// Uncomment the below to make the exception appear
// public DbSet<Manager> Managers { get; set; }
public DbSet<ManagerEntity> ManagerEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(#"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ManagerEntity>().HasKey(b => b.Id);
}
}
public class Manager
{
public string Name { get; set; }
}
public class ManagerEntity : Manager
{
public int Id { get; set; }
}
However... if the goal is simply not to expose an Id property on Manager, there are simpler ways to do that rather than introducing a .NET hierarchy. You can have a private _id field instead, which would be used by EF Core but not otherwise exposed in your application, keeping your data model clean (see docs). Alternatively, you can have an Id shadow property, removing the field/property from your CLR type altogether.

Applying entity configurations in a foreach loop

I was wondering if someone might have an idea how to apply entity configuration dynamically. I don't want to repeat let's say builder.ApplyConfiguration(new PersonConfiguration()); for each entity I have/might have in the future.
I was trying with the following code and for the sake of simplicity I have placed everything into a single file:
namespace WebApplication1.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(p => p.GetTypes())
.Where(p => typeof(IEntity).IsAssignableFrom(p) && p != typeof(IEntity));
foreach (var entityType in entityTypes)
{
// this doesn't work
//builder.ApplyConfiguration(new BaseEntityConfiguration<entityType>());
}
// this works, but I don't want to basically repeat it for each entity individually
//builder.ApplyConfiguration(new BaseEntityConfiguration<Person>());
//builder.ApplyConfiguration(new PersonConfiguration());
}
public virtual DbSet<Person> People { get; set; }
}
public interface IEntity
{
int Id { get; set; }
string RowModifyUser { get; set; }
DateTime RowModifyDate { get; set; }
byte[] RowVersion { get; set; }
}
public class Person : IEntity
{
public int Id { get; set; }
public string RowModifyUser { get; set; }
public DateTime RowModifyDate { get; set; }
public byte[] RowVersion { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class, IEntity
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.RowModifyDate).IsRequired();
builder.Property(m => m.RowModifyUser).IsRequired();
builder.Property(m => m.RowVersion).IsRequired().IsConcurrencyToken();
}
}
public class PersonConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.Property(m => m.FirstName).IsRequired();
builder.Property(m => m.LastName).IsRequired();
}
}
}
In addition to creating a generic BaseEntityConfiguration<TEntity> class as suggested in another answer, you also need to call a generic ModelBuilder.ApplyConfiguration<TEntity>(IEntityTypeConfiguration<TEntity> configuration) method via reflection.
Something like this (needs using System.Reflection;):
// Can be moved to a static readonly field of the class
var applyConfigurationMethodDefinition = typeof(ModelBuilder)
.GetTypeInfo()
.DeclaredMethods
.Single(m => m.Name == "ApplyConfiguration" &&
m.IsGenericMethodDefinition &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType.IsGenericType &&
m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>));
foreach (var entityType in entityTypes)
{
var configurationType = typeof(BaseEntityConfiguration<>).MakeGenericType(entityType);
var configuration = Activator.CreateIntance(configurationType);
var applyConfigurationMethod = applyConfigurationMethodDefinition.MakeGenericMethod(entityType);
applyConfigurationMethod.Invoke(builder, new object[] { configuration });
}
Note that in EF Core 2.1 ModelBuilder class has 2 ApplyConfiguration method overloads which differ only by the type of the parameter, that's why finding the method includes all the checks.
Untested, but you could try the following:
foreach (Type entityType in entityTypes)
{
Type openConfigType = typeof(BaseEntityConfiguration<>);
Type genericConfigType = openConfigType.MakeGenericType(entityType);
builder.ApplyConfiguration(Activator.CreateInstance(genericConfigType));
}

Why does EntityFramework 6 not support explicitly filtering by Discriminator?

The following is a small EF6 Program to demonstrate the issue.
public abstract class Base
{
public int Id { get; set; }
public abstract int TypeId { get; }
}
public class SubA : Base
{
public override int TypeId => 1;
}
public class SubAA : SubA
{
public override int TypeId => 2;
}
public class SubB : Base
{
public override int TypeId => 3;
}
public class SubC : Base
{
public override int TypeId => 4;
}
public class DevartContext : DbContext
{
public virtual DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Base>()
.Map<SubA>(x => x.Requires(nameof(SubA.TypeId)).HasValue(1))
.Map<SubAA>(x => x.Requires(nameof(SubAA.TypeId)).HasValue(2))
.Map<SubB>(x => x.Requires(nameof(SubB.TypeId)).HasValue(3))
.Map<SubC>(x => x.Requires(nameof(SubC.TypeId)).HasValue(4));
}
}
public class Program
{
public static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
}
The jist of it is this: We have a TPH-Model with an explicitly configured Discriminator (TypeId in this case). We then try to query a specific subtype using that TypeId because using the is operator in our hypothetical example would also return SubAAs, not just SubAs.
I could obviously modify the above to something like Where(x => x is SubA && !(x is SubAA)) but this is obviously going to break as soon as I add SubAB, and automating this by building an exact-filter-linq-to-entities-helper-method is obviously really slow because that method has to do a decent amount of reflection. Not to mention that the generated SQL of the above is horrendous because EF/My SQL Provider does not optimize it properly.
Now trying to do the above results in a NotSupportedException being thrown when the query gets materialized, which basically states that because TypeId is not a member of the Entity, I cannot use it for filtering.
I went to look around for ways to circumvent this, but the best thing I could find was a snippet for automatically generating the Where(x => x is SubA && !(x is SubAA)) version to solve the problem, which is likely to be what I will have to do to get around this.
So my question is: Why does EntityFramework not support this?
This soultion working excatly as you wish donot change anything ^^ "never change a running system" :)
You can use enum instead of integers, this give your code more type safety!
static void Main(string[] args)
{
using (DevartContext ctx = new DevartContext())
{
// prevent model-changes from wrecking the test
ctx.Database.Delete();
ctx.Database.Create();
ctx.Bases.Add(new SubA());
ctx.Bases.Add(new SubAA());
ctx.Bases.Add(new SubB());
ctx.SaveChanges();
var result = ctx.Bases.Where(x => x.TypeId == 1);
// throws on materialization, why?
foreach (var entry in result)
{
Console.WriteLine(entry);
}
}
Console.ReadLine();
}
public abstract class Base
{
public int Id { get; set; }
public virtual int TypeId { get; protected set; }
}
public class SubA : Base
{
public override int TypeId { get;protected set; } = 1;
}
public class SubAA : SubA
{
public override int TypeId { get; protected set; } = 2;
}
public class SubB : Base
{
public override int TypeId { get; protected set; } = 3;
}
public class SubC : Base
{
public override int TypeId { get; protected set; } = 4;
}
public class DevartContext : DbContext
{
public DbSet<Base> Bases { get; set; }
public DevartContext()
{
}
}
Result in DB:
Id TypeId Discriminator
1 1 SubA
2 2 SubAA
3 3 SubB

How to exclude all virtual properties from classes inheritted from ClassMapper<T> by default?

I have a lot of POCO classes that contain several virtual properties each. Something like this:
public class Policy
{
public int Id { get; set; }
public int EntityId { get; set; }
public int ProgramId { get; set; }
public string PolicyNumber { get; set; }
public DateTime EffectiveDate { get; set; }
public DateTime ExpirationDate { get; set; }
public virtual Entity Entity{ get; set; }
public virtual Program Program { get; set; }
public virtual ICollection<Transaction> Transactions { get; set; }
}
To make Dapper.Extensions work, I need to write a mapping for each of these classes, which is fine. My problem is, if there are any virtual properties inside a class, they need to be explicitly marked as ignored, which I always forget to do.
public sealed class PolicyMapper : BaseMapper<Policy>
{
public PolicyMapper()
{
Map(p => p.Entity).Ignore();
Map(p => p.Program).Ignore();
Map(p => p.Transactions).Ignore();
AutoMap();
}
}
What would be great for me, if the Dapper.Extensions library will automatically exclude virtual properties, if any, when mapped to the POCO class. There is an extension for Automapper that does something similar (link). Is there a way to do that for Dapper.Extensions library? Possibly something like this:
public sealed class PolicyMapper : BaseMapper<Policy>
{
public PolicyMapper()
{
IgnoreAllVirtual();
AutoMap();
}
}
I found my own solution. Since all my mapping classes derive from BaseMapper class, I decided to override AutoMap() method that will exclude virtual properties:
public class BaseMapper<T> : ClassMapper<T> where T : BaseClass
{
public BaseMapper()
{
}
protected override void AutoMap()
{
CustomAutoMap(null);
}
private void CustomAutoMap(Func<Type, PropertyInfo, bool> canMap)
{
Type type = typeof(T);
bool hasDefinedKey = Properties.Any(p => p.KeyType != KeyType.NotAKey);
PropertyMap keyMap = null;
foreach (var propertyInfo in type.GetProperties())
{
// Exclude virtual properties
if (propertyInfo.GetGetMethod().IsVirtual)
{
continue;
}
if (Properties.Any(p => p.Name.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
if ((canMap != null && !canMap(type, propertyInfo)))
{
continue;
}
PropertyMap map = Map(propertyInfo);
if (!hasDefinedKey)
{
if (string.Equals(map.PropertyInfo.Name, "id", StringComparison.InvariantCultureIgnoreCase))
{
keyMap = map;
}
if (keyMap == null && map.PropertyInfo.Name.EndsWith("id", true, CultureInfo.InvariantCulture))
{
keyMap = map;
}
}
}
if (keyMap != null)
{
keyMap.Key(PropertyTypeKeyTypeMapping.ContainsKey(keyMap.PropertyInfo.PropertyType)
? PropertyTypeKeyTypeMapping[keyMap.PropertyInfo.PropertyType]
: KeyType.Assigned);
}
}
}
}

DTO to Entity Mapping Tool

I have an entity class Person and its corresponding DTO class PersonDto.
public class Person: Entity
{
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual Sex Sex { get; set; }
public virtual Position Position { get; set; }
public virtual Division Division { get; set; }
public virtual Organization Organization { get; set; }
}
public class PersonDto: Dto
{
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public Guid SexId { get; set; }
public Guid PositionId { get; set; }
public Guid DivisionId { get; set; }
public Guid OrganizationId { get; set; }
}
After receiving a DTO object I have to convert it into a person entity. Now I do it completely manually. The code looks like this.
public class PersonEntityMapper: IEntityMapper<Person, PersonDto>
{
private IRepository<Person> _personRepository;
private IRepository<Sex> _sexRepository;
private IRepository<Position> _positionRepository;
private IRepository<Division> _divisionRepository;
private IRepository<Organization> _organizationRepository;
public PersonEntityMapper(IRepository<Person> personRepository,
IRepository<Sex> sexRepository,
IRepository<Position> positionRepository,
IRepository<Division> divisionRepository,
IRepository<Organization> organizationRepository)
{
... // Assigning repositories
}
Person Map(PersonDto dto)
{
Person person = CreateOrLoadPerson(dto);
person.Name = dto.Name;
person.Phone = dto.Phone;
person.Email = dto.Email;
person.Sex = _sexRepository.LoadById(dto.SexId);
person.Position = _positionRepository.LoadById(dto.PositionId);
person.Division = _divisionRepository.LoadById(dto.DivisionId);
person.Organization = _organizationRepository.LoadById(dto.OrganizationId);
return person;
}
}
The code is in fact trivial. But as the number of entities grows so does the number of mapper classes. The result is lots of similar code. Another issue is that when there are mode associations I have to add constructor parameteres for additional repositories. I tried to inject a some kind of a repository factory instead, but it smelled a bad-known Service Locator so I reverted to an original solution.
Unit testing of these mappers also results in a number of similar-looking test methods.
With all this been said I wonder if there exists a solution that can reduce the amount of manually written code and make the unit testing easier.
Thanks in advance.
UPDATE
I'd accomplished the task with Value Injecter but then I realized that I could safely remove it and the rest would still work. Here is the resulting solution.
public abstract class BaseEntityMapper<TEntity, TDto> : IEntityMapper<TEntity, TDto>
where TEntity : Entity, new()
where TDto : BaseDto
{
private readonly IRepositoryFactory _repositoryFactory;
protected BaseEntityMapper(IRepositoryFactory repositoryFactory)
{
_repositoryFactory = repositoryFactory;
}
public TEntity Map(TDto dto)
{
TEntity entity = CreateOrLoadEntity(dto.State, dto.Id);
MapPrimitiveProperties(entity, dto);
MapNonPrimitiveProperties(entity, dto);
return entity;
}
protected abstract void MapNonPrimitiveProperties(TEntity entity, TDto dto);
protected void MapPrimitiveProperties<TTarget, TSource>(TTarget target, TSource source, string prefix = "")
{
var targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
var sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
foreach (var targetProperty in targetProperties) {
foreach (var sourceProperty in sourceProperties) {
if (sourceProperty.Name != string.Format("{0}{1}", prefix, targetProperty.Name)) continue;
targetProperty.SetValue(target, sourceProperty.GetValue(source, null), null);
break;
}
}
}
protected void MapAssociation<TTarget, T>(TTarget target, Expression<Func<T>> expression, Guid id) where T : Entity
{
var repository = _repositoryFactory.Create<T>();
var propertyInfo = (PropertyInfo)((MemberExpression)expression.Body).Member;
propertyInfo.SetValue(target, repository.LoadById(id), null);
}
private TEntity CreateOrLoadEntity(DtoState dtoState, Guid entityId)
{
if (dtoState == DtoState.Created) return new TEntity();
if (dtoState == DtoState.Updated) {
return _repositoryFactory.Create<TEntity>().LoadById(entityId);
}
throw new BusinessException("Unknown DTO state");
}
}
Mapping of each entity is performed with a concrete class derived from BaseEntityMapper. The one for Person entities looks like this.
public class PersonEntityMapper: BaseEntityMapper<Person, PersonDto>
{
public PersonEntityMapper(IRepositoryFactory repositoryFactory) : base(repositoryFactory) {}
protected override void MapNonPrimitiveProperties(Person entity, PersonDto dto)
{
MapAssociation(entity, () => entity.Sex, dto.SexId);
MapAssociation(entity, () => entity.Position, dto.PositionId);
MapAssociation(entity, () => entity.Organization, dto.OrganizationId);
MapAssociation(entity, () => entity.Division, dto.DivisionId);
}
}
Explicitly calling MapAssociation protects against future properties renamings.
You can have a look on the two most used Object-Object mapper:
AutoMapper
AutoMapper is a simple little library built to solve a deceptively
complex problem - getting rid of code that mapped one object to
another. This type of code is rather dreary and boring to write, so
why not invent a tool to do it for us?
Value Injecter
ValueInjecter lets you define your own convention-based matching
algorithms (ValueInjections) in order to match up (inject) source
values to destination values.
There is a comparison article on SO: AutoMapper vs ValueInjecter
You can use GeDA for mapping any entity to a DTO object, it comes with either annotations or DSL support.
http://inspire-software.com/confluence/display/GeDA/FAQ
There are only basic examples on the wiki but jUnits of source code are full of useful examples
You can get it from sourceforge or google code manually or via maven dependency
Details are here: http://inspire-software.com/confluence/display/GeDA/GeDA+-+Generic+DTO+Assembler

Categories

Resources