簡體   English   中英

如何刷新 Entity Framework Core DBContext?

[英]How to refresh an Entity Framework Core DBContext?

當我的表被另一方更新時,dotnet core 中的 db 上下文仍然返回舊值,如何強制刷新 db 上下文?

我已經做了研究,但我只發現人們使用Reload方法來強制刷新上下文,這在 EF 核心中不可用。

其他一些解決方案建議在使用后處理上下文,但我收到錯誤消息,說數據庫上下文是由依賴注入創建的,我不應該弄亂它。

哦,這個問題讓我糾結了好幾天。

我將 Visual Studio 2017 與 .Net Core 2.1 一起使用,我的 EF Core 代碼如下所示:

//  1.  Load a [User] record from our database 
int chosenUserID = 12345;
User usr = dbContext.Users.FirstOrDefault(s => s.UserID == chosenUserID);

//  2. Call a web service, which updates that [User] record
HttpClient client = new HttpClient()
await client.PostAsync("http://someUrl", someContent);

//  3. Attempt to load an updated copy of the [User] record
User updatedUser = dbContext.Users.FirstOrDefault(s => s.UserID == chosenUserID);

在第 3 步,它會簡單地將“updatedUser”設置為 [User] 記錄的原始版本,而不是嘗試加載新副本。 因此,如果在第 3 步之后我修改了 [User] 記錄,我實際上會丟失 Web 服務應用到它的任何設置。

我 - 最終 - 找到了兩個解決方案。

我可以更改ChangeTracker設置。 這有效,但我擔心這樣做的副作用:

dbContext.ChangeTracker.QueryTrackingBehavior = Microsoft.EntityFrameworkCore.QueryTrackingBehavior.NoTracking;

或者,我可以在嘗試重新加載 [User] 記錄之前輸入以下命令...

await dbContext.Entry(usr).ReloadAsync();

這似乎迫使 .Net Core 重新加載那個 [User] 記錄,生活又好了。

我希望這是有用的...

追蹤並修復這個錯誤花了我幾天的時間......

還有描述不同的方式來解決這個緩存的問題一個很好的文章 在這里

依賴注入和 DbContext

您提到當您嘗試重新創建DbContext ,您會收到有關由您的依賴注入 (DI) 系統管理的上下文的錯誤。 使用依賴注入系統創建對象有兩種不同的風格。 DI 可以創建一個在所有消費者之間作為服務共享的全局單例實例,或者它可以為每個工作范圍/工作單元(例如,每個 Web 服務器中的請求)創建一個實例。

如果您的 DI 系統配置為創建DbContext的單個全局共享實例,那么您將遇到與長期存在的DbContext相關的各種問題。

  • DbContext在設計上永遠不會自動從其緩存中刪除對象,因為它不是設計為長期存在的。 因此,長期存在的DbContext將浪費地保留內存。
  • 如果不手動重新加載它加載的每個實體,您的代碼將永遠不會看到加載到其緩存中的項目的更改。
  • DbContext只允許在任何時候運行一個查詢並且不是線程安全的。 如果您嘗試在全局共享實例上運行多個查詢,它將拋出DbConcurrencyException (至少在其異步接口上,不確定其同步接口)。

因此,最佳實踐是為每個工作單元使用單個DbContext 您的 DI 系統可以通過配置為您的應用程序在一個范圍內處理的每個請求提供一個新實例來幫助您解決這個問題。 例如,ASP.NET Core 的依賴注入系統支持按請求對實例進行范圍界定。

刷新單個實體

獲取新數據的最簡單方法是創建一個新的DbContext 但是,在您的工作單元內,或在您的 DI 系統提供的范圍粒度的約束內,您可能會觸發一個外部流程,該流程應該直接在數據庫中修改您的實體。 在退出 DI 的范圍或完成工作單元之前,您可能需要看到該更改。 在這種情況下,您可以通過分離數據對象的實例來強制重新加載。

為此,首先為您的對象獲取EntityEntry<> 這是一個對象,它允許您DbContext對象操作DbContext的內部緩存。 然后,您可以通過將EntitytState.Detached分配給其State屬性來將此條目標記為分離。 我相信這DbContext條目留在緩存中,但會導致DbContext在將來實際加載條目時刪除並替換它。 重要的是它會導致未來加載將新加載的實體實例返回到您的代碼。 例如:

var thing = context.Things.Find(id);
if (thing.ShouldBeSentToService) {
    TriggerExternalServiceAndWait(id);

    // Detach the object to remove it from context’s cache.
    context.Entities(thing).State = EntityState.Detached;

    // Then load it. We will get a new object with data
    // freshly loaded from the database.
    thing = context.Things.Find(id);
}
UseSomeOtherData(thing.DataWhichWasUpdated);

ReloadReloadAsyncEntity Framework Core 1.1開始可用

例子:

//test.Name is test1
var test = dbContext.Tests.FirstOrDefault();
test.Name = "test2";

//test.Name is now test1 again
dbContext.Entry(test).Reload();

