首页 > 解决方案 > C# .NET Core DbContext 不返回数据库中的数据

问题描述

我有以下代码:

public void someMethod(){
    ...

    var accounts = myRepo.GetAccounts(accountId)?.ToList(); 

    ...

    foreach (var account in accounts)
    {
        account.Status="INACTIVE";
        var updatedAccount = myRepo.AddOrUpdateAccounts(account);
    }
}

public Account AddOrUpdateAccounts(Account account){
    //I want to compare account in the Db and what is passed in. So get the account from DB
    var accountFromDb = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.

    //here accountFromDb.Status is returned as INACTIVE, but in the database the column value is ACTIVE
    ...
    ...
}

public IEnumerable<Account> GetAccounts(int id){
    return id <= 0 ? null : m_Context.Accounts.Where(x => x.Id == id);
}

在这里,someMethod()我在里面调用GetAccounts()它从 Accounts 表中返回数据。

然后我正在更改Status帐户,并调用AddOrUpdateAccounts().

在里面AddOrUpdateAccounts(),我想比较传入的帐户和数据库中的内容。当我打电话时GetAccounts(),它返回了一条记录STATUS="INACTIVE"。我没做过SaveChanges()。为什么没有GetAccounts()从数据库返回数据?在 Db 中,状态仍然是“ACTIVE”

标签: c#entity-frameworkasp.net-coredbcontext

解决方案


存储库方法应该返回IQueryable<Account>,而不是IEnumerable<Account>因为这将允许消费者在对数据库执行任何查询之前继续优化任何标准或控制帐户应如何使用:

我会考虑:

public IQueryable<Account> GetAccountsById(int id){
    return m_Context.Accounts.Where(x => x.Id == id);
}

不要返回#null,只返回查询。如果数据不可用,消费者可以决定做什么。

从那里调用代码如下所示:

var accounts = myRepo.GetAccounts(accountId).ToList(); 

foreach (var account in accounts)
{
    account.Status="INACTIVE";
}

您的 addOrUpdate 不起作用:

public Account AddOrUpdateAccounts(Account account){
    ...
    var account = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.

您将帐户作为“帐户”传递,然后尝试声明一个名为“帐户”的局部变量。如果您删除var关键字,您会将 DbContext 的记录加载到修改后的帐户之上,并且您的更改将会丢失。只要帐户仍与 DbContext 关联,就无需将帐户加载到另一个变量中。

编辑:将var account = ...语句更改为如下所示:

public Account AddOrUpdateAccounts(Account account){
    ...
    var accountToUpdate = myRepo.GetAccounts(account.Id); //this doesn't return whats 

accountToUpdate将显示修改状态而不是数据库中的状态,因为 DbContext 仍在跟踪对您修改的实体的引用。( account) 例如,如果我这样做:

var account1st = context.Accounts.Single(x => x.AccountId == 1);
var account2nd = context.Accounts.Single(x => x.AccountId == 1);
Console.WriteLine(account1st.Status); // I get "ACTIVE"
Console.WriteLine(account2nd.Status); // I get "ACTIVE"
account1st.Status = "INACTIVE";
Console.WriteLine(account2nd.Status); // I get "INACTIVE"

两个引用都指向同一个实例。我第二次尝试读取 Account 时无关紧要,只要它来自同一个 DbContext 并且上下文正在跟踪实例。如果您通过不同的 DbContext 读取该行,或者AsNoTracking()与所有读取一起使用,则可以从数据库中重新读取该帐户。您可以重新加载实体,但如果这些变量指向相同的引用,它将覆盖您的更改并将实体设置回Unmodified. 在查看 SQL 分析器输出时,这可能会有点令人困惑,因为在某些情况下,您会看到 EF 运行SELECT查询实体,但返回的实体具有与数据库中不同的修改值。即使从跟踪缓存加载,EF 在某些情况下仍然可以对 DB 执行查询,但它会返回跟踪的实体引用。

/编辑

在保存更改时,实际上只是在与帐户关联的 DbContext 上调用 SaveChanges。“棘手”的部分是确定 DbContext 的范围,以便可以做到这一点。为此推荐的模式是工作单元。有几种不同的,我为 EF 推荐的一个是 Mehdime 的 DbContextScope,但是您可以实现更简单的,可能更容易理解和遵循。本质上,一个工作单元封装了 DbContext,以便您可以定义存储库可以访问相同 DbContext 的范围,然后在工作结束时提交这些更改。

在最基本的层面上:

public interface IUnitOfWork<TDbContext> : IDisposable where TDbContext : DbContext
{
    TDbContext Context { get; } 

    int SaveChanges();
}

public class UnitOfWork : IUnitOfWork<YourDbContext>
{
    private YourDbContext _context = null;
    TDbContext IUnitOfWork<YourDbContext>.Context
    {
        get { return _context ?? (_context = new YourDbContext("YourConnectionString"); }
    }

    int IUnitOfWork<YourDbContext>.SaveChanges()
    {
        if(_context == null)
            return 0;
       return _context.SaveChanges();
    }

    public void Dispose()
    { 
        try
        {
           if (_context != null)
              _context.Dispose();
        }
        catch (ObjectDisposedException)
        { }
    }
}

有了此类可用,并通过 IoC 容器(Autofac、Unity 或 MVC Core)使用依赖注入,您可以将工作单元注册为每个请求的实例,以便当控制器和存储库类在其构造函数中请求一个时,它们会收到相同的实例。

控制器/服务:

private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
private readonly IYourRepository _repository = null;

public YourService(IUnitOfWork<YourDbContext> unitOfWork, IYourRepository repository)
{
   _unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
   _repository = repository ?? throw new ArgumentNullException("repository");
}

存储库

private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;

public YourService(IUnitOfWork<YourDbContext> unitOfWork)
{
   _unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
}

private YourDbContext Context { get { return _unitOfWork.Context; } }

大免责声明:这是一个非常粗略的初始实现,用于大致解释工作单元如何操作,它绝不是适合生产的代码。它有局限性,特别是在处理 DbContext 方面,但应该用作演示。绝对希望实现一个已经存在并解决这些问题的库。这些实现正确管理 DbContext 处置,并将管理上下文之外的范围,例如 TransactionScope,因此即使调用 unitOfWork.Context.SaveChanges() 也需要它们的 SaveChanges。

有了控制器/服务和存储库可用的工作单元,使用存储库和更新更改的代码变为:

var accounts = myRepo.GetAccountsById(accountId).ToList(); 
foreach (var account in accounts)
{
    account.Status="INACTIVE";
}

UnitOfWork.SaveChanges();

使用适当的工作单元,它看起来更像:

using (var unitOfWork = UnitOfWorkFactory.Create())
{
    var accounts = myRepo.GetAccountsById(accountId).ToList(); // Where myRepo can resolve the unit of work via locator.
    foreach (var account in accounts)
    {
        account.Status="INACTIVE";
    }

    unitOfWork.SaveChanges();
}

这样,如果您要调用不同的存储库来获取数据,执行许多不同的更新,则更改将在最后一次调用中全部提交,并在任何数据出现问题时回滚。


推荐阅读