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.