与非常相似的查询的EF性能差异很大

And*_*rey 5 .net entity-framework

我们有两个实体框架查询,一个有Include一个独立查询.他们来了

        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();
Run Code Online (Sandbox Code Playgroud)

一个与第一个具有相同性能的案例(Query2)

        var scope1 = model.Scopes
            .Where(s => (s.Level == intLevel && s.Name == name))
            .First();
        scope1.Settings.Load();
Run Code Online (Sandbox Code Playgroud)

第一个运行30秒,第二个运行亚秒.这太奇怪了,我没有想法.

有谁知道为什么会这样?

编辑:实际TSQL查询运行速度非常快(亚秒)

编辑2:以下是查询:

第一:

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
Run Code Online (Sandbox Code Playgroud)

第二:

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
Run Code Online (Sandbox Code Playgroud)

第三:

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
Run Code Online (Sandbox Code Playgroud)

编辑3:

我无法在同一台机器上继续测试.这是更快的机器上的结果.这是代码和结果:

    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();
            }                
        }
    }
    }
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

编辑4:我使用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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

所以NHibernate形成适当的收集速度比EF快10倍.这真的很难过.

这是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 
Run Code Online (Sandbox Code Playgroud)

Jef*_*ata 1

当您说实际的 TSQL 查询运行得很快时,您是在谈论手动编码的查询吗?

尝试使用 SQL Profiler 查看 EF 3.5 生成的内容。也许这将说明为什么性能会如此不同,并提供一些关于是否以及如何可以提高第一个查询的性能的见解。

另外,这里有几篇博客文章提供了如何在 EF 4 中改进 SQL 生成的具体示例。即使升级到 EF 4 不是一个选择,它们也可能提供一些值得深思的内容。

.NET 4.0 Beta1 中生成的 SQL 的改进

.NET 4.0 中生成的 SQL 的改进

编辑

这是我用来尝试重现您的结果的代码。这是使用 SQL Server 2008 R2、VS 2010(无 SP1)和 Entity Framework 4.0。我必须猜测这个模式;希望它很接近。

要创建表并填充它们:

set nocount on

create table Scopes
(
    [Id]                int identity primary key,
    [Level]             int,
    [Name]              nvarchar(50),
    [ParentScope_Id]    int foreign key references Scopes(Id)
)
create table Settings
(
    [Id]            int identity primary key,
    [Type]          nvarchar(20),
    [Value]         nvarchar(50),
    [Scope_Id]      int foreign key references Scopes(Id)   
)
go

declare @scopeId int,
        @scopeCount int,
        @settingCount int,
        @value nvarchar(50)

set @scopeCount = 0

while @scopeCount < 10
begin   
    insert into Scopes([Level], [Name]) values(1, 'Scope ' + cast(@scopeCount as nvarchar))
    select @scopeId = @@IDENTITY
    set @settingCount = 0

    while @settingCount < 10000
    begin
        set @value = 'Setting ' + cast(@scopeId as nvarchar) + '.' + cast(@settingCount as nvarchar)
        insert into Settings([Type], [Value], [Scope_Id]) values ('Test', @value, @scopeId)
        set @settingCount = @settingCount + 1
    end

    set @scopeCount = @scopeCount + 1
end
Run Code Online (Sandbox Code Playgroud)

使用控制台应用程序进行测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace so_q5205281
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new EFTestEntities())
            {
                int level = 1;
                string name = "Scope 4";

                ExecQuery1(context, level, name);
                ExecQuery1(context, level, name);
                ExecQuery1(context, level, name);

                ExecQuery2(context, level, name);
                ExecQuery2(context, level, name);
                ExecQuery2(context, level, name);
            }

            Console.ReadLine();
        }

        static void ExecQuery1(EFTestEntities context, int level, string name)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();

            var scope = context.Scopes.Include("Settings")
                .Where(s => s.Level == level && s.Name == name)
                .First();

            int settingsCount = scope.Settings.Count();

            stopwatch.Stop();

            Console.WriteLine("Query 1, scope name: {0}, settings count: {1}, seconds {2}", scope.Name, settingsCount, stopwatch.Elapsed.TotalSeconds);
        }

        static void ExecQuery2(EFTestEntities context, int level, string name)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();

            var scope = context.Scopes
                .Where(s => s.Level == level && s.Name == name)
                .First();

            var settings = context.Settings.Where(s => s.Scope.Id == scope.Id).ToList();

            int settingsCount = scope.Settings.Count();

            stopwatch.Stop();

            Console.WriteLine("Query 2, scope name: {0}, settings count: {1}, seconds {2}", scope.Name, settingsCount, stopwatch.Elapsed.TotalSeconds);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

EF 模型是使用默认设置创建的,并从数据库更新模型:

在此输入图像描述

EF 发送的第一个查询的 sql:

exec sp_executesql N'SELECT 
[Project2].[Id] AS [Id], 
[Project2].[Level] AS [Level], 
[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].[Level] AS [Level], 
    [Limit1].[Name] AS [Name], 
    [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].[Level] AS [Level], 
        [Extent1].[Name] AS [Name], 
        [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',N'@p__linq__0 int,@p__linq__1 nvarchar(4000)',@p__linq__0=1,@p__linq__1=N'Scope 4'
Run Code Online (Sandbox Code Playgroud)

对于第二个查询:

exec sp_executesql N'SELECT TOP (1) 
[Extent1].[Id] AS [Id], 
[Extent1].[Level] AS [Level], 
[Extent1].[Name] AS [Name], 
[Extent1].[ParentScope_Id] AS [ParentScope_Id]
FROM [dbo].[Scopes] AS [Extent1]
WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 nvarchar(4000)',@p__linq__0=1,@p__linq__1=N'Scope 4'

exec sp_executesql N'SELECT 
[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',N'@p__linq__0 int',@p__linq__0=5
Run Code Online (Sandbox Code Playgroud)

和输出:

Query 1, scope name: Scope 4, settings count: 10000, seconds 0.6657546
Query 1, scope name: Scope 4, settings count: 10000, seconds 0.1608498
Query 1, scope name: Scope 4, settings count: 10000, seconds 0.1097625
Query 2, scope name: Scope 4, settings count: 10000, seconds 0.0742593
Query 2, scope name: Scope 4, settings count: 10000, seconds 0.0551458
Query 2, scope name: Scope 4, settings count: 10000, seconds 0.0555465
Run Code Online (Sandbox Code Playgroud)