Home > database >  Automapper : Ignore Navigation Properties
Automapper : Ignore Navigation Properties

Time:04-21

I've read the 2 proposed answers here and these 2 answers do not match what I want to do as they are manual work. So If I add another navigation property, I would need to modify AutoMapper config.

The main goal is to make EF objects serializable, without removing lazy loading.

I'm looking to write something like that :

cfg.CreateMap<EntityA, EntityA>(new IgnoreNavigationsProperties());

I'm not looking to identify each property one by one. It should use reflecion. (I can manage reflection part, just need some help to get started to how I could do that code.).

Any help or direction is welcome !

Edit :

So I start to get somewhere with converters.

/// <summary>
/// 
/// </summary>
/// <typeparam name="T"></typeparam>
public class EfEntityConverter<T> : ITypeConverter<T, T> where T : BaseEntity
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="source"></param>
    /// <param name="destination"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public T Convert(T source, T destination, ResolutionContext context)
    {
        
    }
}

But I get a lot of warnings (Which I treat as errors. And If I implement that converter, that will be horrible performances as Convert will be executed each time and I would run reflexion on each object.

So, any idea how to make it better ? (Sometiomes, I need to run it on 100.000 objects)

CodePudding user response:

Reflection can't tell you everything about EF Core's data model. Instead you can build a hashset of all your navigation properties from your DbContext.Model;

using var scope = provider.CreateScope();

var db = scope.GetRequiredService<MyContext>();
var model = db.Model;
var entities = model.GetEntityTypes();

var navigationProperties = entities
    .SelectMany(e => e.GetNavigations())
    // ignore owned type navigations, treat them as part of the data;
    .Where(n => !n.ForeignKey.IsOwnership && n.PropertyInfo!=null)
    .Select(n => n.PropertyInfo)
    .Concat(
        entities
            .SelectMany(e => e.GetSkipNavigations())
            .Where(n => n.PropertyInfo!=null)
            .Select(n => n.PropertyInfo)
    )
    .ToHashSet();

You'll want to store this hashset somewhere as a singleton, so you only build it once, or perhaps once per entity type.

From there you probably want to dynamically build an expression tree equivalent to t => new T{ A = t.A, .... };, skipping any navigation properties from your model.

var parm = Expression.Parameter(typeof(T), "t");

var expr = Expression.Lambda<Func<T,T>>(
    Expression.MemberInit(
        Expression.New(typeof(T)),
        typeof(T)
            .GetProperties()
            .Where(p => p.CanRead && p.CanWrite)
            .Except(navigationProperties)
            .Select(p => Expression.Bind(p, Expression.MakeMemberAccess(parm, p)))
    ),
    parm
);

var factoryMethod = expr.Compile();

Now you can call this method from your .Convert() method to create the copy of the object.

Though this trivial implementation will only make a shallow copy of each property. For some property types, such as owned types, a deep copy would be needed.

  • Related