87

I am using the Generic Repository pattern on top of Entity Framework Code First. Everything was working fine until I needed to include more entities in a query. I got to include one entity successfully, but now I can't figure out how to include multiple entities. Check out what I've got so far:

public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
    var entityName = GetEntityName<TEntity>();
    return _objectContext.CreateQuery<TEntity>(entityName);
}

public IList<TEntity> GetQueryWithInclude<TEntity>(string toInclude) where TEntity : class
{
    var entityName = GetEntityName<TEntity>();
    return _objectContext.CreateQuery<TEntity>(entityName).Include(toInclude).ToList();
}

private string GetEntityName<TEntity>() where TEntity : class
{
    return string.Format("{0}.{1}", _objectContext.DefaultContainerName, _pluralizer.Pluralize(typeof(TEntity).Name));
}

What I tried to do but didn't work was pass in an array of strings into a function, then try to "append" the includes on top of the query. I was wondering what if I called the GetQueryWithInclude and passed an entity name (actually a navigation property) at a time to aggregate the results of the query, but I'm worried this might duplicate the results of the query on each call... What do you think would be the best way to get this to work?

UPDATE:

Here's an example of what I'm trying to achieve:

public IQueryable GetQueryWithIncludes(string[] otherEntities)
{
    var entityName = GetEntityName<TEntity>();
    //now loop over the otherEntities array 
    //and append Include extensions to the query
    //so inside the loop, something like: 
    _objectContext.GetQuery<TEntity>(entityName).Include(otherEntities[index]);
}
starball
  • 20,030
  • 7
  • 43
  • 238
Kassem
  • 8,116
  • 17
  • 75
  • 116
  • elaborate on _"include more entities in a query"_ ? Can you give an example of this? If you have an `ObjectContext` you should be able to query an objects/or related objects with LinqToEntities – gideon Mar 21 '11 at 10:50

4 Answers4

153

Use just the Include extension on IQueryable. It is available in EF 4.1 assembly. If you don't want to reference that assembly in your upper layers create wrapper extension method in your data access assembly.

Here you have example:

public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
    where T : class
{
    if (includes != null)
    {
        query = includes.Aggregate(query, 
                  (current, include) => current.Include(include));
    }

    return query;
}

You will use it for example like:

var query = context.Customers
                   .IncludeMultiple(
                       c => c.Address,
                       c => c.Orders.Select(o => o.OrderItems));

This query will load all customers with their addresses and orders and every order will contain its order items.

Nilesh Thakkar
  • 2,877
  • 1
  • 24
  • 43
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • @Ladislav Mrnka: This is exactly what I'm trying to do. I'm trying to use the Include extension, but I want to create a generic method that accepts any number of navigational properties in an array of strings and then include those with queried entity. See my edit/update above. – Kassem Mar 21 '11 at 10:59
  • 3
    Don't use version with strings. EF 4.1 also offers strongly typed version with lambdas. – Ladislav Mrnka Mar 21 '11 at 11:02
  • @Ladislav Mrnka: Ok but how? Could you provide an example please? – Kassem Mar 21 '11 at 11:04
  • I will post sample later if you don't get solution from somebody else. I'm writting from iPhone at the moment so providing code samples is pretty hard. – Ladislav Mrnka Mar 21 '11 at 11:13
  • @Ladislav Mrnka: Alright, I'll be very thankful. I have to go to my class right now, so I'll make sure to check later on. Appreciated :) – Kassem Mar 21 '11 at 11:15
  • @Ladislav Mrnka: Awesome! This is much better than using magic strings actually! Well, that means I have to upgrade to EF4.1 in order to use it, right? But I have to upgrade either way because CTP5 is not production read and does not work in a medium-trust environment. – Kassem Mar 21 '11 at 22:28
  • One question though, shouldn't the calling code be: `var query = context.Customers.IncludeMultiple(c => c.Address, c => c.Orders... etc)`? – Kassem Mar 21 '11 at 22:30
  • This works in CTP5 as well. Not sure what are you asking for in the last question. You mean that `Select` should not be part of the include? It is the way how to inlcude multiple levels of hiearchy with collection navigatin properties - it is equivalent to "Orders.OrderItems" in magic string version. – Ladislav Mrnka Mar 21 '11 at 22:38
  • Ouh, that's good! (Ladislav, I think he meant that you forgot to write ".IncludeMultiple(..." behind "context.Customers" in your example.) – Slauma Mar 21 '11 at 23:01
  • @Ladislav Mrnka: Yup, I meant the .IncludeMultiple and not the Select. Thanks a lot man, I really appreciate it :) – Kassem Mar 22 '11 at 07:51
  • I downloaded it with NuGet, that was just fun! – Shimmy Weitzhandler May 01 '11 at 23:32
  • Brilliant! - just remember to add ref to the C:\Program Files\Microsoft ADO.NET Entity Framework 4.1\Binaries\EntityFramework.dll and using System.Data.Entity; – StuartLC Nov 17 '11 at 11:27
  • How do you implement IncludeMultiple to extend DbContext? – Jeff Borden Aug 23 '12 at 15:34
  • Understood, and I apologize for being dense. But by default the DbContext does not include the IncludeMany method. How do I go about implementing your method to start including the IncludeMany method? Thank you... – Jeff Borden Aug 23 '12 at 19:47
  • Apologies, I just needed to learn how to extend IQueryable... ty for the solution! – Jeff Borden Aug 23 '12 at 19:54
  • 1
    @JeffBorden: `IncludeMany` is extension method (it is static and the first parameter uses `this` keyword) it works with all classes implementing `IQueryable`. That includes `DbSet`. – Ladislav Mrnka Aug 23 '12 at 19:58
