可以针对性能改进此实体框架查询吗?

时间:2017-11-16 17:06:03

标签: c# sql-server entity-framework

*更新*

我无法删除这个问题,即使从长远来看它实际上不是一个问题...我仍然有一些代码,当我正在处理它做了单独的查询并填充其中的一些字段...当我删除该代码时......以下查询运行速度非常快(2秒):

public IQueryable<ApplicationUser> QueriableUsersList()
{
    return _context.Users.OrderBy(u => u.UserName)           
        .Include(u => u.Accounts.Select(a => a.Broker))
        .Include(u => u.Roles);
}

*原始问题*

我正在尝试使用实体框架加载用户列表(大约600个)...我想预先加载每个用户的查询帐户记录和每个帐户的代理记录(以及每个用户的角色)...这个SQL很快就能得到结果(&lt; 1秒):

select ISNULL(ur.RoleId, 0) as IsAdmin, u.*, a.*, b.* 
  from AspNetUsers u
 inner join Accounts a on u.Id = a.ClientId
 inner join Brokers b on a.Brokerid = b.BrokerId
 left outer join AspNetUserRoles ur on u.id = ur.UserId and ur.RoleId = 1
order by u.UserName

但是这个实体框架查询需要很长时间(大概20到25秒)...尤其是在数据库位于不同服务器上的生产中:

public IQueryable<ApplicationUser> QueriableUsersList()
{
    return _context.Users.OrderBy(u => u.UserName)           
        .Include(u => u.Accounts.Select(a => a.Broker))
        .Include(u => u.Roles);
}

关于如何改进实体框架查询的任何想法和/或如何使它像原生SQL查询一样更快? (注意:我确实需要所有上述对象的所有数据用于我的列表,所以延迟加载不会有帮助。)我希望它会生成一个单独的查询,就像我在上面显示的那样...但它正在生成看似每个用户的几个查询(下面)。

我最终还想使用这个IQueryable来获取分页数据。

我的对象定义如下:

public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>/
{
    public List<Account> Accounts { get; set; }
…
    public bool isAdmin { get {  return this.Roles != null && this.Roles.Any(r => r.RoleId == 1); } }
…
}

public class Account
{
    public int AccountId { get; set; }
…
    public int BrokerId { get; set; }
    public virtual Broker Broker { get; set; }
…
}

public class Broker
{
    public int BrokerId { get; set; }
    public string Name { get; set; }
…
}

使用SQL Server探查器进行实体框架查询,我看到其中一个:

SELECT 
[Project1].[Id] AS [Id], 
[Project1].[UserName] AS [UserName], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[LastName] AS [LastName], 
[Project1].[PhoneNumber] AS [PhoneNumber], 
[Project1].[Email] AS [Email], 
[Project1].[Address] AS [Address], 
[Project1].[City] AS [City], 
[Project1].[State] AS [State], 
[Project1].[Zip] AS [Zip], 
[Project1].[DateAdded] AS [DateAdded], 
[Project1].[IsActive] AS [IsActive], 
[Project1].[C1] AS [C1], 
[Project1].[AccountId] AS [AccountId], 
[Project1].[ClientId] AS [ClientId], 
[Project1].[BrokerId] AS [BrokerId], 
[Project1].[AccountNumber] AS [AccountNumber], 
[Project1].[BrokerUserName] AS [BrokerUserName], 
[Project1].[BrokerPasswordHash] AS [BrokerPasswordHash], 
[Project1].[EquityCurve] AS [EquityCurve], 
[Project1].[CapitalInvested] AS [CapitalInvested], 
[Project1].[IsSimulated] AS [IsSimulated], 
[Project1].[ClosedProfit] AS [ClosedProfit], 
[Project1].[MarketValueTimeStamp] AS [MarketValueTimeStamp], 
[Project1].[IsAuthorizedForLiveTrading] AS [IsAuthorizedForLiveTrading], 
[Project1].[ActivatedOn] AS [ActivatedOn]
FROM ( SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[LastName] AS [LastName], 
    [Extent1].[Address] AS [Address], 
    [Extent1].[Zip] AS [Zip], 
    [Extent1].[City] AS [City], 
    [Extent1].[State] AS [State], 
    [Extent1].[DateAdded] AS [DateAdded], 
    [Extent1].[IsActive] AS [IsActive], 
    [Extent1].[Email] AS [Email], 
    [Extent1].[PhoneNumber] AS [PhoneNumber], 
    [Extent1].[UserName] AS [UserName], 
    [Extent2].[AccountId] AS [AccountId], 
    [Extent2].[ClientId] AS [ClientId], 
    [Extent2].[BrokerId] AS [BrokerId], 
    [Extent2].[AccountNumber] AS [AccountNumber], 
    [Extent2].[BrokerUserName] AS [BrokerUserName], 
    [Extent2].[BrokerPasswordHash] AS [BrokerPasswordHash], 
    [Extent2].[EquityCurve] AS [EquityCurve], 
    [Extent2].[CapitalInvested] AS [CapitalInvested], 
    [Extent2].[IsSimulated] AS [IsSimulated], 
    [Extent2].[ClosedProfit] AS [ClosedProfit], 
    [Extent2].[MarketValueTimeStamp] AS [MarketValueTimeStamp], 
    [Extent2].[IsAuthorizedForLiveTrading] AS [IsAuthorizedForLiveTrading], 
    [Extent2].[ActivatedOn] AS [ActivatedOn], 
    CASE WHEN ([Extent2].[AccountId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM  [dbo].[AspNetUsers] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Accounts] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ClientId]
)  AS [Project1]
ORDER BY [Project1].[UserName] ASC, [Project1].[Id] ASC, [Project1].[C1] ASC

