Entity Framework Include with Func next

I defined an Include with Func.

Cool. But what about Include(“Products.Order_Details”)?

Indeed my Include can only take one relationship level.

So I change my code like this:

public static class ObjectQueryExtension

{

    public static ObjectQuery<T> Include<T>(this ObjectQuery<T> mainQuery, Expression<Func<T, object>> subSelector)

    {

        return mainQuery.Include(FuncToString(subSelector.Body));

    }

    private static string FuncToString(Expression selector)

    {

        switch (selector.NodeType)

        {

            case ExpressionType.MemberAccess:

                return ((selector as MemberExpression).Member as Reflection.PropertyInfo).Name;

            case ExpressionType.Call:

                var method = selector as MethodCallExpression;

                return FuncToString(method.Arguments[0]) + “.” + FuncToString(method.Arguments[1]);

            case ExpressionType.Quote:

                return FuncToString(((selector as UnaryExpression).Operand as LambdaExpression).Body);

        }

        throw new InvalidOperationException();

    }

    public static K Include<T, K>(this EntityCollection<T> mainQuery, Expression<Func<T, object>> subSelector)

        where T : EntityObject, IEntityWithRelationships

        where K : class

    {

        return null;

    }

    public static K Include<T, K>(this T mainQuery, Expression<Func<T, object>> subSelector)

        where T : EntityObject

        where K : class

    {

        return null;

    }

}


and I can now do this:

context.Categories.Include(ca => ca.Products.Include<Products, Order_Details>(p => p.Order_Details).Include<Order_Details, Orders>(od => od.Orders))


Isn’t it cool?

This entry was posted in 7671, 7674. Bookmark the permalink.

