繁体   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