Dapper icon indicating copy to clipboard operation
Dapper copied to clipboard

One to Many relationship - Mapping through reflection

Open georgait opened this issue 2 years ago • 1 comments

In order to avoid boilerplate code with dictionary to keep values etc i was thinking to create a more generic solution for one to many scenarios. I came up with this:

public async Task<IReadOnlyCollection<TEntity>> ReadAsync<TEntity, TManyEntity, TParameters>(string query, TParameters parameters)
    {
        using var connection = _context.CreateConnection();

        var dictionary = new Dictionary<int, TEntity>();
        var result = await connection.QueryAsync<TEntity, TManyEntity, TEntity>(query,
            (one, many) => Map(dictionary, one, many),
            parameters,
            commandType: CommandType.StoredProcedure);

        return result.Distinct().ToList();
    }
private static TEntity Map<TEntity, TManyEntity>(Dictionary<int, TEntity> dictionary, TEntity one, TManyEntity many)
    {
        var entityType = one?.GetType()!;

        var idProperty = entityType.GetProperties()
            .FirstOrDefault(p => p.IsDefined(typeof(IdAttribute), true))!;

        var getId = PropertyHelper.InvokeGet<TEntity, int>(idProperty);
        var id = getId(one);

        if (!dictionary.TryGetValue(id, out var currentOne))
        {
            currentOne = one;
            dictionary.Add(id, currentOne);
        }

        var manyProperty = entityType.GetProperties()
            .FirstOrDefault(p => p.IsDefined(typeof(ManyRelationshipAttribute), true))!;
        
        var collection = (List<TManyEntity>)manyProperty?.GetValue(currentOne)!;
        var collectionType = collection.GetType();

        var addToCollection = PropertyHelper.InvokeAdd<TEntity, TManyEntity>(collectionType);        
        addToCollection(collection, many);

        return currentOne;
    }
internal static class PropertyHelper
{
    private static readonly ConcurrentDictionary<string, Delegate> _delegateCache = new();

    public static Func<TClass, TResult> InvokeGet<TClass, TResult>(PropertyInfo property) =>
        (Func<TClass, TResult>)_delegateCache.GetOrAdd(property.Name, key =>
        {
            var getMethod = property.GetMethod;
            var res = getMethod?.CreateDelegate(typeof(Func<TClass, TResult>))!;
            return res;
        });

   public static Action<List<TChildClass>, TChildClass> InvokeAdd<TParentClass, TChildClass>(Type type) =>    
        (Action<List<TChildClass>, TChildClass>)_delegateCache.GetOrAdd("Many", key =>
        {
            var addMethod = type.GetMethod("Add")!;

            var delegateType = typeof(Action<List<TChildClass>, TChildClass>)!;
            var res = addMethod?.CreateDelegate(delegateType)!;
            return (Action<List<TChildClass>, TChildClass>)res;
        });
}

By doing this i get no improvements in comparison with reflection without delegates. My question is, is it worth it, when it comes to performance, to use it this way or it is better to keep boilerplate code everytime i need to do a one-many releationship with no reflection at all?

georgait avatar Jul 25 '22 05:07 georgait

Hi @georgait, I think you only answered your question. So here are the benefits I am listing of using Reflection:

  1. Dynamic and flexible mapping
  2. Reduces code Duplication
  3. Improves Performance: Reflection-based mapping can improve performance in scenarios where you need to map large collections of objects. By using reflection to map object properties, you can avoid the overhead of manually mapping each property individually. However, it's important to note that reflection-based mapping can be slower than hand-written mapping code in some scenarios.

I hope this is helpful. 🙌

asherborntofly avatar Apr 26 '23 07:04 asherborntofly