然后看似每个用户中的一个:

SELECT TOP (1) 
[Extent1].[BrokerId] AS [BrokerId], 
[Extent1].[Name] AS [Name], 
[Extent1].[Code] AS [Code], 
[Extent1].[IsExternal] AS [IsExternal]
FROM [dbo].[Brokers] AS [Extent1]
WHERE [Extent1].[BrokerId] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=2

还为每个用户提供了其中一个:

SELECT 
[Project2].[Id] AS [Id], 
[Project2].[ReferralId] AS [ReferralId], 
[Project2].[FirstName] AS [FirstName], 
[Project2].[LastName] AS [LastName], 
[Project2].[Address] AS [Address], 
[Project2].[Address2] AS [Address2], 
[Project2].[Zip] AS [Zip], 
[Project2].[City] AS [City], 
[Project2].[StateProvince] AS [StateProvince], 
[Project2].[Country] AS [Country], 
[Project2].[State] AS [State], 
[Project2].[startPopupChecked] AS [startPopupChecked], 
[Project2].[DateAdded] AS [DateAdded], 
[Project2].[CurrentPortfolioId] AS [CurrentPortfolioId], 
[Project2].[NotificationsEmailAddress] AS [NotificationsEmailAddress], 
[Project2].[NotificationMobileNumber] AS [NotificationMobileNumber], 
[Project2].[ReceivesEmailNotifications] AS [ReceivesEmailNotifications], 
[Project2].[ReceivesTextNotifications] AS [ReceivesTextNotifications], 
[Project2].[IsActive] AS [IsActive], 
[Project2].[HasSeen] AS [HasSeen], 
[Project2].[Email] AS [Email], 
[Project2].[EmailConfirmed] AS [EmailConfirmed], 
[Project2].[PasswordHash] AS [PasswordHash], 
[Project2].[SecurityStamp] AS [SecurityStamp], 
[Project2].[PhoneNumber] AS [PhoneNumber], 
[Project2].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed], 
[Project2].[TwoFactorEnabled] AS [TwoFactorEnabled], 
[Project2].[LockoutEndDateUtc] AS [LockoutEndDateUtc], 
[Project2].[LockoutEnabled] AS [LockoutEnabled], 
[Project2].[AccessFailedCount] AS [AccessFailedCount], 
[Project2].[UserName] AS [UserName], 
[Project2].[BlockOrder_BlockOrderId] AS [BlockOrder_BlockOrderId], 
[Project2].[C1] AS [C1], 
[Project2].[UserId] AS [UserId], 
[Project2].[RoleId] AS [RoleId]
FROM ( SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[ReferralId] AS [ReferralId], 
    [Limit1].[FirstName] AS [FirstName], 
    [Limit1].[LastName] AS [LastName], 
    [Limit1].[Address] AS [Address], 
    [Limit1].[Address2] AS [Address2], 
    [Limit1].[Zip] AS [Zip], 
    [Limit1].[City] AS [City], 
    [Limit1].[StateProvince] AS [StateProvince], 
    [Limit1].[Country] AS [Country], 
    [Limit1].[State] AS [State], 
    [Limit1].[startPopupChecked] AS [startPopupChecked], 
    [Limit1].[DateAdded] AS [DateAdded], 
    [Limit1].[CurrentPortfolioId] AS [CurrentPortfolioId], 
    [Limit1].[NotificationsEmailAddress] AS [NotificationsEmailAddress], 
    [Limit1].[NotificationMobileNumber] AS [NotificationMobileNumber], 
    [Limit1].[ReceivesEmailNotifications] AS [ReceivesEmailNotifications], 
    [Limit1].[ReceivesTextNotifications] AS [ReceivesTextNotifications], 
    [Limit1].[IsActive] AS [IsActive], 
    [Limit1].[HasSeen] AS [HasSeen], 
    [Limit1].[Email] AS [Email], 
    [Limit1].[EmailConfirmed] AS [EmailConfirmed], 
    [Limit1].[PasswordHash] AS [PasswordHash], 
    [Limit1].[SecurityStamp] AS [SecurityStamp], 
    [Limit1].[PhoneNumber] AS [PhoneNumber], 
    [Limit1].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed], 
    [Limit1].[TwoFactorEnabled] AS [TwoFactorEnabled], 
    [Limit1].[LockoutEndDateUtc] AS [LockoutEndDateUtc], 
    [Limit1].[LockoutEnabled] AS [LockoutEnabled], 
    [Limit1].[AccessFailedCount] AS [AccessFailedCount], 
    [Limit1].[UserName] AS [UserName], 
    [Limit1].[BlockOrder_BlockOrderId] AS [BlockOrder_BlockOrderId], 
    [Extent2].[UserId] AS [UserId], 
    [Extent2].[RoleId] AS [RoleId], 
    CASE WHEN ([Extent2].[UserId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM   (SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[ReferralId] AS [ReferralId], 
        [Extent1].[FirstName] AS [FirstName], 
        [Extent1].[LastName] AS [LastName], 
        [Extent1].[Address] AS [Address], 
        [Extent1].[Address2] AS [Address2], 
        [Extent1].[Zip] AS [Zip], 
        [Extent1].[City] AS [City], 
        [Extent1].[StateProvince] AS [StateProvince], 
        [Extent1].[Country] AS [Country], 
        [Extent1].[State] AS [State], 
        [Extent1].[startPopupChecked] AS [startPopupChecked], 
        [Extent1].[DateAdded] AS [DateAdded], 
        [Extent1].[CurrentPortfolioId] AS [CurrentPortfolioId], 
        [Extent1].[NotificationsEmailAddress] AS [NotificationsEmailAddress], 
        [Extent1].[NotificationMobileNumber] AS [NotificationMobileNumber], 
        [Extent1].[ReceivesEmailNotifications] AS [ReceivesEmailNotifications], 
        [Extent1].[ReceivesTextNotifications] AS [ReceivesTextNotifications], 
        [Extent1].[IsActive] AS [IsActive], 
        [Extent1].[HasSeen] AS [HasSeen], 
        [Extent1].[Email] AS [Email], 
        [Extent1].[EmailConfirmed] AS [EmailConfirmed], 
        [Extent1].[PasswordHash] AS [PasswordHash], 
        [Extent1].[SecurityStamp] AS [SecurityStamp], 
        [Extent1].[PhoneNumber] AS [PhoneNumber], 
        [Extent1].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed], 
        [Extent1].[TwoFactorEnabled] AS [TwoFactorEnabled], 
        [Extent1].[LockoutEndDateUtc] AS [LockoutEndDateUtc], 
        [Extent1].[LockoutEnabled] AS [LockoutEnabled], 
        [Extent1].[AccessFailedCount] AS [AccessFailedCount], 
        [Extent1].[UserName] AS [UserName], 
        [Extent1].[BlockOrder_BlockOrderId] AS [BlockOrder_BlockOrderId]
        FROM [dbo].[AspNetUsers] AS [Extent1]
        WHERE [Extent1].[Id] = @p__linq__0 ) AS [Limit1]
    LEFT OUTER JOIN [dbo].[AspNetUserRoles] AS [Extent2] ON [Limit1].[Id] = [Extent2].[UserId]
)  AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC',N'@p__linq__0 int',@p__linq__0=346

