Kilian
Kilian

Reputation: 47

Automapper with custom lazy loading objects

I have a Person class which contains a property that lazy loads (custom made lazy loading) the person address data through accessing the Item property. I would want it to be mapped to a POCO class. How could it be done?

In addition, is it possible to be mapped only if it has data (checking the HasData property) and mapped as null if there isn’t data?. These are the source classes:

public class SourcePerson
{
    public string Name { get; set; }

    public MyLazyLoadingObject<SourceAddress> Address;
}

public class SourceAddress
{
    public string City { get; set; }

    public string Country { get; set; }
}

This is the custom lazy loading class (simplified):

public class MyLazyLoadingObject<T>
{
    private int? _id;
    private T _object;

    public T Item
    {
        get
        {
            if (!_object.IsReaded)
            {
                _object.Read();
            }

            return _object;
        }
    }

    public bool HasData
    {
        get
        {
            return _id.HasValue;
        }
    }

    // Other non-relevant properties and methods 
}

These are the destination classes:

public class DestinationPerson
{
    public string Name { get; set; }

    public DestinationAddress Address;
}

public class DestinationAddress
{
    public string City { get; set; }

    public string Country { get; set; }
}

Upvotes: 2

Views: 1296

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205579

Couldn't find conventional way of setting up conversion from MyLazyLoadingObject<T> to T and then T to some TDestination without code repetition.

But custom IObjectMapper implementation with some manual expression building does the job.

Here is the class that builds the mapping expression:

public class MyLazyLoadingObjectMapper : IObjectMapper
{
    public bool IsMatch(TypePair context)
    {
        return context.SourceType.IsGenericType && context.SourceType.GetGenericTypeDefinition() == typeof(MyLazyLoadingObject<>);
    }

    public Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
    {
        var item = Expression.Property(sourceExpression, "Item");
        Expression result = item;
        if (item.Type != destExpression.Type)
        {
            var typeMap = configurationProvider.ResolveTypeMap(item.Type, destExpression.Type);
            result = Expression.Invoke(typeMap.MapExpression, item, destExpression, contextExpression);
        }
        // source != null && source.HasData ? result : default(TDestination)
        return Expression.Condition(
            Expression.AndAlso(
                Expression.NotEqual(sourceExpression, Expression.Constant(null)),
                Expression.Property(sourceExpression, "HasData")
            ),
            result,
            Expression.Default(destExpression.Type)
        );
    }
}

All you need is to register it to the MapperRegistry:

AutoMapper.Mappers.MapperRegistry.Mappers.Add(new MyLazyLoadingObjectMapper());

and of course create the regular type maps (which I guess you already did):

cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourcePerson, DestinationPerson>();

Upvotes: 2

Kilian
Kilian

Reputation: 47

I've achieved it this way:

cfg.CreateMap<SourcePerson, DestinationPerson>().ForMember(t => t.Address, o => o.MapFrom(s => (s.Address.HasData)? s.Address.Item : null));

Upvotes: 1

Related Questions