繁体   English   中英

延迟加载时无法访问 Dbcontext 之外的实体框架关系

[英]Can't access entity framework relations outside Dbcontext when Lazyloading

我有一个使用实体框架的 asp.net 应用程序。 我有这两个模型:

public class CustomerModel
{
        public int Id{get;set; }
        [Required]
        public string Name {get;set; }
        [Required]
        public string Surname { get; set; }
        [Required]
        [Range(18,110)]
        public uint Age { get; set; }
        [Required]
        public virtual AdressModel Adress { get; set; }
        [Required]
        public  DateTime Created { get; set; }
}

public class AdressModel
{
    public int Id { get; set; }
    [Required]
    public int HouseNumber { get; set; }
    [Required]
    public string Town { get; set; }
    [Required]
    public string ZipCode { get; set; }
    [Required]
    public string Country { get; set; }
}

还有一个如下所示的 dbcontext 类:

public class DemoContext : DbContext
{
    public  DbSet<CustomerModel> Customers { get; set; }
    public  DbSet<AdressModel> Adresses { get; set; }




    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseLazyLoadingProxies();
        options.UseSqlite(@"Data Source=/home/ask/RiderProjects/Parkz/identifier.sqlite");

    }

}

然后我有一个控制器,它只需要加载我在我的数据库中拥有的所有客户,以及他们的地址。

为此,我有这个:

public IActionResult sendhere()
{
    List<CustomerModel> customers = new List<CustomerModel>();

    using (var db = new DemoContext()) {
        customers = db.Customers 
            .Include(c => c.Adress) 
            .ToList(); 
    }
    
    return Content("hi");
}

我试图调试一下。

问题是,一旦我退出“使用”块,所有相关的地址对象仅包含此错误:

System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.LazyLoadOnDisposedContextWarning': An attempt was made to lazy-load navigation 'CustomerModelProxy.Adress' after the associated DbContext was disposed. This exception can be suppressed or logged by passing event ID 'CoreEventId.LazyLoadOnDisposedContextWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
   at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition`2.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, TParam1 arg1, TParam2 arg2)
   at Microsoft.EntityFrameworkCore.Diagnostics.CoreLoggerExtensions.LazyLoadOnDisposedContextWarning(IDiagnosticsLogger`1 diagnostics, DbContext context, Object entityType, String navigationName)
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.LazyLoader.ShouldLoad(Object entity, String navigationName, NavigationEntry& navigationEntry)
   at Microsoft.EntityFrameworkCore.Infrastruc…

这是因为我退出了上下文。

即使我退出了 Dbcontext,我如何仍然可以访问客户的地址,以便我可以将它们返回到视图

在使用 EF 时,我的一般建议是不应在其 DbContext 范围之外引用实体。 您当然可以使用分离的实体,但您必须尊重它们从离开 DbContext 范围的那一刻起就以“原样”状态提供。 出于这个原因,我建议在 DbContext 范围之外传递的任何内容都应该是 POCO ViewModel 或 DTO 类,以避免混淆实体类实际上是功能性的、表示数据域状态的附加实体,还是分离的外壳。

选项 1:处理 DTO/ViewModel。

public IActionResult sendhere()
{
    using (var db = new DemoContext()) {
        var customerDTOs = db.Customers 
            .Select(c => new CustomerDTO
            {
                 // fill in the fields you want here.
                 Addresses = c.Addresses.Select(a => new AddressDTO
                 {
                     // fill in the address fields you want here.
                 }).ToList()
            }).ToList(); 
        return View(customerDTOs);
    }
}

您也可以利用 Automapper,设置所需的投影规则并使用ProjectTo<CustomerDTO>(config)来替换上面对Select()的使用。

在利用投影时,您根本不需要延迟加载代理。 这可以说已成为 EF 推荐的事实上的方法。

投影方法的优点是这些 DTO(或 ViewModel)不能与实体混淆。 分离实体的问题在于,如果代码中有可能接受实体的方法,这些方法可能期望获取实体并访问未加载的成员。 如果它们被附加并且在 DbContext 的范围内,则这些成员可以被延迟加载(出于性能原因,这不是理想的,但功能性)但是如果它们被分离,则会出现错误或 NullRefExceptions。 投影的另一个优点是从数据库中提取并发送到视图逻辑或端的数据的有效负载仅包含所需的数据。

选项 2:不要取消 DbContext 的范围。 对于像 ASP.Net MVC Web 应用程序这样的项目,您可以利用 IoC 容器将依赖项注入到您的控制器中。 通过这种方式,您可以将 DbContext 设置为注入到构造函数中,并将生命周期范围设置为请求。 这样,对于任何给定的请求,您可能调用的所有服务/类都可以由容器管理并可以访问 DbContext。

public class SomeController
{
    private readonly DemoContext _context;

    public SomeController(DemoContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public IActionResult sendhere()
    {
        var customers = _context.Customers 
            .Include(c => c.Address) 
            .ToList(); 
    
        return View(customers);
    }
}

这可以与选项 1 结合使用,以避免需要为每个请求/操作限定 DbContext 的范围,并更好地促进您可能希望对 DbContext 进行多次调用并确保使用相同的上下文实例的情况。 对于 IoC 容器,有许多不同的可用容器,我相信 ASP.Net Core 带有一个默认容器,尽管我个人使用并推荐 Autofac。 它有很好的文档和示例,说明如何将它与 MVC 项目联系起来。

选项 3:急切加载您需要参考的所有内容。 您提供的示例实际上应该可以工作,但是您的真实代码可能缺少所需关系的急切加载( .Include() ),因为您的示例不会尝试对您加载的客户集合做任何事情。

如果您的代码是:

List<CustomerModel> customers = new List<CustomerModel>();

using (var db = new DemoContext()) {
    customers = db.Customers 
        .Include(c => c.Address) 
        .ToList(); 
}

var firstAddressId = customers.FirstOrDefault()?.Address.Id;

这应该可以工作,因为地址是急切加载的。 但是,如果您有:

List<CustomerModel> customers = new List<CustomerModel>();

using (var db = new DemoContext()) {
    customers = db.Customers 
        .ToList(); 
}

var firstAddressId = customers.FirstOrDefault()?.Address.Id;

...没有Include(c => c.Address) ,那么它将因该错误而失败。

使用 EF Core,如果您想要返回 DbContext 范围之外的实体并且您启用了延迟加载代理,您将需要暂时关闭代理创建以避免代理错误。 在这种情况下,您不急于加载的任何内容都将保留为 #null 或默认值。

List<CustomerModel> customers = new List<CustomerModel>();

using (var db = new DemoContext()) {
    db.ContextOptions.ProxyCreationEnabled = false;
    customers = db.Customers 
        .Include(c => c.Address) 
        .ToList(); 
}

Return View(customers);

这应确保 EF 不对 DbContext 实例范围内的查询使用代理,当您想要传递 DbContext 范围之外的实体时应避免使用代理。 当您知道不需要急切加载每个引用的开销时,这可能很有用。 但是,在这种情况下使用投影(选项 1)要好得多,以避免将来混淆实体是否实际上可能有 #null 数据,或者仅仅是它不是急切加载的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM