[英]Entity Framework 6: Are Navigation Properties Smart?
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): 因此,我想出了一个友好的解决方案,可以帮助我确保整个调用将作为SQL命令执行(这只是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. 因此,正如您所看到的,这基本上完成了Navigation属性将要做的事情,但是它只是以可表达表达式树格式创建命令,因此后续子句将内置到SQL命令中。 For example, the same statement as before, would now return an IQueryable upon which we can add a Where clause:
例如,与以前相同的语句现在将返回一个IQueryable,我们可以在其上添加Where子句:
SomeDbObject.QueryableSomeNavigationProperty.Where(x => x.IsSomeBool);
Now, the question is, what happens if I want to query another navigation property in the where clause. 现在的问题是,如果我要查询where子句中的另一个导航属性,会发生什么情况。 For example:
例如:
SomeDbObject.QueryableSomeNavigationProperty.Where(x => SomeDbObject.AnotherNavigationProperty.Any());
So, do I need to make another method that returns an IQueryable for SomeDbObject.AnotherNavigationProperty? 因此,我是否需要使另一个方法为SomeDbObject.AnotherNavigationProperty返回IQueryable? Or, does EF do the right thing here and build it into a SQL statement?
或者,EF是否在这里做正确的事并将其构建为SQL语句?
I can clarify, if needed, but I think this covers the gist of what I am looking for. 如果需要,我可以澄清一下,但是我认为这涵盖了我所寻找的要点。
Thanks! 谢谢!
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!
注释表示SQL何时出现! :)
:)
Hope this helps the next poor soul who is confused by what EF6 does at what point! 希望这可以帮助下一个可怜的灵魂,他在什么时候对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);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.