Skip to content
This repository has been archived by the owner on May 23, 2024. It is now read-only.

Nested includes in ISpecification #40

Closed
dag23 opened this issue Aug 17, 2017 · 17 comments
Closed

Nested includes in ISpecification #40

dag23 opened this issue Aug 17, 2017 · 17 comments

Comments

@dag23
Copy link

dag23 commented Aug 17, 2017

Hi, I have been looking at ISpecification and the way the includes are added. Could something similiar be implemented for nested includes, that later are translated to ThenInclude in IQueryable?

@trevor-hackett
Copy link

I was wondering the same the thing. I had to change the specifications to use strings instead of expressions to do nested includes. Not entirely ideal because refactoring can easily break the includes.

Sent from my Google Nexus 6P using FastHub

@ardalis
Copy link
Collaborator

ardalis commented Aug 22, 2017

I'm not sure if there's a supported way to do this (hence why it's not implemented yet). I'm open to suggestions, though. This is somewhat related (TL;DR: you only need ThenInclude for collection navigation properties):
dotnet/efcore#4716

@ardalis
Copy link
Collaborator

ardalis commented Sep 1, 2017

Related: dotnet/efcore#9523

@ardalis
Copy link
Collaborator

ardalis commented Sep 26, 2017

For now this is implemented using string-based includes, to maximize flexibility. The downside is, they're magic strings and could become out of sync with entity renames.

Example usage:

public CustomerOrdersWithItemsSpecification(string buyerId)
{
    _buyerId = buyerId;
    AddInclude(o => o.OrderItems); // lambda syntax works for immediate children
    AddInclude("OrderItems.ItemOrdered"); // string syntax works for any level of depth
}

@ardalis ardalis closed this as completed Sep 26, 2017
@ModernRonin
Copy link

I know this has been closed, I just wanted to add you can get around the refactoring issue with magical strings by doing something like this:

$"{nameof(PriceList.Revisions)}.{nameof(PriceListRevision.VehicleClassPrices)}.{nameof(VehicleClassPricing.Product)}",

That's what I do. Anyway, even so, an expression based solution would be nicer, ofc.

@hanslai
Copy link

hanslai commented Jan 4, 2018

@ModernRonin I cannot quite follow with your example, can you use @ardalis 's example with
OrderItems.ItemOrdered and give an example using nameof() Thanks

@hanslai
Copy link

hanslai commented Jan 4, 2018

@ardalis have you find any other alternative approach to avoid using"magic string" with Specification pattern. Thanks

@ardalis
Copy link
Collaborator

ardalis commented Jan 4, 2018

I like the nameof solution. I've just added it via the PR noted above.

@hanslai
Copy link

hanslai commented Jan 4, 2018

Thanks.

@mrukas
Copy link
Contributor

mrukas commented Dec 3, 2019

In case someone still wants to have this feature. I added a pull request that adds the possibility to chain includes with .Include() and .ThenInclude().
c3193b1

@fingers10
Copy link

For some reason this multiple nesting is not working for me. Can anyone please assist me on where I'm wrong?

Here are my Classes:

Branch:

public class Branch : BaseEntity<Guid>, IAggregateRoot
{
    private Branch()
    {
        // required by EF
    }

    public Guid ClientId { get; set; }
    public Client Client { get; set; }
}

Client:

public class Client : BaseEntity<Guid>, IAggregateRoot
{
    private Client()
    {
        // required by EF
    }

    public Guid BusinessId { get; set; }
    public Business Business { get; set; }
}

Business:

public class Business : BaseEntity<Guid>, IAggregateRoot
{
    private Business()
    {
        // required by EF
    }

    public string Name { get; set; }
}

I'm trying to load Branch with Client and Business. Here is my specification:

public class BranchWithClientSpecification : BaseSpecification<Branch>
{
    public BranchWithClientSpecification() : base(b => b.Active)
    {
        AddInclude(b => b.Client);
        AddInclude($"{nameof(Branch.Client)}.{nameof(Client.Business)}");
    }
}

AddInclude($"{nameof(Branch.Client)}.{nameof(Client.Business)}"); line is not working. I always get null for public Business Business { get; set; } property in Client class. Can anyone please assist on where I'm wrong?

@JonathanLoscalzo
Copy link

I think that @mrukas is an interesting solution.

We are trying to use Include & .ThenInclude but also use filtering includes. With strings magic that is not possible.

ref: https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager#filtered-include

@JonathanLoscalzo
Copy link

I think that @mrukas is an interesting solution.

We are trying to use Include & .ThenInclude but also use filtering includes. With strings magic that is not possible.

ref: https://docs.microsoft.com/en-us/ef/core/querying/related-data/eager#filtered-include

Actually, I found the Ardalis.Specification package, I think that is the solution, but if we want to build a simpler solution, is not as easy as I expected

@ardalis
Copy link
Collaborator

ardalis commented Nov 9, 2021

Yes, this is solved already in Ardalis.Specification, if you're able to just consume that package.

@JonathanLoscalzo
Copy link

We have a simplistic solution, but we need to use include and then-include with filtering. It is not easy to add without break all

@fiseni
Copy link
Contributor

fiseni commented Nov 10, 2021

If you use the latest version of Ardalis.Specification, you'll get chaining capabilities for Include-TheInclude.
The filtered includes, e.g. Include(x => x.Where(something)), are introduced starting with EF Core 5. So you have to use a minimum of that version of EF Core to get that capability.

@JonathanLoscalzo
Copy link

@fiseni Yes, we are using EF core 5.
Regarding using Ardalis.Specification, We have just implemented an Specification, so move to another implementation is not a good idea. (Next project will be mandatory use that package)

Anyway, I have implemented some pieces similar to Ardalis.Specification solution, but more simplistic (not as good as Ardalis, of course)

We have an issue with Nested ThenIncludes and IncludeExtension.cs, instead of returing IQueryable, our code returns IIncludableQueryable (for chaining of chaining), also we have changed the generic implementation of that method:

public static IIncludableQueryable<T, TProperty> Include<T, TProperty>(this IQueryable<T> source, IncludeExpressionInfo info)
    {
            _ = info ?? throw new ArgumentNullException(nameof(info));

            var types = new Type[] {
                info.EntityType,
                info.PropertyType
            };

            var methodInfo = typeof(EntityFrameworkQueryableExtensions).GetMethods().Where(m => m.Name.Equals("Include")).First();

            var genericMethod = methodInfo.MakeGenericMethod(types);
            var result = genericMethod.Invoke(null, new object[] { source, info.LambdaExpression });
            return (IIncludableQueryable<T, TProperty>)result;
   }

In the end, we have nested .ThenInclude. Thanks all for building these projects.

(Nota: I don't know if Ardalis.Specification allows nested ThenIncludes[?])

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants