*更新*
我无法删除这个问题,即使从长远来看它实际上不是一个问题...我仍然有一些代码,当我正在处理它做了单独的查询并填充其中的一些字段...当我删除该代码时......以下查询运行速度非常快(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
答案 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
次查询,因为它是加载导航属性的延迟加载。
我强烈建议禁用延迟加载。它将迫使您创建只需要您想要的好查询。
答案 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)。
老实说,如果表现是一个问题,那么使用Dapper或Chain之类的东西要好得多。即使是基本的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);
}