簡體   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