如何强制实体框架始终从数据库中获取更新的数据?

我正在使用 EntityFramework 扩展库执行批量更新。唯一的问题是 EF 没有跟踪库执行的批量更新。所以当我再次查询 DbContext时,它不会返回更新的实体。

我发现在查询时使用 AsNoTracking()方法禁用跟踪并从数据库中获取新数据。但是,由于 EF 没有跟踪使用 AsNoTracking()查询的实体,因此无法对查询的数据执行任何更新。

有没有办法迫使 EF 在跟踪变化的同时获得最新的数据?

103652 次浏览

请尝试刷新单个实体:

Context.Entry<T>(entity).Reload()

编辑: 为了获得实体集合的新数据,值得在每次请求后尝试释放 DbContext实例。

使代码在相同的上下文中运行将不会产生更新的实体。它只会在运行之间追加在数据库中创建的新实体。EF 力量重新加载可以这样做:

ObjectQuery _query = Entity.MyEntity;
_query.MergeOption = MergeOption.OverwriteChanges;
var myEntity = _query.Where(x => x.Id > 0).ToList();

我是在寻找更新实体后导航属性没有填充的问题的解决方案时偶然发现这个问题的。每当我试图从数据库重新加载实体时,它都会从本地存储中获取条目,而不会通过延迟加载填充导航属性。我没有破坏上下文并重新创建一个,而是发现这使我能够在代理工作的情况下获得新的数据:

_db.Entry(entity).State = EntityState.Detached;

其背后的逻辑是——我的更新附加了实体,这样它就可以跟踪对它的更改。这将它添加到本地存储中。此后,任何使用函数代理检索实体的尝试都将导致它获取本地代理,而不是出去到数据库并返回一个新的、支持代理的实体。我尝试了上面的 reload 选项,它确实会刷新数据库中的对象,但是它不会给您带有延迟加载的代理对象。我试过做 Find(id), Where(t => t.Id = id), First(t => t.Id = id)。最后,我检查了提供的可用状态,发现有一个“分离”状态。找到了!希望这对谁有帮助。

我声明了实体变量,没有赋值,作为类的一部分。这允许我在不丢失变量供其他方法引用的情况下释放一个实例。我只是偶然发现这个,所以它没有太多的运行时间在它的腰带下,但到目前为止,它似乎工作得很好。

public partial class frmMyForm
{
private My_Entities entity;


public frmMyForm()
{
InitializeComponent();
}


private void SomeControl_Click(object sender, EventArgs e)
{
db.SaveChanges();
db.Dispose();
entity = new My_Entities();
//more code using entity ...
}

对我来说。 我访问 DbContext 的方式如下:

_viewModel.Repo.Context

To force EF to hit the database I do this:

_viewModel.Repo.Context = new NewDispatchContext();

用新实例覆盖当前 DbContext。然后下次我使用我的数据服务时,他们从数据库中获取数据。

Reloading specific entities was not an option for me because I didn't know the exact entity. I also did not want to create a new DbContext as it is injected by DI. So I resorted to the following trick to "reset" the whole context.

            foreach (var entry in db.ChangeTracker.Entries())
{
entry.State = EntityState.Detached;
}

无意中发现了这个问题,我的应用程序没有从数据库中返回新的数据。

这些似乎是3种解决方案:

  1. 重新加载在选择: 首先你选择的对象,然后重新加载。加载两次,如果它没有缓存?

  2. 使用后分离: 如果在使用后忘记分离对象,将会导致应用程序中完全独立的部分出现 bug,而这些部分将非常难以追踪。

