如何强制Entity Framework始终从数据库中获取更新数据?

时间:2014-03-04 16:28:11

标签: c# asp.net-mvc entity-framework dbcontext

我正在使用EntityFramework.Extended库来执行批量更新。唯一的问题是EF无法跟踪库执行的批量更新。因此,当我再次查询DbContext时,它不会返回更新的实体。

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

有没有办法强制EF在跟踪更改时获取最新数据?

7 个答案:

答案 0 :(得分:123)

请尝试刷新单个实体:

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

修改 要获取实体集合的新数据,请务必在每次请求后部署DbContext实例。

答案 1 :(得分:12)

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

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

它背后的逻辑是 - 我的更新附加了实体,因此它会跟踪对它的更改。这会将其添加到本地商店。此后,任何使用功能代理检索实体的尝试都会导致它获取本地实体,而不是转到数据库并返回一个新的,启用代理的实体。我尝试了上面的重载选项,它确实从数据库中刷新了对象,但这并没有为您提供延迟加载的代理对象。我试过做Find(id), Where(t => t.Id = id), First(t => t.Id = id)。最后,我检查了提供的可用状态,并看到存在“分离”状态。找到了!希望这有助于某人。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

我声明了实体变量,没有赋值,作为类的一部分。这允许我处理一个实例而不会丢失变量以供其他方法参考。我刚刚遇到过这个问题,所以它在它的带下没有很多运行时间,但到目前为止似乎工作正常。

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 ...
}

答案 4 :(得分:1)

偶然发现了这个问题。我的应用没有从数据库返回新数据。

这似乎是 3 个解决方案:

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

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

  3. 使用后处理 DbContext。绝对是要走的路。

我在 Repository 类中创建了我的 DbContext 实例。如果 DbContext 是在存储库级别声明的,那么我无法控制它的处理方式。这是一个禁忌。如果我在每次调用时都创建一个新的 DbContext,那么我就不能调用 Select、修改数据,然后调用 Update。

似乎我的存储库模式从根本上缺少某些东西。

在对基础知识库模式进行了一些研究后,我找到了解决方案:工作单元模式和知识库模式。

This is an excellent article on the Unit of Work pattern

this article from Microsoft。我目前拥有的是页面中进一步的存储库,缺少的是“实现通用存储库和工作单元类”部分

基本上,您不是将存储库注入到您的服务中,而是通过注入到您的服务中的 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 和桌面应用程序中,类实例的生命周期要长得多,而且问题更多。

This article from Microsoft 明确指出依赖注入不适合在 Blazor 中管理 DbContext 的生命周期:

<块引用>

在 Blazor Server 应用中,范围服务注册可能会出现问题 因为实例在用户内部的组件之间共享 电路。 DbContext 不是线程安全的,也不是为并发而设计的 采用。由于以下原因,现有的生命周期是不合适的:

  • Singleton 在应用的所有用户之间共享状态并导致 不适当的同时使用。
  • Scoped(默认)构成了类似的 同一用户的组件之间的问题。
  • 瞬态导致新的 每个请求的实例;但由于组件可以长期使用,这 导致比预期更长的上下文。

他们建议使用工厂模式,可以像这样实现

/// <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 容器中。只有 IWorkOfUnitFactory。

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

我有一个 SetStatus 方法来更新数据库中的状态字段。这种方法应该如何知道它是独立操作还是较大事务的一部分?

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

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

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

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

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

另一种选择是让 IUnitOfWorkFactory.Create 接受 workTransaction 参数,并在设置时:

  • 重用现有的 DbContext
  • 请勿丢弃
  • IUnitOfWork.Save 不会提交

呸!那个错误是一个兔子洞。这是一个很长的解决方案,但应该永远解决它。

答案 5 :(得分:0)

对我来说...... 我像这样访问我的DbContext:

_viewModel.Repo.Context

要强制EF命中数据库,我会这样做:

_viewModel.Repo.Context = new NewDispatchContext();

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

答案 6 :(得分:0)

重新加载特定实体对我来说不是一个选择,因为我不知道确切的实体。我也不想创建一个新的 DbContext,因为它是由 DI 注入的。所以我采用了以下技巧来“重置”整个上下文。

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