We have two Entity Framework queries, one with Include one with standalone query. Here they are
ConfigModelContainer model = new ConfigModelContainer();
var scope = model.Scopes.Include("Settings")
.Where(s => (s.Level == intLevel && s.Name == name))
.First();
ConfigModelContainer model = new ConfigModelContainer();
var scope = model.Scopes
.Where(s => (s.Level == intLevel && s.Name == name))
.First();
var settings = model.Settings.Where(s => s.Scope.Id == scope.Id).ToList();
one more case that has same performance as first one (Query2)
var scope1 = model.Scopes
.Where(s => (s.Level == intLevel && s.Name == name))
.First();
scope1.Settings.Load();
First one runs for 30 seconds, second runs for sub-second. This is so weird, that I have no ideas.
Does anyone know why this might happen?
Edit: Actual TSQL queries run very fast (subsecond)
Edit 2: Here are queries:
First:
SELECT
[Project2].[Level] AS [Level],
[Project2].[Id] AS [Id],
[Project2].[Name] AS [Name],
[Project2].[ParentScope_Id] AS [ParentScope_Id],
[Project2].[C1] AS [C1],
[Project2].[Id1] AS [Id1],
[Project2].[Type] AS [Type],
[Project2].[Value] AS [Value],
[Project2].[Scope_Id] AS [Scope_Id]
FROM ( SELECT
[Limit1].[Id] AS [Id],
[Limit1].[Name] AS [Name],
[Limit1].[Level] AS [Level],
[Limit1].[ParentScope_Id] AS [ParentScope_Id],
[Extent2].[Id] AS [Id1],
[Extent2].[Type] AS [Type],
[Extent2].[Value] AS [Value],
[Extent2].[Scope_Id] AS [Scope_Id],
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM (SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Level] AS [Level],
[Extent1].[ParentScope_Id] AS [ParentScope_Id]
FROM [dbo].[Scopes] AS [Extent1]
WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1) ) AS [Limit1]
LEFT OUTER JOIN [dbo].[Settings] AS [Extent2] ON [Limit1].[Id] = [Extent2].[Scope_Id]
) AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC
Second:
SELECT
[Limit1].[Level] AS [Level],
[Limit1].[Id] AS [Id],
[Limit1].[Name] AS [Name],
[Limit1].[ParentScope_Id] AS [ParentScope_Id]
FROM ( SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Level] AS [Level],
[Extent1].[ParentScope_Id] AS [ParentScope_Id]
FROM [dbo].[Scopes] AS [Extent1]
WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1)
) AS [Limit1]
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Type] AS [Type],
[Extent1].[Value] AS [Value],
[Extent1].[Scope_Id] AS [Scope_Id]
FROM [dbo].[Settings] AS [Extent1]
WHERE [Extent1].[Scope_Id] = @EntityKeyValue1
Third:
SELECT
[Limit1].[Level] AS [Level],
[Limit1].[Id] AS [Id],
[Limit1].[Name] AS [Name],
[Limit1].[ParentScope_Id] AS [ParentScope_Id]
FROM ( SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Level] AS [Level],
[Extent1].[ParentScope_Id] AS [ParentScope_Id]
FROM [dbo].[Scopes] AS [Extent1]
WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1)
) AS [Limit1]
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Type] AS [Type],
[Extent1].[Value] AS [Value],
[Extent1].[Scope_Id] AS [Scope_Id]
FROM [dbo].[Settings] AS [Extent1]
WHERE [Extent1].[Scope_Id] = @p__linq__0
Edit 3:
I was not able to continue tests on same machine. Here are results on faster machine. Here is code and and results:
static void Main(string[] args)
{
int intLevel = 2;
string name = "fb226050-4f92-4fca-9442-f76565b33877";
Stopwatch sw = new Stopwatch();
using (CMEntities model = new CMEntities())
{
sw.Start();
for (int i = 0; i < 5; i++)
{
var scope1 = model.Scopes.Include("Settings")
.Where(s => (s.Level == intLevel && s.Name == name))
.First();
Console.WriteLine("Query:1, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
}
}
Console.WriteLine();
using (CMEntities model = new CMEntities())
{
sw.Start();
for (int i = 0; i < 5; i++)
{
var scope1 = model.Scopes
.Where(s => (s.Level == intLevel && s.Name == name))
.First();
scope1.Settings.Load();
Console.WriteLine("Query:2, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
}
}
Console.WriteLine();
using (CMEntities model = new CMEntities())
{
for (int i = 0; i < 5; i++)
{
var scope = model.Scopes
.Where(s => (s.Level == intLevel && s.Name == name))
.First();
var settings = model.Settings.Where(s => s.Scope.Id == scope.Id).ToList();
Console.WriteLine("Query:3, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
}
}
}
}
Results:
Query:1, Iter:0, Time:2477
Query:1, Iter:1, Time:1831
Query:1, Iter:2, Time:1933
Query:1, Iter:3, Time:1774
Query:1, Iter:4, Time:1949
Query:2, Iter:0, Time:2036
Query:2, Iter:1, Time:1870
Query:2, Iter:2, Time:1921
Query:2, Iter:3, Time:1751
Query:2, Iter:4, Time:1758
Query:3, Iter:0, Time:188
Query:3, Iter:1, Time:201
Query:3, Iter:2, Time:185
Query:3, Iter:3, Time:203
Query:3, Iter:4, Time:217
Edit 4: I rewrote the code using NHibernate:
static void Main(string[] args)
{
var cfg = new StoreConfiguration();
var sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString("Data Source=.;Initial Catalog=CM;Integrated Security=True;MultipleActiveResultSets=True")
)
.Mappings(m =>
m.AutoMappings.Add(
AutoMap.AssemblyOf<Entities.Scope>(cfg)
.Conventions
.Add(
Table.Is(x => x.EntityType.Name + "s"),
PrimaryKey.Name.Is(x => "Id"),
ForeignKey.EndsWith("_id")
)
)
)
.BuildSessionFactory();
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 5; i++)
{
sw.Start();
var session = sessionFactory.OpenSession();
int intLevel = 2;
string name = "fb226050-4f92-4fca-9442-f76565b33877";
var scope = session.CreateCriteria<Entities.Scope>()
.SetFetchMode("Settings", FetchMode.Eager)
.Add(Restrictions.Eq("Name", name))
.Add(Restrictions.Eq("Level", intLevel))
.UniqueResult<Entities.Scope>();
Console.WriteLine("Query:0, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
sw.Reset();
}
}
results are:
Query:0, Iter:0, Time:446
Query:0, Iter:1, Time:223
Query:0, Iter:2, Time:303
Query:0, Iter:3, Time:275
Query:0, Iter:4, Time:284
So NHibernate forms proper collection 10 times faster than EF. This is really sad.
Here is query generated by NHibernate:
SELECT this_.id AS id0_1_,
this_.name AS name0_1_,
this_.LEVEL AS level0_1_,
settings2_.scope_id AS scope4_3_,
settings2_.id AS id3_,
settings2_.id AS id1_0_,
settings2_.TYPE AS type1_0_,
settings2_.VALUE AS value1_0_,
settings2_.scope_id AS scope4_1_0_
FROM scopes this_
LEFT OUTER JOIN settings settings2_
ON this_.id = settings2_.scope_id
WHERE this_.name = @p0
AND this_.LEVEL = @p1
When you say that the actual TSQL queries run fast, are you talking about hand-coded queries?
Try using SQL Profiler to see what is being generated by EF 3.5. Perhaps this will show why the performance would be so different, and provide some insight into whether and how it would be possible to improve the performance of the first query.
Also, here are a couple of blog posts giving specific examples of how sql generation was improved in EF 4. Even if upgrading to EF 4 isn’t an option, they might provide food for thought.
Improvements to the Generated SQL in .NET 4.0 Beta1
Improvements to Generated SQL in .NET 4.0
Edit
Here’s the code I used to try and reproduce your results. This is using SQL Server 2008 R2, VS 2010 (no SP1) and Entity Framework 4.0. I had to guess at the schema; hopefully it’s close.
To create the tables and populate them:
Using a console application to test:
The EF model was created using the default settings, and updating the model from the database:
The sql sent from EF for the first query:
and for the second query:
and the output: