【问题标题】:Entity Framework 6: Are Navigation Properties Smart?实体框架 6:导航属性是否智能?
【发布时间】:2014-10-04 08:17:21
【问题描述】:

所以,我已经知道像这样的代码可以在内存中完成所有操作:

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 语句中?

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

谢谢!

【问题讨论】:

  • 你有什么证据证明以上两条语句是在内存中完成的?
  • 在 StackOverflow 上搜索这个问题。我暂时没有链接。
  • 查看 IntelliTrace 以了解您的 EF 查询在做什么
  • 我建议大家将这一行放在他们的 DataContext 构造函数中 this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);当然将它包装在#if DEBUG

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


【解决方案1】:

好的,大家好。我进行了一堆模拟,结果就出来了。请参阅 cmets 了解每种情况下的结果。注释表示 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);
        }
    }
}

【讨论】:

  • 另外,我用 LINQPad 验证了所有这些,得到了类似的结果:linqpad.net
猜你喜欢
  • 1970-01-01
  • 2014-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-29
相关资源
最近更新 更多