22 Responses to Entity Framework Include with Func next

  1. Mike L says:

    Absolutely incredible! I don’t know what else to say

  2. Matthieu MEZIL says:

    Thanks [:D]

  3. Ken Smith says:

    Very cool! I also hate using strings in this context: it seems counterintuitive, and very susceptible to mistakes. At the same time, I’m not sure I’ll be using this method. Which of the following is easier for the next guy looking at my code to understand?

    Room room = ctx.Room
    .Include(“Sessions.User”)
    .Include(“Sessions.Whiteboards.Owner”)
    .Include(“Sessions.Whiteboards.WhiteboardShape”)
    .Include(“Sessions.MediaStreams”)
    .Include(“Owner”)
    .FirstOrDefault(r => r.OwnerID == ownerUserID && r.Name == roomName);

    Room room2 = ctx.Room
    .Include(r => r.Sessions.Include(s => s.User))
    .Include(r => r.Sessions.Include
    (s => s.Whiteboards).Include(w => w.Owner))
    .Include(r => r.Sessions.Include(s => s.Whiteboards).Include(w => w.WhiteboardShape))
    .Include(r => r.Sessions.Include(s => s.MediaStreams))
    .Include(r => r.Owner)
    .FirstOrDefault(r => r.OwnerID == ownerUserID && r.Name == roomName);

    They both do the same thing, but it requires a great deal more effort to understand what the second one is doing. I don’t like the first way at all — but the second way is, if anything, uglier and more complicated.

    That’s not to say I don’t appreciate the code — I very much do! You’re trying to solve a problem that MS really should have solved itself. There just doesn’t seem to be an easy way to solve it.

    Does anyone know if MS has addressed this in .NET 4.0?

  4. Diego Vega says:

    @Ken, I too wish we had addressed this in .NET 4.0, but unfortunately it didn’t climb to the top of the priority list as we were implementing other features (i.e. POCO support, functions exposed in LINQ, model defined functions, T4 integration, FK support, Contains, DefaultIfEmpty, Single/First/OrDefault, better stored procedure support, ExecuteStoreQuery/Translate, OrderBy Lifting, improvements for StartsWith, optimizations for non-unicode columns, EntityDataSource improvements including LINQ support, etc).

    It is hard to get any new feature in for .NET 4.0 at this point, but I promise I will bring it up for the next version.

  5. Matthieu MEZIL says:

    Thanks a lot Diego

    @Ken: you can simplify a little your code like this:

               Room room2 = ctx.Room
                   .Include(r => r.Sessions.Include<Session, User>(s => s.User))
                   .Include(r => r.Sessions.Include<Session, Whiteboard>(s => s.Whiteboards).Include(w => w.Owner))
                   .Include(r => r.Sessions.Include<Session, Whiteboard>(s => s.Whiteboards).Include(w => w.WhiteboardShape))
                   .Include(r => r.Sessions.Include<Session, MediaStream>(s => s.MediaStreams))
                   .Include(r => r.Owner)
                   .FirstOrDefault(r => r.OwnerID == ownerUserID && r.Name == roomName);

  6. Kamil says:

    I thinkk this is better:
    public static ObjectQuery Include(this ObjectQuery mainQuery, Expression> subSelector)
    {
    var sb = new StringBuilder();
    FuncToString(sb, keyExpression.Body);
    return mainQuery.Include(sb.ToString());
    }

    private static void FuncToString(StringBuilder sb, Expression selector)
    {
    switch (selector.NodeType)
    {
    case ExpressionType.Parameter:
    // sb.Append(((ParameterExpression) selector).Name);
    return;
    case ExpressionType.MemberAccess:
    FuncToString(sb, ((MemberExpression) selector).Expression);
    sb.Append(“.”);
    sb.Append(((MemberExpression) selector).Member.Name);
    return;
    }
    throw new InvalidOperationException();
    }

    And now you can use like this:
    query.Include(c=>c.Session)
    .Incude(c=>c.Session.User)
    :)

  7. Matthieu MEZIL says:

    Hi
    the problem with your solution is with collection : customers.Include(“Orders.OrderDetails”)
    That’s why I did it like this
    Matthieu

  8. Ralf says:

    Hello,
    Thanks a lot for this code.
    I was looking for something like that since I do not want to have strings for names sitting there too.

    Now, I only have a strange problem: When I build a query and .Include it passes the data to the FuncToString routine as a ExpressionType.Convert (10) and not one of the three defined to work with.

    I added another line to the case section, like this
    Case ExpressionType.Convert
    Return TryCast(TryCast(TryCast(selector, UnaryExpression).Operand, MemberExpression).Member, Reflection.PropertyInfo).Name (VB Syntax)

    which is exactly the same as the .MemberAccess case except for another TryCase selector to UnaryExpression first.

    I have no idea what I am doing there, how why what when, but it seems to work. Has anybody a hint why this change is needed, or what I was doing wrong in the first place? Why do I end up with ExpressionType Convert??

    I have a second far harder question too: If you have two entities mapped m:n and the middle table of the database does not appear in your entity model (I like that…) I cannot get the function to work at all.
    Any hint on that?

    Thanks a lot!
    Ralf

  9. Matthieu MEZIL says:

    Try with DirectCast instead of TryCast. It was stupid in my case to use the as operator instead a direct cast.

  10. nite says:

    Kamil’s really solved it there – no-one’s noticed ;)

  11. I am using your extension method in a solution but when in EF I compile a LINQ query with an include like this:
    public static Func GetUserByEmail =
    CompiledQuery.Compile
    ((MyNexTVEntities ctx, string email) => (from a in ctx.Authentication.Include(a => a.Users)
    where a.Utenti.Email == email select a).FirstOrDefault());

    I got the following error at runtime :
    LINQ to Entities does not recognize the method ‘System.Data.Objects.ObjectQuery`1[MntvServicePOX.Authentication] Include[Authentication](System.Data.Objects.ObjectQuery`1[MntvServicePOX.Authentication], System.Linq.Expressions.Expression`1[System.Func`2[MntvServicePOX.Authentication,System.Object]])’ method, and this method cannot be translated into a store expression.

    Any idea why your extension fail with compiled query ?

  12. Nelson says:

    I can’t get a multiple level path working on Entity Framework 4 with POCO. The following works:

    context.Orders.Include(o => o.OrderItems)

    But then if I try:
    context.Orders.Include(o => o.OrderItems.Include(s => s.OrderItemSub))

    I get the error: cannot convert from ICollection to EntityCollection

    With POCO, it’s ICollection instead of EntityCollection. Any way to get this working?

  13. Matthieu MEZIL says:

    @Nelson.
    Good remark.
    You just have to change EntityCollection to ICollection in my code.
    Let me know if you don’t achieve it.

  14. David says:

    Matthieu,

    Is your updated new version for EF4 something close to:

    public static class ObjectQueryExtension
    {
    #region Private Function

    private static string FuncToString(Expression selector)
    {
    switch (selector.NodeType)
    {
    case ExpressionType.MemberAccess:
    return ((selector as MemberExpression).Member as System.Reflection.PropertyInfo).Name;
    case ExpressionType.Call:
    var method = selector as MethodCallExpression;
    return FuncToString(method.Arguments[0]) + “.” + FuncToString(method.Arguments[1]);
    case ExpressionType.Quote:
    return FuncToString(((selector as UnaryExpression).Operand as LambdaExpression).Body);
    }
    throw new InvalidOperationException();
    }

    #endregion

    #region Public Function

    public static IQueryable Include(this IQueryable mainQuery, Expression> subSelector)
    {
    var objectQuery = mainQuery as ObjectQuery;
    if (objectQuery != null)
    return objectQuery.Include(FuncToString(subSelector.Body));
    return mainQuery;
    }
    public static K Include
    (this ICollection mainQuery, Expression> subSelector)
    where T : class
    where K : class
    {
    return null;
    }
    public static K Include(this T mainQuery, Expression> subSelector)
    where T : class
    where K : class
    {
    return null;
    }

    #endregion
    }

    Am I missing anything here? Thanks.

  15. DaveW says:

    I know this is an old post, but like POCO Self Tracking entities require some slightly different changes. They don’t subtype EntityObject or IEntityWithRelationships. They do implement IObjectWithChangeTracker, but that is a namespace specific interface created with each EF model instance. So if your application is partitioned into functional areas that do not share a common EF model you cannot use this interface in the check either assuming you are trying to provide the extension method via your common architecture.

  16. Paul says:

    Will this work with CTP4 code first? I am getting this error:

    LINQ to Entities does not recognize the method ‘System.Data.Objects.ObjectQuery`1[PostHope.Core.DomainObjects.SiteAnnouncement] Include[SiteAnnouncement](System.Data.Objects.ObjectQuery`1[PostHope.Core.DomainObjects.SiteAnnouncement], System.Linq.Expressions.Expression`1[System.Func`2[PostHope.Core.DomainObjects.SiteAnnouncement,System.Object]])’ method, and this method cannot be translated into a store expression.

  17. Micha says:

    Hi Matthieu,
    just found your awesome include, could have saved me some time. Measnwhile I did my own implementation, maybe you wan’t to have a look:
    http://michael-sander.eu/index.php/2010/09/29/strongly-typed-include-in-the-entity-framework/

    The syntax would be like
    from asset in dataContext.Assets
    .Include(x => x.AssetType)
    .Include(x => x.Locations)
    .Include(x => x.Locations.First().MeasuringPoints)
    .Include(x => x.Locations.First().MeasuringPoints.First().MeasuringPointType)
    select asset;
    which is a bit shorter than yours, but i really dislike the First() calls here.

  18. Matthieu MEZIL says:

    Hi Michael
    Yes the syntax is more easy with your fake first.
    However, as you, I really dislike the First() calls.

  19. I implemented a similar method with a slightly different approach, using an ExpressionVisitor:

    http://tomlev2.wordpress.com/2010/10/03/entity-framework-using-include-with-lambda-expressions/

    Not sure which is better…

    Anyway, the EF Feature CTP already provides a method to do the same thing, hopefully it will eventually be included in the framework

  20. Nice solution indeed.

    You might want to compare with the following:
    http://www.codetuning.net/blog/post/Entity-Framework-compile-safe-Includes.aspx

    (The latest version of the code has become part of http://ef4tiers.codeplex.com/).

    Kind regards

  21. Cecil says:

    The RTM version of EF4 doesn’t seem to come with the Include method that accepts Lamabdas :(

  22. sgissinger says:

    Very useful extension thanks.
    I added some recursivity to method FuncToString in order to use more than one level using one Expression only.

    case ExpressionType.MemberAccess:
    MemberExpression expr = selector as MemberExpression;

    if (expr.Expression.NodeType == ExpressionType.MemberAccess)
    return FuncToEntityFrameworkString(expr.Expression) + “.” + (expr.Member as PropertyInfo).Name;
    else
    return (expr.Member as PropertyInfo).Name;

    You can now use it like the following
    objectContext.OBJ1.Include(f => f.OBJ2.OBJ3.OBJ4);

    Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>