实体框架6:导航属性是否智能?

时间:2014-10-04 08:17:22

标签: c# sql entity-framework entity-framework-6

所以,我已经知道像这样的代码在内存中做了所有事情:

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

除此之外(仍在记忆中):

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

所以,我提出了一个友好的解决方案,可以帮助我确保整个调用将作为SQL命令执行(这只是SomeDbObject上的一个属性):

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

因此,正如您所看到的,这基本上完成了Navigation属性所做的事情,但它只是以表达式可树形格式创建命令,以便后续子句将构建到SQL命令中。例如,与之前相同的语句现在将返回一个IQueryable,我们可以在其上添加Where子句:

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

现在,问题是,如果我想在where子句中查询另一个导航属性会发生什么。例如:

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

那么,我是否需要为SomeDbObject.AnotherNavigationProperty创建另一个返回IQueryable的方法?或者,EF在这里做正确的事情并将其构建为SQL语句吗?

如果需要,我可以澄清,但我认为这涵盖了我所寻找的要点。

谢谢!

1 个答案:

答案 0 :(得分:1)

好的,大家好。我运行了一系列模拟并且判决结果正在进行中。请参阅每个方案中的结果注释。注释表示SQL何时吐出! :)

希望这有助于下一个对EF6在什么时候做什么感到困惑的可怜的灵魂!

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);
        }
    }
}