Search code examples
c#entity-frameworkmicroservices.net-framework-version.net-standard-2.0

Include method from Entity Framework from in .NET Framework 4.5 is incompatible with .NET Standard 2.0


Originally this class was written in the .NET framework 4.5 and i'm now converting it to .NET Standard 2.0. However, the include method doesn't behave the same anymore. I'm receiving the following error:

'IQueryable' does not contain a definition for 'Include' and no accessible extension method 'Include' accepting a first argument of type 'IQueryable' could be found (are you missing a using directive or an assembly reference?)

Libaries being used:

using Microservices.LibCore.Core;
using Microservices.LibCore.Core.Base.Models;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.SqlClient;
using System.Linq;
using System.Net;
using System.Reflection;

public static IQueryable<T> IncludeRelated<T>(this IQueryable<T> originalQuery, int maxLevel = 2, bool includeCollections = false)
    {
        if (Config.get<bool>("EntityUtil_IncludeRelatedCollectionsAlways", false))
        {
            includeCollections = true;
        }

        var includeFunc = IncludeRelatedRecursive(typeof(T), "", 1, maxLevel, includeCollections);

        if (includeFunc != null)
        {
            return (IQueryable<T>)includeFunc(originalQuery);
        }
        else
        {
            return originalQuery;
        }
    }

private static Func<IQueryable, IQueryable> IncludeRelatedRecursive(Type type, string root, int level, int maxLevel, bool includeCollections = false)
    {
        if (level > maxLevel)
        {
            return null;
        }

        if (includeCollections)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                type = type.GetGenericArguments()[0];
            }
        }
        Func<IQueryable, IQueryable> includeFunc = null;

        foreach (var prop in type.GetProperties()

            .Where(p => Attribute.IsDefined(p, typeof(ForeignKeyAttribute)) &&
            !Attribute.IsDefined(p, typeof(JsonIgnoreAttribute))))
        {
            var includeChildPropFunc = IncludeRelatedRecursive(prop.PropertyType, root + prop.Name + ".", level + 1, maxLevel, includeCollections); //propertiesChecked

            if (includeChildPropFunc != null)
            {
                includeFunc = Compose(includeFunc, includeChildPropFunc);
            }
            else
            {
                Func<IQueryable, IQueryable> includeProp = f => f.Include(root + prop.Name);

                includeFunc = Compose(includeFunc, includeProp);
            }
        }
        return includeFunc;
    }

Solution

  • Include is in the Microsoft.EntityFrameworkCore namespace and the Microsoft.EntityFrameworkCore.dll assembly:

    EntityFrameworkQueryableExtensions.Include Method

    But in EF Core it requires an IQueryable<T>, not an IQueryable. Since you're using reflection to traverse the entity graph (and thus don't have a compile-time entity type T), you'll have to use reflection to invoke Include. Somehting like this:

        public static System.Linq.IQueryable Include(this System.Linq.IQueryable source, string navigationPropertyPath)
        {
            var entityType = source.GetType().GetGenericArguments().Single();
    
            var includeMethodGenericDefinition = typeof(Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions).GetMethods()
                                                    .Where(m => m.Name == "Include")
                                                    .Where(m => m.GetParameters()[1].ParameterType == typeof(string))
                                                    .Single();
            var includeMethod = includeMethodGenericDefinition.MakeGenericMethod(entityType);
    
            return (IQueryable)includeMethod.Invoke(null, new object[] { source, navigationPropertyPath });
    
        }