如何刷新Entity Framework Core DBContext?

时间:2017-09-13 19:06:52

标签: c# .net-core entity-framework-core

当我的表由另一方更新时,dotnet核心中的db上下文仍然返回旧值,如何强制Db上下文刷新?

我已经完成了研究,但我发现人们只使用Reload方法(EF核心中没有这种方法)来强制刷新上下文。

其他一些解决方案建议在使用后处理上下文,但是我得到错误,说DB上下文是通过依赖注入创建的,我不应该搞砸它。

8 个答案:

答案 0 :(得分:7)

依赖注入和DbContext

您提到,当您尝试重新创建DbContext时,会收到有关由依赖项注入(DI)系统管理的上下文的错误。使用依赖项注入系统创建对象有两种不同的样式。 DI可以创建一个在所有使用者之间共享的服务的全局单例实例,也可以按工作范围/单位(例如,Web服务器中的每个请求)创建一个实例。

如果将DI系统配置为创建DbContext的单个全局共享实例,那么您将遇到与寿命长的DbContext相关的各种问题。根据设计,DbContext永远不会自动从其缓存中删除对象,因为它的目的不是长期存在。因此,寿命长的DbContext将浪费内存。另外,如果不手动重新加载其加载的每个实体,您的代码将永远不会看到加载到其缓存中的项的更改。因此,the best practice is to use a single DbContext per unit of work。您的DI系统可以通过配置为在范围内为您的应用程序处理的每个请求提供一个新实例来帮助您。例如,ASP.NET Core的依赖注入系统支持按请求对实例进行范围界定。

刷新单个实体

获取新数据的最简单方法是创建一个新的DbContext。但是,在您的工作单元中,或在DI系统提供的范围界定的范围内,您可能会触发一个外部过程,该过程应该直接在数据库中修改您的实体。您可能需要先看到所做的更改,然后才能退出DI的范围或完成工作单元。在这种情况下,您可以通过分离数据对象的实例来强制重新加载。

要执行此操作,请首先为您的对象获取EntityEntry<>。该对象使您可以操纵该对象的DbContext内部缓存。然后,您可以通过将EntitytState.Detached设置为其State属性来将其标记为分离。我相信这会将条目保留在缓存中,但会导致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);

答案 1 :(得分:6)

哦,这个问题困扰了我好几天。

我将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]记录,并且生活再次美好。

我希望这是有用的...

跟踪并修复此错误花了我几天的时间。...

还有一篇很棒的文章描述了解决此缓存问题here的各种方法。

答案 2 :(得分:3)

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

答案 3 :(得分:2)

这个简单的序列刷新了 EFCore 5.0 下的整个上下文:

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

答案 4 :(得分:1)

您必须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();
}

答案 5 :(得分:0)

这是我的解决方案,希望对您有所帮助。

  • detach函数将分离所有嵌套的实体和实体数组。
  • 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<TPrimaryKey>
{
    [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);

答案 6 :(得分:0)

随着 .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;
        }
    }
}

使用所描述的模式解决了“数据库上下文是通过依赖注入创建的”错误,并且不需要 Reload()/Refresh() 方法。

答案 7 :(得分:-1)

理想情况下,另一方会在进行更新时通知您。这可以通过message queue来实现。

其中:Azure Service Bus,Rabbit MQ,0MQ