【问题标题】:Navigation Property Eagerly loading when Lazy Loading is turned on开启延迟加载时快速加载导航属性
【发布时间】:2020-02-09 07:20:17
【问题描述】:

当打开延迟加载时,我的导航属性似乎正在急切地加载。 我有一个这样的 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"
            });
        });

    }
}

我不希望加载导航属性“用户”,除非我使用“包含”。但它会自动加载。在我的启动中,我有这样的配置。

       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。我希望“用户”为空

【问题讨论】:

  • 延迟加载意味着导航属性一被调用就会自动加载。如果要使用“包含”,则必须使用急切加载而不是延迟加载
  • 所以我设置了 UseLazyLoadingProxies(false) 这意味着当我加载导航属性时不应该加载。但它正在加载。
  • 我刚刚用图片更新了问题。因为延迟加载设置为false。我希望“用户”为空
  • 尝试像这样添加用户数据:modelBuilder.Entity&lt;User&gt;(b =&gt; { b.HasData(new User { AccountId = "37846734-172e-4149-8cec-6f43d1eb3f60", Id = Guid.NewGuid().ToString(), AuthID = Guid.NewGuid().ToString(), Name = "Raas Masood", ProfilePicUrl = "https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50" })
  • 现在当我运行时用户为空但是当我包含用户“返回等待 DbSet.Include("User").ToListAsync();"它仍然是空的。 DbSet 设置为 _context()

标签: entity-framework entity-framework-core lazy-loading


【解决方案1】:

遵循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

【讨论】:

  • “检测到不支持的可能对象循环。这可能是由于循环或对象深度大于最大允许深度 32。”看起来模型结构中正在发生一些循环的东西。
  • 我创建了一个 github 存储库。就像一个魅力;-)我在我的回答中为你链接了它
【解决方案2】:

即使禁用延迟加载,如果 DbContext 已经在跟踪您引用的实体,该引用也会自动填充。

例如,如果我有 2 个父记录,每个记录都包含一个子记录:

父 1 => 子 1

父 2 => 子 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 表示已加载并跟踪父 ID#2 的 dbContext。在 DbContext 的生命周期中,这可能发生在我们调用之前的任何地方,尤其是在生命周期范围是整个请求的情况下。当我们稍后去检索我们的孩子时,假设延迟加载被关闭并且我们不急切加载他们的父母,你会发现孩子 ID #1 的父母引用将是#null,但 DbContext 会将 Parent #2 与孩子#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 不会跟踪该父引用。

随着应用程序的成熟和新代码的引入或重构,这可能会导致各种古怪的行为。只需有人将AsNoTracking() 添加为“性能优化”,并且以前引用某些内容的某些行为现在已被破坏。

作为避免复杂化的一般规则,我的建议是永远不要返回生成它们的 DbContext 范围之外的实体,而是依赖 ViewModel 或 DTO 来表示要传递给视图或返回到API 使用者,而不是为此目的尝试选择性地填充实体。实体应始终反映完整的数据状态,要么通过填充其所有数据,要么通过所有数据可访问(通过代理)。当不完整的实体被传递时,最终它们会进入需要完整实体的方法(基于方法签名),当假设数据是完整的但该实体的来源遗漏了一些东西时,这会导致问题。

【讨论】:

  • 在这种情况下,OData 可以达到目的。我们可以简单地返回 IQueryable 集合,其余的魔法将由客户端的 oData 完成。我们可以选择、包含和查询 oData 查询字符串中的所有内容。但我也明白你在说什么。我只是想专注于查询优化,以便我从数据库到 api 是一次轻松的旅行。
猜你喜欢
  • 2014-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 2011-08-11
相关资源
最近更新 更多