5 个答案:

答案 0 :(得分:1)

  

使用SQL Server探查器进行实体框架查询,我看到其中一个:

     

然后看似每个用户中的一个:

     

还为每个用户提供了其中一个:

这是因为你最有可能做一个组合;不执行查询,实体框架启用了延迟加载(我建议您关闭)并在循环中访问导航属性。 It looks very much like the Entity-Framework N+1 Problem

未执行查询:

var nonexecuted = _context.Users.OrderBy(u => u.UserName)           
    .Include(u => u.Accounts.Select(a => a.Broker))
    .Include(u => u.Roles);

执行查询:

var executed = _context.Users.OrderBy(u => u.UserName)           
    .Include(u => u.Accounts.Select(a => a.Broker))
    .Include(u => u.Roles)
    .ToList() // .AsEnumerable() etc...

如果您执行以下操作,则执行未执行的查询:

var maxIndex = 4;
for(idx = 0; idx < maxIndex; idx ++)
{
  nonexecuted.Users.Skip(idx).Accounts.First()
}

将执行maxIndex次查询,因为它是加载导航属性的延迟加载。

我强烈建议禁用延迟加载。它将迫使您创建只需要您想要的好查询。

Depending on the number of Accounts, Users and Roles you have, you could be running into a Cartesian Product problem as well

