For EF6, I had a method in my generic repository that I exposed to all service layers in order to retrieve entities from the database with any nested properties as needed:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
{
var queryable = set.AsQueryable();
return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
This way, I could use the method in the following way:
var data = repo.OldMethod(x => x.Papers, => x.People.Select(y => y.Addresses)).ToList();
In EF6, this would load the Papers
navigation property, the People
navigation property, and the Addresses
navigation property on each person. This, as expected, throws an exception in EFCore. Because of the switch to Include-->ThenInclude method in EFCore, I'm not quite sure how to easily replicate this at my service layer which I'd like to not require any information about EntityFramework.
Keep using EF6 if the data access code is stable and not likely to evolve or need new features. Port to EF Core if the data access code is evolving or if the app needs new features only available in EF Core. Porting to EF Core is also often done for performance.
EF Core 6.0 itself is 31% faster executing queries. Heap allocations have been reduced by 43%.
Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query. Eager loading is achieved by use of the Include method. For example, the queries below will load blogs and all the posts related to each blog. Include is an extension method in the System.
Entity Framework Core is what's known as an Object Relational Mapper (ORM). Created by Microsoft, the library allows developers to work abstractly with their database.
This has been asked many times since the initial release of EF Core. Earlier prerelease versions of EF Core even were supporting it, but then it has been removed from EF Core code (I guess in order to promote the new Include
/ ThenInclude
pattern).
While Include
/ ThenInclude
pattern looks more clear (besides the current Intellisense issues), it has one major drawback - requires access to EntityFrameworkQueryableExtensions
, thus reference to Microsoft.EntityFrameworkCore
assembly. While params
Expression>` pattern has no such requirement.
The good thing is the one can relatively easily add that functionality. The EF6 source code is publicly available on GitHub, and from there we can see that it uses a method called TryParsePath to build dot separated string path which then is passed to the string
overload of Include
method.
The same can be applied in EF Core. We can probably use the EF6 code, but I'm going to provide my own version. It can be easily be seen that the supported constructs are member accessors or calls to method called Select
with 2 arguments, the second being LambdaExpression
.
Following is my interpretation of the above, encapsulated in two custom extension methods:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore
{
public static class IncludeExtensions
{
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> includePaths) where T : class
=> includePaths.Aggregate(source, (query, path) => query.Include(path));
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> includePaths) where T : class
=> source.Include(includePaths.Select(e => GetIncludePath(e?.Body)));
static string GetIncludePath(Expression source, bool allowParameter = false)
{
if (allowParameter && source is ParameterExpression)
return null; // ok
if (source is MemberExpression member)
return CombinePaths(GetIncludePath(member.Expression, true), member.Member.Name);
if (source is MethodCallExpression call && call.Method.Name == "Select"
&& call.Arguments.Count == 2 && call.Arguments[1] is LambdaExpression selector)
return CombinePaths(GetIncludePath(call.Arguments[0]), GetIncludePath(selector.Body));
throw new Exception("Invalid Include path.");
}
static string CombinePaths(string path1, string path2)
=> path1 != null ? path1 + "." + path2 : path2;
}
}
The first is simply helper for calling multiple string
includes (taken from my answer to Entity Framework Core 2.0.1 Eager Loading on all nested related entities). The second is the method in question, which converts the expressions to strings and call the first. The main work is done by GetIncludePath
private method which recursively processes the expression based on the aforementioned rules, plus one additional rule - when navigating bottom up, it should end with lambda parameter.
Now the implementation of the method is question is simple as that:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
=> set.Include(includeProperties);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With