5

Say goodbye to the hardcoded ObjectQuery(T).Include calls

If you're using EF > 4, then it's built in, check DbExtensions.Include on MSDN.

Stacked
  • 6,892
  • 7
  • 57
  • 73
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
3

//I have included the bare minimum here. Below is how to use it.

     IQueryable<File> xg= UnitOfWork.Files.GetAllLazyLoad(d => d.FileId == 1, 
            r => r.FileCategory);
//where r.FileCategory is a navigational property.

//Interface


    namespace Msh.Intranet.Repository.GenericRepoPattern
    {
        public interface IRepository<T> where T:class
        {

            IQueryable<T> GetAllLazyLoad(Expression<Func<T, bool>> filter, params Expression<Func<T, object>>[] children);

        }
    }



        namespace Msh.Intranet.Repository.GenericRepoPattern
        {
            /// <summary>
            /// The EF-dependent, generic repository for data access
            /// </summary>
            /// <typeparam name="T">Type of entity for this Repository.</typeparam>
            public class EFRepository<T> : IRepository<T> where T : class
            {
                public EFRepository(DbContext dbContext)
                {
                    if (dbContext == null)
                        throw new ArgumentNullException("dbContext");
                    DbContext = dbContext;
                    DbSet = DbContext.Set<T>();

                }

                protected DbContext DbContext { get; set; }

                protected DbSet<T> DbSet { get; set; }


                public virtual IQueryable<T> GetAllLazyLoad(Expression<Func<T, bool>> filter, params Expression<Func<T, object>>[] children) 
                {


                        children.ToList().ForEach(x=>DbSet.Include(x).Load());
                        return DbSet;
                }

            }
        }
hidden
  • 3,216
  • 8
  • 47
  • 69
  • 2
    It looks like your example method GetAllLazyLoad is actually eagerly loading the data. It is my understanding that lazy load would be without the include, pulling the data the first time its accessed. What your method seems to show is using the include pulling the data when the object is created. – Bennett Dill Jul 10 '14 at 14:08
3

For entity framework create a list of strings to hold the includes. See the below example code

public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> filter,
                                              int skip = 0,
                                              int take = int.MaxValue,
                                              Func<IQueryable<TObject>, IOrderedQueryable<TObject>> orderBy = null,
                                              IList<string> incudes = null)
    {
        var _resetSet = filter != null ? DbSet.AsNoTracking().Where(filter).AsQueryable() : DbSet.AsNoTracking().AsQueryable();

        if (incudes != null)
        {
            foreach (var incude in incudes)
            {
                _resetSet = _resetSet.Include(incude);
            }
        }
        if (orderBy != null)
        {
            _resetSet = orderBy(_resetSet).AsQueryable();
        }
        _resetSet = skip == 0 ? _resetSet.Take(take) : _resetSet.Skip(skip).Take(take);

        return _resetSet.AsQueryable();
    }

For more detail see the below link http://bulletproofcoder.com/blog/using-include-in-a-generic-entity-framework-repository

In entity framework core we shall use IIncludableQueryable. See the below example code

        public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> filter,
                                              int skip = 0,
                                              int take = int.MaxValue,
                                              Func<IQueryable<TObject>, IOrderedQueryable<TObject>> orderBy = null,
                                              Func<IQueryable<TObject>, IIncludableQueryable<TObject, object>> include = null)
    {
        var _resetSet = filter != null ? DbSet.AsNoTracking().Where(filter).AsQueryable() : DbSet.AsNoTracking().AsQueryable();

        if (include != null)
        {
            _resetSet = include(_resetSet);
        }
        if (orderBy != null)
        {
            _resetSet = orderBy(_resetSet).AsQueryable();
        }
        _resetSet = skip == 0 ? _resetSet.Take(take) : _resetSet.Skip(skip).Take(take);

        return _resetSet.AsQueryable();
    }
Bibin Gangadharan
  • 1,393
  • 12
  • 13
  • 2
    Could you elaborate it a bit more. Is this a self written Filter method and using IIncludableQueryable? And how are you using it in the code? – liqSTAR Feb 18 '21 at 07:43
  • 2
    Yes it is self return filter method. IIncludableQueryable is a interface provided by EF code. More details : https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.query.iincludablequeryable-2?view=efcore-5.0 Example code (I wrote for my project): var clientStores = await _unitOfWork.ClientStoreRepository.Filter(predicate, skip: skip, take: take, orderBy: c => c.OrderBy(a => a.StoreName), include: s => s.Include(l => l.Location).ThenInclude(la => la.LocationArea) .Include(c => c.Country)).ToListAsync(); – Bibin Gangadharan Feb 26 '21 at 07:15
  • 1
    And how to you chain with `.ThenInclude(s=>s.Something)`? – Bellash Apr 28 '22 at 09:29