答案 1 :(得分:0)

你加入3个表,从表中获取所有数据,我认为你没有任何索引列,而数据库在另一个服务器上

1)过滤记录,因为您不需要真正的所有记录

2)只选择您需要的列,而不是选择返回所有列数据的u。,a。

3)为您的数据库添加一个索引列(对于大多数时候出现在where子句中的列,您应该使用索引列)

并检查数据库服务器上的查询执行时间,看它不是网络延迟

答案 2 :(得分:0)

您是否跟踪过第二个语句产生的SQL?看起来它将加载所有用户,帐户,经纪人和角色,而无需进行任何您想要的加入和过滤。如果这些表很大,这可能需要时间。

(编辑:对不起,我刚注意到你已经做了这个。)

认为如果你想像你的第一个语句那样产生一些SQL,那么你将不得不使用linq。如果不能访问您的模型,很难确切知道您需要什么,但我怀疑它看起来像是:

var users = 
    from user in context.Users
    join acc in Accounts on user.ID equals acc.ClientId
    join broker in Brokers on acc.BrokerId equals broker.brokerid
    join r in Roles on user.id equals r.userid into roles
    from role in roles.DefaultIfEmpty()
    where role.RoleId == 1
    select user;   // or whatever you want to select

return users.ToList();

棘手的部分是左外连接,我现在无法检查我的语法,但我希望这会有所帮助。

答案 3 :(得分:0)

您可以使用.FromSql并传入您想要的查询。或者您可以使用封装查询的视图(当然,除非使用EF Core)。

老实说,如果表现是一个问题,那么使用DapperChain之类的东西要好得多。即使是基本的CRUD操作EF is significantly slower than the other two

答案 4 :(得分:0)

我无法删除这个问题,即使从长远来看它实际上不是一个问题...我仍然有一些代码,当我正在处理它做了单独的查询并填充其中的一些字段...当我删除该代码时......以下查询运行速度非常快(2秒):

public IQueryable<ApplicationUser> QueriableUsersList()
{
    return _context.Users.OrderBy(u => u.UserName)           
        .Include(u => u.Accounts.Select(a => a.Broker))
        .Include(u => u.Roles);
}