0

So, I already know that code like this does everything in memory:

SomeDbObject.SomeNavigationProperty.Where(x => x.IsSomeBool);

In addition to this (still in memory):

SomeDbObject.SomeNavigationProperty.AsQueryable().Where(x => x.IsSomeBool);

So, I came up with a friendly solution that helps me make sure that the whole call will be executed as a SQL command (this is just a property on SomeDbObject):

public IQueryable<AnotherDbObject> QueryableSomeNavigationProperty
{
    get
    {
        return SomeStaticContext.AnotherDbObjects.Where(x => x.ForeignKeyForSomeDbObject == this.Id);
    }
}

So, as you can see, this basically does what the Navigation property would have done, but it just creates the command in expression-treeable format, so that subsequent clauses will be built into the SQL command. For example, the same statement as before, would now return an IQueryable upon which we can add a Where clause:

SomeDbObject.QueryableSomeNavigationProperty.Where(x => x.IsSomeBool);

Now, the question is, what happens if I want to query another navigation property in the where clause. For example:

SomeDbObject.QueryableSomeNavigationProperty.Where(x => SomeDbObject.AnotherNavigationProperty.Any());

So, do I need to make another method that returns an IQueryable for SomeDbObject.AnotherNavigationProperty? Or, does EF do the right thing here and build it into a SQL statement?

I can clarify, if needed, but I think this covers the gist of what I am looking for.

Thanks!

twitchax
  • 815
  • 6
  • 14
  • 1
    What proof do you have that the above two statements are done in memory? – SnareChops Oct 04 '14 at 08:24
  • Searching for this question on StackOverflow. I don't have the links at the moment. – twitchax Oct 04 '14 at 08:38
  • Check out IntelliTrace to see what your EF querie s are doing – Nick Strupat Oct 06 '14 at 06:11
  • I advice everyone to put this line in their DataContext constructor this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s); Of course wrap it in #if DEBUG – Stilgar Oct 06 '14 at 06:19
  • How was SomeNavigationProperty being defined to begin with? – scott.korin Oct 06 '14 at 16:06
  • A full linq statement would be helpful. I'd used navigation properties plenty of times and they are executed in SQL instead of in memory. Also, do you have lazy loading turned on or not? is SomeDBObject already a realized or is this in a LINQ statment? – scott.korin Oct 06 '14 at 16:42
  • SomeDbObject is already realized. Lazy loading is turned on. – twitchax Oct 06 '14 at 19:17
  • What do you mean by a full LINQ statement? You mean, a query-like syntax LINQ statement? – twitchax Oct 06 '14 at 19:17
  • I don't think `SomeDbObject.QueryableSomeNavigationProperty.Where(x => SomeDbObject.AnotherNavigationProperty.Any());` would actually execute. You'd probably get a runtime error. And I'm not sure what the point would be of doing that. – scott.korin Oct 07 '14 at 03:30
  • Why would that not execute? – twitchax Oct 07 '14 at 06:10
  • I'm just going to take @NickStrupat 's suggestion and let everyone know what the results are... – twitchax Oct 07 '14 at 06:14
  • 1
    ["Explicit-loading with DbContext"](http://www.entityframeworktutorial.net/EntityFramework4.3/explicit-loading-with-dbcontext.aspx) and ["Include() vs Load()"](http://stackoverflow.com/q/19319116/419348) may be helpful. – AechoLiu Oct 07 '14 at 08:30
  • Yes, the explicit-loading is very helpful, thanks! – twitchax Oct 07 '14 at 08:41

1 Answers1

1

Ok, everyone. I ran a bunch of simulations and the verdict is in. See the comments for results in each scenario. Comments denote when the SQL came spitting out! :)

Hope this helps the next poor soul who is confused by what EF6 does at what point!

class Program
{
    private static readonly Action<string> DebugWriteLine = s => System.Diagnostics.Debug.WriteLine(s);
    private static readonly Action<string> WriteLine = s => { System.Console.WriteLine(s); DebugWriteLine(s); };