https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetracking.entityentry.reload?view=efcore-1.1

隨着 .NET 5.0 和 Entity Framework Core 5.0 的發布,推薦的模式是使用DBContext factory 在 Statup.cs 我改變了:

services.AddDbContext<MyDbContext>...

services.AddDbContextFactory<MyDbContext>...

我所有的存儲庫類都使用相同的基類,在這里我在構造函數中創建上下文:

protected BaseRepository(IDbContextFactory<MyDbContext> contextFactory)
{
    _context = contextFactory.CreateDbContext();
}

我使用一個非常簡單的存儲庫工廠來確保每次需要時都能獲得存儲庫和 dbcontext 的新實例:

using System;
using Data.Models.Entities;
using Microsoft.Extensions.DependencyInjection;

namespace Data.Repository
{
    public class RepositoryFactory : IRepositoryFactory
    {
        private readonly IServiceProvider _serviceProvider;

        public RepositoryFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        
        public IApplicationRepository BuildApplicationRepository()
        {
            var service = _serviceProvider.GetService<IApplicationRepository>();

            return service;
        }
    }
}

使用描述的模式解決了“DB 上下文是由依賴注入創建的”錯誤,並且不需要 Reload()/Refresh() 方法。

您必須detach實體與上下文detach ,或者為.Reload()實現您自己的擴展

這是.Reload()實現。 來源: https : //weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core

public static TEntity Reload<TEntity>(this DbContext context, TEntity entity) where TEntity : class
{
    return context.Entry(entity).Reload();
}

public static TEntity Reload<TEntity>(this EntityEntry<TEntity> entry) where TEntity : class
{
    if (entry.State == EntityState.Detached)
    {
        return entry.Entity;
    }

    var context = entry.Context;
    var entity = entry.Entity;
    var keyValues = context.GetEntityKey(entity);

    entry.State = EntityState.Detached;

    var newEntity = context.Set<TEntity>().Find(keyValues);
    var newEntry = context.Entry(newEntity);

    foreach (var prop in newEntry.Metadata.GetProperties())
    {
        prop.GetSetter().SetClrValue(entity, 
        prop.GetGetter().GetClrValue(newEntity));
    }

    newEntry.State = EntityState.Detached;
    entry.State = EntityState.Unchanged;

    return entry.Entity;
}

其中GetEntityKey()

public static object[] GetEntityKey<T>(this DbContext context, T entity) where T : class
{
    var state = context.Entry(entity);
    var metadata = state.Metadata;
    var key = metadata.FindPrimaryKey();
    var props = key.Properties.ToArray();

    return props.Select(x => x.GetGetter().GetClrValue(entity)).ToArray();
}

這個簡單的序列刷新了 EFCore 5.0 下的整個上下文:

public void Refresh()
{
    (Context as DbContext).Database.CloseConnection();
    (Context as DbContext).Database.OpenConnection();
}

以上是我的解決方法,希望對你有幫助。

  • 分離函數將分離所有嵌套實體和實體數組。
  • findByIdAsync 將有一個可選參數來分離實體並重新加載它。

存儲庫

    public void Detach(TEntity entity)
    {
        foreach (var entry in _ctx.Entry(entity).Navigations)
        {
            if (entry.CurrentValue is IEnumerable<IEntity> children)
            {
                foreach (var child in children)
                {
                    _ctx.Entry(child).State = EntityState.Detached;
                }
            }
            else if (entry.CurrentValue is IEntity child)
            {
                _ctx.Entry(child).State = EntityState.Detached;
            }
        }
        _ctx.Entry(entity).State = EntityState.Detached;
    }

例如:

課程:

public interface IEntity : IEntity<Guid>
{
}

public interface IEntity<TPrimaryKey>
{
    [JsonProperty("id")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    TPrimaryKey Id { get; set; }
}

public class Sample : IEntity
{
    public Guid Id { get; set; }
    public string Text { get; private set; }   
    public Guid? CreatedByUserId { get; set; }
    public virtual User CreatedByUser { get; set; }     
    public List<SampleItem> SampleItems { get; set; } = new List<SampleItem>();
}

public class SampleItem : IEntity
{
    public Guid Id { get; set; }
    public string Text { get; private set; }   
    public Guid? CreatedByUserId { get; set; }
    public virtual User CreatedByUser { get; set; }     
}

經理

    public async Task<Sample> FindByIdAsync(Guid id, bool includeDeleted = false, bool forceRefresh = false)
    {
        var result = await GetAll()
            .Include(s => s.SampleItems)
            .IgnoreQueryFilters(includeDeleted)
            .FirstOrDefaultAsync(s => s.Id == id);

        if (forceRefresh)
        {
            _sampleRepository.Detach(result);
            return await FindByIdAsync(id, includeDeleted);
        }

        return result;
    }

控制器

    SampleManager.FindByIdAsync(id, forceRefresh: true);

理想情況下,另一方會在進行更新時通知您。 這可以通過消息隊列來實現。

其中包括:Azure 服務總線、Rabbit MQ、0MQ

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM