Entity framework 拥有 collections 超级慢
Entity framework is super slow with owned collections
这些是我的模型:
帐号
public enum AccountType { Company, User }
public class Account
{
[Key, Column(Order=0)] //Composite key with AccountType
public long IdCompanyOrUser { get; set; }
[Key, Column(Order = 1)] //Composite key with IdCompanyOrUser
public AccountType AccountType { get; set; }
[JsonIgnore]
public string? Hash { get; set; }
[JsonIgnore]
public List<RefreshToken>? RefreshTokens { get; set; }
[JsonIgnore]
public List<LoginAttempt>? LoginAttempts { get; set; }
}
刷新令牌
[Owned]
public class RefreshToken
{
[Key]
[JsonIgnore]
public long Id { get; set; }
public string Token { get; set; }
public DateTime Expires { get; set; }
public DateTime Created { get; set; }
public DateTime? Revoked { get; set; }
public string? RevokedBy { get; set; }
public string? ReasonRevoked { get; set; }
public bool IsExpired => DateTime.UtcNow >= Expires;
public bool IsRevoked => Revoked != null;
public bool IsActive => !IsRevoked && !IsExpired;
}
登录尝试
public enum FailReason { WrongUserOrPassword, UserInactive, CompanyExpired, CountryRestriction, NoRoleAssigned }
[Owned]
public class LoginAttempt
{
[Key]
[JsonIgnore]
public long Id { get; set; }
public DateTime Time { get; set; }
public bool Success { get; set; }
public string Ip { get; set; }
public FailReason? FailReason { get; set; }
}
}
这是我的数据库上下文
public class AuthDbContext : DbContext
{
public AuthDbContext(DbContextOptions<AuthDbContext> options) : base(options) { }
public DbSet<Account> Account { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>().HasKey(table => new {
table.IdCompanyOrUser,
table.AccountType
});
}
}
当我运行这个查询
Account? account = FindSingleOrDefault(x => x.IdCompanyOrUser == id & (int)x.AccountType == accountTypeInt);
我正在使用设计模式存储库,所以这会调用
protected readonly DbContext Context;
private DbSet<TEntity> _entities;
public Repository(DbContext context)
{
Context = context;
_entities = context.Set<TEntity>();
}
public TEntity FindSingleOrDefault(Expression<Func<TEntity, bool>> predicate) => _entities.SingleOrDefault(predicate);
该请求最多需要 3 秒来检索帐户以及大约 3000 个刷新令牌和 200 次登录尝试。对于这么少量的记录,我觉得太长了。
我正在检查我的日志,这是 EF 核心从我的表达式翻译的查询。 (93和0是我填的,这些是参数)。我直接在 pgadmin 中测试了它,它花费的时间大致相同。
SELECT t."IdCompanyOrUser", t."AccountType", t."Hash", l."Id", l."AccountIdCompanyOrUser", l."AccountType", l."FailReason", l."Ip", l."Success", l."Time", r."Id", r."AccountIdCompanyOrUser", r."AccountType", r."Created", r."Expires", r."ReasonRevoked", r."Revoked", r."RevokedBy", r."Token"
FROM (
SELECT a."IdCompanyOrUser", a."AccountType", a."Hash"
FROM "Account" AS a
WHERE (a."IdCompanyOrUser" = 93) AND (a."AccountType" = 0)
LIMIT 2
) AS t
LEFT JOIN "LoginAttempt" AS l ON (t."IdCompanyOrUser" = l."AccountIdCompanyOrUser") AND (t."AccountType" = l."AccountType")
LEFT JOIN "RefreshToken" AS r ON (t."IdCompanyOrUser" = r."AccountIdCompanyOrUser") AND (t."AccountType" = r."AccountType")
ORDER BY t."IdCompanyOrUser", t."AccountType", l."Id"
我在我的数据库上测试了两个单独的查询以获得登录尝试,另一个查询用于刷新令牌,它们非常快(60 毫秒)。 有人知道为什么这么慢吗?有什么办法可以优化吗?
我想保留当前的实现方式,其中刷新令牌和登录尝试由帐户 [owned]
进行。这是我第一次使用拥有的实体类型,所以我不确定我是否正确实施它以及我是否理解它们的使用方式。如果可能的话,我将不胜感激一些反馈和帮助:)
PS: 我用的是ET core 6.
通常预加载会引入 master-detail 记录的笛卡尔爆炸。 EF Core 将所有内容加载到内存中,然后提供记录去重功能。如果记录数很大,可能会导致性能急剧下降。
从 EF Core 5 开始,有运算符 AsSplitQuery()
改变了这种行为并通过单独的查询加载 master-details 记录。
进一步阅读:Split queries
这些是我的模型:
帐号
public enum AccountType { Company, User }
public class Account
{
[Key, Column(Order=0)] //Composite key with AccountType
public long IdCompanyOrUser { get; set; }
[Key, Column(Order = 1)] //Composite key with IdCompanyOrUser
public AccountType AccountType { get; set; }
[JsonIgnore]
public string? Hash { get; set; }
[JsonIgnore]
public List<RefreshToken>? RefreshTokens { get; set; }
[JsonIgnore]
public List<LoginAttempt>? LoginAttempts { get; set; }
}
刷新令牌
[Owned]
public class RefreshToken
{
[Key]
[JsonIgnore]
public long Id { get; set; }
public string Token { get; set; }
public DateTime Expires { get; set; }
public DateTime Created { get; set; }
public DateTime? Revoked { get; set; }
public string? RevokedBy { get; set; }
public string? ReasonRevoked { get; set; }
public bool IsExpired => DateTime.UtcNow >= Expires;
public bool IsRevoked => Revoked != null;
public bool IsActive => !IsRevoked && !IsExpired;
}
登录尝试
public enum FailReason { WrongUserOrPassword, UserInactive, CompanyExpired, CountryRestriction, NoRoleAssigned }
[Owned]
public class LoginAttempt
{
[Key]
[JsonIgnore]
public long Id { get; set; }
public DateTime Time { get; set; }
public bool Success { get; set; }
public string Ip { get; set; }
public FailReason? FailReason { get; set; }
}
}
这是我的数据库上下文
public class AuthDbContext : DbContext
{
public AuthDbContext(DbContextOptions<AuthDbContext> options) : base(options) { }
public DbSet<Account> Account { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>().HasKey(table => new {
table.IdCompanyOrUser,
table.AccountType
});
}
}
当我运行这个查询
Account? account = FindSingleOrDefault(x => x.IdCompanyOrUser == id & (int)x.AccountType == accountTypeInt);
我正在使用设计模式存储库,所以这会调用
protected readonly DbContext Context;
private DbSet<TEntity> _entities;
public Repository(DbContext context)
{
Context = context;
_entities = context.Set<TEntity>();
}
public TEntity FindSingleOrDefault(Expression<Func<TEntity, bool>> predicate) => _entities.SingleOrDefault(predicate);
该请求最多需要 3 秒来检索帐户以及大约 3000 个刷新令牌和 200 次登录尝试。对于这么少量的记录,我觉得太长了。
我正在检查我的日志,这是 EF 核心从我的表达式翻译的查询。 (93和0是我填的,这些是参数)。我直接在 pgadmin 中测试了它,它花费的时间大致相同。
SELECT t."IdCompanyOrUser", t."AccountType", t."Hash", l."Id", l."AccountIdCompanyOrUser", l."AccountType", l."FailReason", l."Ip", l."Success", l."Time", r."Id", r."AccountIdCompanyOrUser", r."AccountType", r."Created", r."Expires", r."ReasonRevoked", r."Revoked", r."RevokedBy", r."Token"
FROM (
SELECT a."IdCompanyOrUser", a."AccountType", a."Hash"
FROM "Account" AS a
WHERE (a."IdCompanyOrUser" = 93) AND (a."AccountType" = 0)
LIMIT 2
) AS t
LEFT JOIN "LoginAttempt" AS l ON (t."IdCompanyOrUser" = l."AccountIdCompanyOrUser") AND (t."AccountType" = l."AccountType")
LEFT JOIN "RefreshToken" AS r ON (t."IdCompanyOrUser" = r."AccountIdCompanyOrUser") AND (t."AccountType" = r."AccountType")
ORDER BY t."IdCompanyOrUser", t."AccountType", l."Id"
我在我的数据库上测试了两个单独的查询以获得登录尝试,另一个查询用于刷新令牌,它们非常快(60 毫秒)。 有人知道为什么这么慢吗?有什么办法可以优化吗?
我想保留当前的实现方式,其中刷新令牌和登录尝试由帐户 [owned]
进行。这是我第一次使用拥有的实体类型,所以我不确定我是否正确实施它以及我是否理解它们的使用方式。如果可能的话,我将不胜感激一些反馈和帮助:)
PS: 我用的是ET core 6.
通常预加载会引入 master-detail 记录的笛卡尔爆炸。 EF Core 将所有内容加载到内存中,然后提供记录去重功能。如果记录数很大,可能会导致性能急剧下降。
从 EF Core 5 开始,有运算符 AsSplitQuery()
改变了这种行为并通过单独的查询加载 master-details 记录。
进一步阅读:Split queries