    static void Main(string[] args)
    {
        Statics.Entities.Database.Log = WriteLine;

        WhereClauseOnSimpleProperty();
        WhereClauseOnNavigationProperty();
        WhereClauseOnICollection();
        WhereClauseOnIQueryable();
        WhereClauseOnIQueryableWithIQueryable();

        System.Console.ReadKey();
    }

    static void WhereClauseOnSimpleProperty()
    {
        WriteLine("Get objects with a where clause (simple property).");
        WriteLine("    Calling: var users = entities.Users.Where(u => u.FirstName == \"Julie\");");
        var users = Statics.Entities.Users.Where(u => u.FirstName == "Julie");
        WriteLine("    Calling: users.ToList();");
        var usersList = users.ToList();
        // SQL got built and called here (NOTE: SQL call is not made until the data needs to be "realized"):
        /* SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Uin] AS [Uin], 
            [Extent1].[ClientId] AS [ClientId], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent1].[LastName] AS [LastName]
        FROM [dbo].[Users] AS [Extent1]
        WHERE 'Julie' = [Extent1].[FirstName]
        */
        WriteLine("    There are " + usersList.Count + " users.");
    }

    static void WhereClauseOnNavigationProperty()
    {
        WriteLine("Get objects with a where clause (1-to-many navigation property).");
        WriteLine("    Calling: var users = Entities.Users.Where(u => u.FirstName == \"Julie\" && u.Votes.Any());");
        var users = Statics.Entities.Users.Where(u => u.FirstName == "Julie" && u.Votes.Any());
        WriteLine("    Calling: users.ToList();");
        var usersList = users.ToList();
        // SQL got built and called here (NOTE: using the ICollection navigation property on the lambda parameter "u" builds just one SQL statement):
        /* SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Uin] AS [Uin], 
            [Extent1].[ClientId] AS [ClientId], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent1].[LastName] AS [LastName]
        FROM [dbo].[Users] AS [Extent1]
        WHERE ('Julie' = [Extent1].[FirstName]) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Votes] AS [Extent2]
            WHERE [Extent1].[Id] = [Extent2].[UserId]
        ))
        */
        WriteLine("    There are " + usersList.Count + " users.");
    }

    static void WhereClauseOnICollection()
    {
        WriteLine("Get objects with a where clause (simple property) from an ICollection.");
        WriteLine("    Calling: var users = Entities.Users.First(u => u.FirstName == \"Julie\" && u.Votes.Any());");
        var user = Statics.Entities.Users.First(u => u.FirstName == "Julie" && u.Votes.Any());
        // SQL got built and called here (NOTE: data is realized immediately because we are allocating a single object):
        /* SELECT TOP (1) 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Uin] AS [Uin], 
            [Extent1].[ClientId] AS [ClientId], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent1].[LastName] AS [LastName]
        FROM [dbo].[Users] AS [Extent1]
        WHERE ('Julie' = [Extent1].[FirstName]) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Votes] AS [Extent2]
        WHERE [Extent1].[Id] = [Extent2].[UserId]
        ))
        */
        WriteLine("    Calling: var votes = user.Votes.AsQueryable().Where(v => v.VoteValue > 0);");
        var votes = user.Votes.AsQueryable().Where(v => v.VoteValue > 0);
        // SQL got built and called here (NOTE: there "where" clause is executed in app memory/time [it's not in the SQL call]):
        /* SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[UserId] AS [UserId], 
            [Extent1].[VoteValue] AS [VoteValue]
        FROM [dbo].[Votes] AS [Extent1]
        WHERE [Extent1].[UserId] = @EntityKeyValue1
        */
        WriteLine("    Calling: votes.ToList();");
        var votesList = votes.ToList();
        WriteLine("    There are " + votesList.Count + " votes.");
    }

