为什么不更新数据库数据但对象进行更新并且没有错误?

时间:2019-08-19 10:57:51

标签: c# entity-framework entity-framework-core repository-pattern unit-of-work

我有这个银行ATM模拟应用程序,该应用程序实现了一些域驱动的设计架构和工作单元模式。

此应用程序具有3个基本功能:

  • 查询余额
  • 存款
  • 提现

这些是项目层:

ATM.Model(域模型实体层)

namespace ATM.Model
{
public class BankAccount
{
    public int Id { get; set; }
    public string AccountName { get; set; }
    public decimal Balance { get; set; }

    public decimal CheckBalance()
    {
        return Balance;
    }

    public void Deposit(int amount)
    {
        // Domain logic
        Balance += amount;
    }

    public void Withdraw(int amount)
    {
        // Domain logic
        //if(amount > Balance)
        //{
        //    throw new Exception("Withdraw amount exceed account balance.");
        //}

        Balance -= amount;
    }
}
}

namespace ATM.Model
{
public class Transaction
{
    public int Id { get; set; }
    public int BankAccountId { get; set; }
    public DateTime TransactionDateTime { get; set; }
    public TransactionType TransactionType { get; set; }
    public decimal Amount { get; set; }
}

public enum TransactionType
{
    Deposit, Withdraw
}
}

ATM.Persistence(持久层)

namespace ATM.Persistence.Context
{
public class AppDbContext : DbContext
{        
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"[connstring]");
    }

    public DbSet<BankAccount> BankAccounts { get; set; }
    public DbSet<Transaction> Transactions { get; set; }
}
}

namespace ATM.Persistence.Repository
{
public class RepositoryBankAccount
{
    public AppDbContext context { get; }

    public RepositoryBankAccount()
    {
        context = new AppDbContext();
    }

    public BankAccount FindById(int bankAccountId)
    {
        return context.BankAccounts.Find(bankAccountId);
    }

    public void AddBankAccount(BankAccount account)
    {
        context.BankAccounts.Add(account);
    }

    public void UpdateBankAccount(BankAccount account)
    {
        context.Entry(account).State = EntityState.Modified;
    }
}
}

namespace ATM.Persistence.Repository
{
public class RepositoryTransaction
{
    private readonly AppDbContext context;

    public RepositoryTransaction()
    {
        context = new AppDbContext();
    }

    public void AddTransaction(Transaction transaction)
    {
        context.Transactions.Add(transaction);
    }
}
}

namespace ATM.Persistence.UnitOfWork
{
public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext db;
    public UnitOfWork()
    {
        db = new AppDbContext();
    }

    private RepositoryBankAccount _BankAccounts;
    public RepositoryBankAccount BankAccounts
    {
        get
        {
            if (_BankAccounts == null)
            {
                _BankAccounts = new RepositoryBankAccount();
            }
            return _BankAccounts;
        }
    }

    private RepositoryTransaction _Transactions;
    public RepositoryTransaction Transactions
    {
        get
        {
            if (_Transactions == null)
            {
                _Transactions = new RepositoryTransaction();
            }
            return _Transactions;
        }
    }

    public void Dispose()
    {
        db.Dispose();
    }

    public int Commit()
    {
        return db.SaveChanges();
    }

    public void Rollback()
    {
        db
        .ChangeTracker
        .Entries()
        .ToList()
        .ForEach(x => x.Reload());
    }
}
}

ATM.ApplicationService(应用程序层)

namespace ATM.ApplicationService
{
public class AccountService
{        
    private readonly UnitOfWork uow;

    public AccountService()
    {            
        uow = new UnitOfWork();
    }

    public void DepositAmount(BankAccount bankAccount, int amount)
    {            
        bankAccount.Deposit(amount);
        uow.BankAccounts.UpdateBankAccount(bankAccount);

        var transaction = new Transaction()
        {
            BankAccountId = bankAccount.Id,
            Amount = amount,
            TransactionDateTime = DateTime.Now,
            TransactionType = TransactionType.Deposit
        };

        uow.Transactions.AddTransaction(transaction);

        try
        {
            uow.Commit();
        }
        catch
        {
            uow.Rollback();
        }
        finally
        {
            uow.Dispose();
        }
    }

    public void WithdrawAmount(BankAccount bankAccount, int amount)
    {            
        bankAccount.Withdraw(amount);
        uow.BankAccounts.UpdateBankAccount(bankAccount);
        //repoBankAccount.UpdateBankAccount(bankAccount);

        var transaction = new Transaction()
        {
            BankAccountId = bankAccount.Id,
            Amount = amount,
            TransactionDateTime = DateTime.Now,
            TransactionType = TransactionType.Withdraw
        };

        uow.Transactions.AddTransaction(transaction);

        try
        {
            uow.Commit();
        }
        catch
        {
            uow.Rollback();
        }
        finally
        {
            uow.Dispose();
        }
    }

    public decimal CheckBalanceAmount(int bankAccountId)
    {
        BankAccount bankAccount = uow.BankAccounts.FindById(bankAccountId);

        return bankAccount.CheckBalance();
    }
}
}

ATM.ConsoleUICore

