![](/img/trans.png)
[英]How to refresh Entity Framework Core DbContext but keep my property decorations?
[英]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
,您會收到有關由您的依賴注入 (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);
Reload
和ReloadAsync
從Entity 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();
隨着 .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();
}
以上是我的解決方法,希望對你有幫助。
存儲庫
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.