4
votes

We are using Entity Framework Core, with a set of Entities that all share a base class.

public class EntityBase { ... }

public class FirstEntityChild : EntityBase { ... }
public class SecondEntityChild : EntityBase { ... }

We use this so we can query for either a FirstEntityChild or SecondEntityChild without needing to know which one at compile time.

To provide the data to our view, we have view models.

public class FirstEntityChildViewModel { ... }
public class SecondEntityChildViewModel { ... }

We're hoping to use AutoMapper's ProjectTo to map the query results from our database entity to a view model. We have mapping profiles set up from FirstEntityChild to FirstEntityChildViewModel and from SecondEntityChild to SecondEntityChildViewModel. We have no maps from EntityBase.

We're currently attempting to accomplish this through type inference with an extension method:

public static IQueryable<TDestination> ProjectTo<TSource, TDestination>(
    this IQueryable<TSource> query,
    TSource sourceTypeInstance,
    TDestination destinationTypeInstance,
    IConfigurationProvider configuration)
{
    return query.ProjectTo<TDestination>(configuration);
}

And we're using that as follows, given that sourceType is either FirstEntityChild or SecondEntityChild, and targetType is either FirstEntityChildViewModel or SecondEntityChildViewModel, and both are known only at runtime:

var sourceInstance = Activator.CreateInstance(sourceType);
var targetInstance = Activator.CreateInstance(targetType);

var results = await query
    .ProjectTo(sourceInstance, targetInstance, mapper.ConfigurationProvider)
    .ToListAsync();

The issue we're facing happens inside our ProjectTo extension. When I debug and stop on that line, sourceTypeInstance and destinationTypeInstance appear in the debugger to be FirstEntityChild and FirstEntityChildViewModel respectively. But when the line executes, AutoMapper attempts to map from EntityBase to object, which we don't have a mapping profile for (and which I don't think would even be possible):

InvalidOperationException: Missing map from EntityBase to System.Object. Create using Mapper.CreateMap.

Is it possible to indicate the actual, derived type AutoMapper should be using? Or are we just bumping up against the limitations of generics at this point?

1
Do you have maps from FirstEntityChild to FirstEntityChildViewModel? or do you only have maps from EntityBase to something? - Vidmantas Blazevicius
We do have maps from FirstEntityChild to FirstEntityChildViewModel and SecondEntityChild to SecondEntityChildViewModel. No maps from EntityBase to anything else. Thank you for pointing this out, I've edited the question to reflect this. - Jimmy
I mean there must be more to this, why are you creating instance through Activator and then immediately mapping it? Even if you did that, wouldn't mapping default(TSource) to default(TDestination would be better? Why is sourceInstance even needed here since you intend source to come from EF? - Vidmantas Blazevicius
I will make a wild bet that your mapper.ConfigurationProvider maybe doesn't hold the config for your maps. - Vidmantas Blazevicius
We use Activator because we start with only knowing the types we're coming from and mapping to at runtime, so we're using type inference to allow using ProjectTo. I'm certain our mapping profiles are correct because using Map on an enumerated list of our entities works just fine. Besides, the error message we get indicates that AutoMapper isn't using our mapping profiles, as it is mapping from and to the wrong types. - Jimmy

1 Answers

0
votes

You could use reflection to create a generic method out of ProjectTo and your destination type and then invoke that method to get the results of ProjectTo. For instance:

public static IQueryable<TDestination> ProjectTo<TSource, TDestination>(
    this IQueryable<TSource> query,
    TSource sourceTypeInstance,
    TDestination destinationTypeInstance,
    IConfigurationProvider configuration)
{
    var projectToMethod = typeof(AutoMapper.QueryableExtensions.Extensions)
            .GetMethod("ProjectTo", new Type[]
            {
                typeof(IQueryable),
                typeof(IConfigurationProvider),
                typeof(IDictionary<string, object>),
                typeof(string[])
            })
            .MakeGenericMethod(typeof(TDestination));
    return projectToMethod.Invoke(query, new object[] { query, configuration, new Dictionary<string, object>(), new string[] { } });
}