    static void WhereClauseOnIQueryable()
    {
        WriteLine("Get objects with a where clause (1-to-many navigation property) from an IQueryable.");
        WriteLine("    Calling: var users = Entities.Users.First(u => u.FirstName == \"Julie\" && u.Votes.Any());");
        var user = Statics.Entities.Users.First(u => u.FirstName == "Julie" && u.Votes.Any());
        // SQL got built and called here:
        /* SELECT TOP (1) 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Uin] AS [Uin], 
            [Extent1].[ClientId] AS [ClientId], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent1].[LastName] AS [LastName]
        FROM [dbo].[Users] AS [Extent1]
        WHERE ('Julie' = [Extent1].[FirstName]) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Votes] AS [Extent2]
        WHERE [Extent1].[Id] = [Extent2].[UserId]
        ))
        */
        WriteLine("    Calling: var votes = user.QueryableVotes.Where(v => user.Votes.AsQueryable().Contains(v));");
        var votes = user.QueryableVotes.Where(v => user.Votes.AsQueryable().Contains(v));
        // SQL got built and called here (NOTE: this is just the "user.Votes.AsQueryable().Contains(v)" part of the query):
        /* SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[UserId] AS [UserId], 
            [Extent1].[VoteValue] AS [VoteValue]
        FROM [dbo].[Votes] AS [Extent1]
        WHERE [Extent1].[UserId] = @EntityKeyValue1
        */
        WriteLine("    Calling: votes.ToList();");
        var votesList = votes.ToList();
        // NOTE: EF6 dies here because it had already computed "user.Votes.Contains(v)" (see above), and that can't go into the query.
        WriteLine("    There are " + votesList.Count + " votes.");
    }

    static void WhereClauseOnIQueryableWithIQueryable()
    {
        WriteLine("Get objects with a where clause (1-to-many navigation property as an IQueryable) from an IQueryable.");
        WriteLine("    Calling: var users = Entities.Users.First(u => u.FirstName == \"Julie\" && u.Votes.Any());");
        var user = Statics.Entities.Users.First(u => u.FirstName == "Julie" && u.Votes.Any());
        // SQL got built and called here:
        /* SELECT TOP (1) 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Uin] AS [Uin], 
            [Extent1].[ClientId] AS [ClientId], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent1].[LastName] AS [LastName]
        FROM [dbo].[Users] AS [Extent1]
        WHERE ('Julie' = [Extent1].[FirstName]) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Votes] AS [Extent2]
        WHERE [Extent1].[Id] = [Extent2].[UserId]
        ))
        */
        WriteLine("    Calling: var votes = user.QueryableVotes.Where(v => user.QueryableVotes.Contains(v));");
        var votes = user.QueryableVotes.Where(v => user.QueryableVotes.Contains(v)); // Yes, I know this is reduntant...just making sure the SQL looks right.
        WriteLine("    Calling: votes.ToList();");
        var votesList = votes.ToList();
        // SQL got built and called here (NOTE: making all expressions true IQueryables will build the "correct" [one call to rule them all] SQL expression):
        /* SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[UserId] AS [UserId], 
            [Extent1].[VoteValue] AS [VoteValue]
        FROM [dbo].[Votes] AS [Extent1]
        WHERE ([Extent1].[UserId] = @p__linq__0) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Votes] AS [Extent2]
            WHERE ([Extent2].[UserId] = @p__linq__1) AND ([Extent2].[Id] = [Extent1].[Id])
        ))
        */
        WriteLine("    There are " + votesList.Count + " votes.");
    }

    // SPECIAL NOTE: The clauses should follow these guidelines:
    /*
        * 1. If the condition operates on the lambda parameter, then use the ICollection navigation property to achieve one statement.
        *      For example: var user = Statics.Entities.Users.First(u => u.FirstName == "Julie" && u.Votes.Any());
        * 2. If the condition operates on a "non-navigation" property of the lambda parameter, then use the IQueryable expression to acheive one statement.
        *      For example: var votes = user.QueryableVotes.Where(v => user.QueryableVotes.Contains(v));
    */
}

public partial class User
{
    public IQueryable<Vote> QueryableVotes
    {
        get
        {
            return Statics.Entities.Votes.Where(v => v.UserId == this.Id);
        }
    }
}
twitchax
  • 815
  • 6
  • 14
  • 1
    Also, I verified all of these with LINQPad, which gives similar results: http://www.linqpad.net/. – twitchax Oct 07 '14 at 08:39