namespace ATM.ConsoleUICore
{
class Program
{
    static void Main()
    {
        AccountService accountService = new AccountService();
        RepositoryBankAccount repoBankAccount = new RepositoryBankAccount();

        var bankAccount = repoBankAccount.FindById(2);

        Console.WriteLine("1. Check balance");
        Console.WriteLine("2. Deposit");
        Console.WriteLine("3. Withdraw");
        Console.WriteLine("Enter option: ");
        string opt = Console.ReadLine();
        switch (opt)
        {
            case "1":
                Console.WriteLine($"Your balance is ${bankAccount.CheckBalance()}");
                break;
            case "2":
                // User to input amount.
                // Data validation to make sure amount is greater than zero.
                // Pass the input amount to Application layer.

                accountService.DepositAmount(bankAccount, 50);

                // After getting the operation status from Application service layer.
                // Print operation status here: Either success or fail
                Console.WriteLine("Deposit successfully");
                break;
            case "3":            
                break;
            default:
                break;
        }

    }
}
}

我可以成功检查余额。对于选项2,我可以执行“存款”选项而不会出现任何错误。但是在数据库中,我的余额余额未更新。事务也未添加到数据库中。

如果我以context.SaveChanges();方法放回UpdateBankAccount,则可以使用。它返回1。但是,我使用UoW执行SaveChanges()SaveChanges()确实以UoW Commit方法执行,但数据库未反映其更改。 UoW Commit方法SaveChanges返回0。

完整代码可在Github repository上找到。

2 个答案:

答案 0 :(得分:6)

问题的核心在于,正在创建两个AppDbContext实例来执行一个动作。在一个实例中进行更改,而在另一实例上调用SaveChanges。显然,它没有反映在基础数据库中。

我们现在将从下至上逐步介绍您的代码。

ATM.ConsoleUICore.Program.Main()方法中,请注意以下代码:

AccountService accountService = new AccountService();
...
...
...
accountService.DepositAmount(bankAccount, 50);

您正在创建AccountService的实例。在AccountService的构造函数中,您正在创建UnitOfWork的实例,如下所示:

private readonly UnitOfWork uow;
public AccountService()
{            
    uow = new UnitOfWork();
}

UnitOfWork的构造函数中,您正在创建AppDbContext的实例(该实例派生自DbContext)。
您还拥有BankAccounts属性,它是RepositoryBankAccount的一个实例,如下所示:

private readonly AppDbContext db;
public UnitOfWork()
{
    db = new AppDbContext();
}
...
...
...
private RepositoryBankAccount _BankAccounts;
public RepositoryBankAccount BankAccounts
{
    get
    {
        if (_BankAccounts == null)
        {
            _BankAccounts = new RepositoryBankAccount();
        }
        return _BankAccounts;
    }
}

现在有问题...

RepositoryBankAccount的构造函数中,您再次创建AppDbContext的实例,如下所示:

public AppDbContext context { get; }
public RepositoryBankAccount()
{
    context = new AppDbContext();
}

实际上,您冒充一个UnitOfWork实例下的操作作为一个数据库事务执行。但是,当您在存储库中创建AppDbContext的不同实例时,情况并非如此。您的工作单元已从存储库中分离出来。您必须连接它们。到处都应该是AppDbContext的相同实例。

那么,解决方案是什么?

请勿在任何存储库中创建AppDbContext的实例。而是从工作单元中注入现有实例。

public AppDbContext context { get; }

public RepositoryBankAccount(AppDbContext appDbContext)//<==Inject the AppDbContext
{
    context = appDbContext;//<==Do NOT create new instance here; assign the injected instance.
}

然后,在您的UnitOfWork类中,如下更改属性BankAccounts

private RepositoryBankAccount _BankAccounts;
public RepositoryBankAccount BankAccounts
{
    get
    {
        if (_BankAccounts == null)
        {
            _BankAccounts = new RepositoryBankAccount(db);//<==Note that `db` means `AppDbContext` is injected
        }
        return _BankAccounts;
    }
}

顺便说一句,避免将所有这些不必要的包装纸放在包装纸上。

看看这个answer,它解释了为什么不需要这种包装器。

以防万一,您决定继续进行现有设计时,我已经在上面提出了解决方案。

此外,我建议您的一个工作单元应该是一个数据库事务。因此,数据库事务在创建工作单元的实例时开始,而在处置它时结束(提交或回滚)。要么所有内容都刷新到数据库,要么不刷新。在这之间发生的所有事情都应该是一个数据库事务的一部分。如果出现异常,请一起回滚工作单元。

答案 1 :(得分:1)

对任何数据库事务尝试这种格式。创建的多个实例

public RepositoryTransaction()
    {
        context = new AppDbContext();
    }

仅表示您每次都创建实例,并且不会将其保存到数据库中。

  using(AppDbContext db = new AppDbContext())
{
   var transaction = new Transaction()
        {
            BankAccountId = bankAccount.Id,
            Amount = amount,
            TransactionDateTime = DateTime.Now,
            TransactionType = TransactionType.Deposit
        };

    // save them back to the database
    //Add new Employee to database
         db.Transactions.InsertOnSubmit(transaction);

         //Save changes to Database.
         db.SubmitChanges();
}


     using(AppDbContext db = new AppDbContext())
    {
        // get the record
        Transaction dbProduct = db.Transactions.Single(p => p.BankAccountId == 1);

    // set new values
    dbProduct.TransactionDateTime = DateTime.Now; 
    dbProduct.TransactionType =  TransactionType.Deposit;

    // save them back to the database
    db.SubmitChanges();
}