  3. 在使用后处理 DbContext。

我正在 Repository 类中创建我的 DbContext 实例。如果 DbContext 是在 Repository 级别声明的,那么我就无法控制如何处理它。绝对不行。如果我在每次调用时都创建一个新的 DbContext,那么我就不能调用 Select,修改数据,然后再调用 Update。

看起来我的仓库模式中缺少了一些基本的东西。

通过对基本知识库模式的研究,我找到了解决方案: 工作单元模式与知识库模式并存。

This is an excellent article on the Unit of Work pattern

或者 这篇来自微软的文章。我现在在页面上面有一个仓库,缺少的是“实现一个通用仓库和一个工作类单元”一节

基本上,不是将存储库注入到服务中,而是通过注入到服务中的 UnitOfWork 访问所有存储库。它将解决许多问题。

public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationContext _context;
public UnitOfWork(ApplicationContext context)
{
_context = context;
Developers = new DeveloperRepository(_context);
Projects = new ProjectRepository(_context);
}
public IDeveloperRepository Developers { get; private set; }
public IProjectRepository Projects { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}

仍然存在的问题是: 如何创建 IUnitOfWork 实例?

如果我在类构造函数中创建它,就像注入存储库一样,那么它的创建和销毁方式完全相同,我们又回到了同样的问题。在 ASP.NET 和 MVC 中,类实例的生命周期很短,所以在构造函数中注入类实例可能没什么问题,但是在 Blazor 和桌面应用中,类实例的生命周期要长得多,而且问题更严重。

这篇来自微软的文章明确指出,依赖注入不适合管理 Blazor 的 dbContext 生命周期:

在 BlazorServer 应用程序中,作用域内的服务注册可能是有问题的 因为实例是在用户的 DbContext 不是线程安全的,也不是为并发而设计的 由于以下原因,现有寿命是不适当的:

  • Singleton 在应用程序的所有用户之间共享状态,并导致 inappropriate concurrent use.
  • 作用域(默认值)提出了类似的 同一用户的组件之间的问题。
  • 瞬态导致新的 但是由于组件可以是长期的,因此 结果在一个比预期的更长的生存环境中。

They suggest using the Factory pattern, which can be implemented like this

/// <summary>
/// Creates instances of UnitOfWork. Repositories and UnitOfWork are not automatically injected through dependency injection,
/// and this class is the only one injected into classes to give access to the rest.
/// </summary>
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private readonly IDateTimeService _dateService;
private readonly DbContextOptions<PaymentsContext> _options;


public UnitOfWorkFactory(IDateTimeService dateService, DbContextOptions<PaymentsContext> options)
{
_dateService = dateService;
_options = options;
}


/// <summary>
/// Creates a new Unit of Work, which can be viewed as a transaction. It provides access to all data repositories.
/// </summary>
/// <returns>The new Unit of Work.</returns>
public IUnitOfWork Create() => new UnitOfWork(CreateContext(), _dateService);


/// <summary>
/// Creates a new DbContext.
/// </summary>
/// <returns>The new DbContext.</returns>
public PaymentsContext CreateContext() => new(_options);
}

IWorkOfUnit 和任何存储库都不会注册到 IoC 容器中。

最后... 如何在各种服务之间共享事务?

我有一个 SetStatus 方法,用于更新数据库中的 status 字段。这个方法如何知道它是一个独立的操作还是一个更大事务的一部分?

既然类级别的依赖注入不适合管理和共享单元工作,那么唯一的选择就是将其作为参数传递给需要它的方法。

我为每个需要它的方法添加了一个可选的 IUnitOfWork? workScope = null参数,并且只有当这个参数为 null 时才调用 Save。这是一个实现。

public virtual async Task<TempOrder?> SetStatusAsync(int orderId, PaymentStatus status, IUnitOfWork? workScope = null)
{
using var unitOfWork = _workFactory.Create();
var work = workScope ?? unitOfWork;


var order = await work.Orders.GetByIdAsync(orderId);
if (order != null)
{
order.Status = status;
work.Orders.Update(order); // DateModified gets set here


if (workScope == null)
{
await work.SaveAsync();
}
}
return order;
}

另一个选项是让 IUnitOfWorkFactory:

  • Re-use the existing DbContext
  • 不要丢弃
  • IUnitOfWork.Save won't submit

我的最终实现可以这样使用

public virtual async Task<TempOrder?> SetStatusAsync(int orderId, PaymentStatus status, IUnitOfWork? workScope = null)
{
using var unitOfWork = _workFactory.Create(workScope);


var order = await unitOfWork.Orders.GetByIdAsync(orderId);
if (order != null)
{
order.Status = status;
work.Orders.Update(order); // DateModified gets set here
await unitOfWork.SaveAsync(); // Ignored if workScope != null
}
return order;
}

呼!那只虫子就是个兔子洞。这是一个相当长的解决方案,但应该用一个坚实的架构来解决它。