Search code examples
entity-frameworkreflectioninterfacedbset

How to query the DbSets of all types that implement an interface?


Many of my data models use this interface:

public interface IHasPrimaryImageProperty
{
    PrimaryImageDataModel PrimaryImage { get; set; }
    int? PrimaryImageId { get; set; }
}

Where PrimaryImageDataModel is:

public class PrimaryImageDataModel
{
    public int Id { get; set; }
    public string ImageFile { get; set; }
    public int TotalItemsUsingImage { get; set; }
}

I want populate PrimaryImageDataModel.TotalItemsUsingImage, by performing counts on all data models that implement IHasPrimaryImageProperty.

So far I have managed to get a list of types that implement the IHasPrimaryImageProperty.

But I haven't been able to get the total for each Type.

Please see the example below for a demonstration of what I would like to acheive.

public static PrimaryImageDataModel GetImageUsageTotals(PrimaryImageDataModel image)
{
    var typesUsingImage = GetTypesWithPrimaryImageProperty();
    int totalUsingImage = 0;
    foreach (Type typeUsingImage in typesUsingImage)
    {
        // I WOULD LIKE TO DO SOMETHING LIKE THIS
        totalForType = db.Set<typeUsingImage>()
            .Where(x => x.PrimaryImageId == image.Id)
            .Count()

        totalUsingImage += totalForType;
    }
    image.TotalItemsUsingImage = totalUsingImage;
    return image;
}


public static IEnumerable<Type> GetTypesWithPrimaryImageProperty()
{
    var currentAssembly = Assembly.GetExecutingAssembly();
    foreach (Type type in currentAssembly.GetTypes())
    {
        if (type.GetInterfaces().Contains(typeof(IHasPrimaryImageProperty)))
        {
            yield return type;
        }
    }
}

Solution

  • IQueryable is covariant. See Variance in Generic Interfaces (C#) This allows an IQueryable<SomeEntity> to be cast to IQueryable<InterfaceType>, for interfaces implemented by that Entity type.

    So if you put this method on your EF6 DbContext type:

    public IQueryable<T> GetQuery<T>(Type EntityType)
    {    
        return (IQueryable<T>)this.Set(EntityType);
    }
    

    Or like this for EF Core:

    public IQueryable<T> GetQuery<T>(Type EntityType)
    {
    
        var pq = from p in this.GetType().GetProperties()
                 where p.PropertyType.IsGenericType
                    && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)
                    && p.PropertyType.GenericTypeArguments[0] ==  EntityType
                 select p;
        var prop = pq.Single();
    
        return (IQueryable<T>)prop.GetValue(this);
    
    }
    

    Then you can write

    foreach (Type typeUsingImage in typesUsingImage)
    {
        // I WOULD LIKE TO DO SOMETHING LIKE THIS
        totalForType = db.GetQuery<IHasPrimaryImageProperty>(typeUsingImage)
            .Where(x => x.PrimaryImageId == image.Id)
            .Count()
    
        totalUsingImage += totalForType;
    }