导航 属性 打开延迟加载时立即加载
Navigation Property Eagerly loading when Lazy Loading is turned on
看起来我的导航 属性 在延迟加载打开时正在急切加载。
我有一个像这样的 DBContext 设置
public class BBBankContext : DbContext
{
public BBBankContext(DbContextOptions<BBBankContext> options)
: base(options)
{ }
public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>(b =>
{
b.HasData(new Account
{
Id = "37846734-172e-4149-8cec-6f43d1eb3f60",
AccountNumber = "0001-1001",
AccountTitle = "Raas Masood",
CurrentBalance = 2342.34,
Email = "raasmasood@hotmail.com",
PhoneNumber = "6096647000",
AccountStatus = AccountStatus.Active
});
b.OwnsOne(e => e.User).HasData(new
{
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
Id = Guid.NewGuid().ToString(),
AuthID = Guid.NewGuid().ToString(),
Name = "Raas Masood",
ProfilePicUrl = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50"
});
});
}
}
我不想加载导航 属性 "User",除非我使用 "Include"。但它会自动加载。在我的启动中,我有这样的配置。
services.AddDbContext<BBBankContext>(
b => b.UseSqlServer(connection)
.UseLazyLoadingProxies(false) //Install-Package Microsoft.EntityFrameworkCore.Proxies -Version 3.1.1
);
实体看起来像这样
public class Account : BaseEntity
{
public string AccountNumber { get; set; }
public string AccountTitle { get; set; }
public double CurrentBalance { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public AccountStatus AccountStatus { get; set; }
public virtual User User { get; set; }
}
public enum AccountStatus
{
Active = 0,
InActive = 1
}
public class User : BaseEntity
{
public string AuthID { get; set; }
public string Name { get; set; }
public string ProfilePicUrl { get; set; }
}
public class BaseEntity
{
[Key]
public string Id { get; set; }
}
因为延迟加载设置为 false。我希望 "user" 为空
按照 Microsoft EF Core instructions 这是我的方法(我另外将 Key 属性 类型更改为 Guid):
public class BaseEntity
{
[Key]
public Guid Id { get; set; }
}
public class Account : BaseEntity
{
public string AccountNumber { get; set; }
public string AccountTitle { get; set; }
public double CurrentBalance { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public AccountStatus AccountStatus { get; set; }
public virtual User User { get; set; }
public Guid UserId { get; set; }
}
public class User : BaseEntity
{
public string AuthID { get; set; }
public string Name { get; set; }
public string ProfilePicUrl { get; set; }
public virtual Account Account { get; set; }
}
public enum AccountStatus
{
Active = 0,
InActive = 1
}
public class BBBankContext : DbContext
{
public BBBankContext(DbContextOptions<BBBankContext> options)
: base(options)
{ }
public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>(b =>
{
b.HasData(new Account
{
Id = Guid.Parse("37846734-172e-4149-8cec-6f43d1eb3f60"),
AccountNumber = "0001-1001",
AccountTitle = "Raas Masood",
CurrentBalance = 2342.34,
Email = "raasmasood@hotmail.com",
PhoneNumber = "6096647000",
AccountStatus = AccountStatus.Active,
UserId = Guid.Parse("24ce7f8a-cbeb-4b33-8a3d-952830b92d04")
});
b.HasOne(a => a.User)
.WithOne(u => u.Account)
.HasForeignKey<Account>(a => a.UserId);
});
modelBuilder.Entity<User>(b =>
{
b.HasData(new User
{
Id = Guid.Parse("24ce7f8a-cbeb-4b33-8a3d-952830b92d04"),
AuthID = Guid.NewGuid().ToString(),
Name = "Raas Masood",
ProfilePicUrl = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50"
});
});
}
}
您可以找到工作存储库at GitHub
即使禁用延迟加载,如果 DbContext 已经在跟踪您引用的实体,该引用也会自动填充。
例如,如果我有 2 Parent 条记录,每条记录包含一条 Child 条记录:
Parent 1 => Child 1
Parent 2 => Child 2
using (var context = new AppContext())
{
var junk = context.Parents.Single(x => x.ParentId == 2);
var children = context.Children.ToList();
Assert.IsNull(children.Single(x => x.ChildId == 1).Parent);
Assert.IsNotNull(children.Single(x => x.ChildId == 2).Parent);
}
这是一个非常粗略的行为示例,但 junk
表示 dbContext 已加载并跟踪 Parent ID#2。在 DbContext 的生命周期中,这可能发生在我们调用之前的任何地方,尤其是在生命周期范围涵盖整个请求的情况下。当我们稍后去检索我们的 Children 时,假设关闭了延迟加载并且我们不急于加载他们的 parents,你会发现 Child ID #1 的 parent 引用将为 #null,但 DbContext 会将 Parent #2 与 Child #2 相关联。当上下文填充 Child #2 引用时,它会看到与 parent 的关联并需要 Parent ID #2,它的本地跟踪缓存知道 Parent #2,因此引用自动填充。
如果 junk
行改为:
var junk = context.Parents.AsNoTracking().Single(x => x.ParentId == 2);
然后 Child #2 的 Parent 引用也将是 #null,因为 DbContext 不会跟踪那个 parent 引用。
随着应用程序的成熟和引入新代码或 re-factored,这可能会导致各种古怪的行为。所需要的只是有人将 AsNoTracking()
添加为 "performance optimization",并且以前引用某物的某些行为现在已损坏。
作为避免复杂化的一般规则,我的建议是永远不要 return 实体超出生成它们的 DbContext 的范围,而是依赖 ViewModel 或 DTO 来表示您要传递给的数据视图或 return 给 API 消费者,而不是尝试为此目的有选择地填充实体。一个实体应该始终反映一个完整的数据状态,要么通过填充它的所有数据,要么通过所有数据访问(通过代理)。当不完整的实体四处传递时,它们最终会进入需要完整实体的方法(基于方法签名),这会导致在假设数据是完整的但实体的来源遗漏了一些东西时出现问题。
看起来我的导航 属性 在延迟加载打开时正在急切加载。 我有一个像这样的 DBContext 设置
public class BBBankContext : DbContext
{
public BBBankContext(DbContextOptions<BBBankContext> options)
: base(options)
{ }
public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>(b =>
{
b.HasData(new Account
{
Id = "37846734-172e-4149-8cec-6f43d1eb3f60",
AccountNumber = "0001-1001",
AccountTitle = "Raas Masood",
CurrentBalance = 2342.34,
Email = "raasmasood@hotmail.com",
PhoneNumber = "6096647000",
AccountStatus = AccountStatus.Active
});
b.OwnsOne(e => e.User).HasData(new
{
AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60",
Id = Guid.NewGuid().ToString(),
AuthID = Guid.NewGuid().ToString(),
Name = "Raas Masood",
ProfilePicUrl = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50"
});
});
}
}
我不想加载导航 属性 "User",除非我使用 "Include"。但它会自动加载。在我的启动中,我有这样的配置。
services.AddDbContext<BBBankContext>(
b => b.UseSqlServer(connection)
.UseLazyLoadingProxies(false) //Install-Package Microsoft.EntityFrameworkCore.Proxies -Version 3.1.1
);
实体看起来像这样
public class Account : BaseEntity
{
public string AccountNumber { get; set; }
public string AccountTitle { get; set; }
public double CurrentBalance { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public AccountStatus AccountStatus { get; set; }
public virtual User User { get; set; }
}
public enum AccountStatus
{
Active = 0,
InActive = 1
}
public class User : BaseEntity
{
public string AuthID { get; set; }
public string Name { get; set; }
public string ProfilePicUrl { get; set; }
}
public class BaseEntity
{
[Key]
public string Id { get; set; }
}
因为延迟加载设置为 false。我希望 "user" 为空
按照 Microsoft EF Core instructions 这是我的方法(我另外将 Key 属性 类型更改为 Guid):
public class BaseEntity
{
[Key]
public Guid Id { get; set; }
}
public class Account : BaseEntity
{
public string AccountNumber { get; set; }
public string AccountTitle { get; set; }
public double CurrentBalance { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public AccountStatus AccountStatus { get; set; }
public virtual User User { get; set; }
public Guid UserId { get; set; }
}
public class User : BaseEntity
{
public string AuthID { get; set; }
public string Name { get; set; }
public string ProfilePicUrl { get; set; }
public virtual Account Account { get; set; }
}
public enum AccountStatus
{
Active = 0,
InActive = 1
}
public class BBBankContext : DbContext
{
public BBBankContext(DbContextOptions<BBBankContext> options)
: base(options)
{ }
public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Account>(b =>
{
b.HasData(new Account
{
Id = Guid.Parse("37846734-172e-4149-8cec-6f43d1eb3f60"),
AccountNumber = "0001-1001",
AccountTitle = "Raas Masood",
CurrentBalance = 2342.34,
Email = "raasmasood@hotmail.com",
PhoneNumber = "6096647000",
AccountStatus = AccountStatus.Active,
UserId = Guid.Parse("24ce7f8a-cbeb-4b33-8a3d-952830b92d04")
});
b.HasOne(a => a.User)
.WithOne(u => u.Account)
.HasForeignKey<Account>(a => a.UserId);
});
modelBuilder.Entity<User>(b =>
{
b.HasData(new User
{
Id = Guid.Parse("24ce7f8a-cbeb-4b33-8a3d-952830b92d04"),
AuthID = Guid.NewGuid().ToString(),
Name = "Raas Masood",
ProfilePicUrl = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50"
});
});
}
}
您可以找到工作存储库at GitHub
即使禁用延迟加载,如果 DbContext 已经在跟踪您引用的实体,该引用也会自动填充。
例如,如果我有 2 Parent 条记录,每条记录包含一条 Child 条记录:
Parent 1 => Child 1
Parent 2 => Child 2
using (var context = new AppContext())
{
var junk = context.Parents.Single(x => x.ParentId == 2);
var children = context.Children.ToList();
Assert.IsNull(children.Single(x => x.ChildId == 1).Parent);
Assert.IsNotNull(children.Single(x => x.ChildId == 2).Parent);
}
这是一个非常粗略的行为示例,但 junk
表示 dbContext 已加载并跟踪 Parent ID#2。在 DbContext 的生命周期中,这可能发生在我们调用之前的任何地方,尤其是在生命周期范围涵盖整个请求的情况下。当我们稍后去检索我们的 Children 时,假设关闭了延迟加载并且我们不急于加载他们的 parents,你会发现 Child ID #1 的 parent 引用将为 #null,但 DbContext 会将 Parent #2 与 Child #2 相关联。当上下文填充 Child #2 引用时,它会看到与 parent 的关联并需要 Parent ID #2,它的本地跟踪缓存知道 Parent #2,因此引用自动填充。
如果 junk
行改为:
var junk = context.Parents.AsNoTracking().Single(x => x.ParentId == 2);
然后 Child #2 的 Parent 引用也将是 #null,因为 DbContext 不会跟踪那个 parent 引用。
随着应用程序的成熟和引入新代码或 re-factored,这可能会导致各种古怪的行为。所需要的只是有人将 AsNoTracking()
添加为 "performance optimization",并且以前引用某物的某些行为现在已损坏。
作为避免复杂化的一般规则,我的建议是永远不要 return 实体超出生成它们的 DbContext 的范围,而是依赖 ViewModel 或 DTO 来表示您要传递给的数据视图或 return 给 API 消费者,而不是尝试为此目的有选择地填充实体。一个实体应该始终反映一个完整的数据状态,要么通过填充它的所有数据,要么通过所有数据访问(通过代理)。当不完整的实体四处传递时,它们最终会进入需要完整实体的方法(基于方法签名),这会导致在假设数据是完整的但实体的来源遗漏了一些东西时出现问题。