简体   繁体   English

为什么数据库数据没有更新但对象确实没有错误?

[英]Why the database data is not being updated but object did and without error?

I have this bank ATM mock-up app which implements some Domain-Driven Design architecture and Unit of Work pattern. 我有这个银行ATM模型应用程序,它实现了一些域驱动设计架构和工作单元模式。

This app have 3 basic functions: 这个程序有3个基本功能:

  • Check balance 查看余额
  • Deposit 存款
  • Withdraw 退出

These are the project layers: 这些是项目层:

ATM.Model (Domain model entity layer) 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 (Persistence Layer) 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 (Application layer) 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 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;
        }

    }
}
}

I could check balance successfully. 我可以成功检查平衡。 For option 2, I can execute "Deposit" option without any error. 对于选项2,我可以执行“存款”选项而不会出现任何错误。 But in the database, my balance balance is not being updated. 但是在数据库中,我的余额余额没有更新。 Transaction is also not added into the db. 事务也没有添加到数据库中。

If I put back context.SaveChanges(); 如果我放回context.SaveChanges(); in UpdateBankAccount method, it works. UpdateBankAccount方法中,它可以工作。 It returns 1. But, I use UoW to perform SaveChanges() . 它返回1.但是,我使用UoW来执行SaveChanges() The SaveChanges() did executed in UoW Commit method but the database didn't reflect its changes. SaveChanges()确实在UoW Commit方法中执行,但数据库没有反映其更改。 The UoW Commit method SaveChanges returns 0. UoW Commit方法SaveChanges返回0。

Complete code can be found on Github repository . 完整的代码可以在Github存储库中找到。

The core of the problem here is that, two instances of AppDbContext are being created to conduct one single action. 这里问题的核心是,正在创建两个AppDbContext实例来执行单个操作。 Changes are made in one instance and SaveChanges is being called on other instance. 在一个实例中进行更改,并在其他实例上调用SaveChanges Obviously, it is not being reflected in underlying database. 显然,它没有反映在底层数据库中。

We will now go through your code step by step from bottom to top. 我们现在将从下到上逐步完成您的代码。

In ATM.ConsoleUICore.Program.Main() method, note the following code: ATM.ConsoleUICore.Program.Main()方法中,请注意以下代码:

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

You are creating an instance of AccountService . 您正在创建AccountService的实例。 In constructor of AccountService , you are creating an instance of UnitOfWork as below: AccountService构造函数中,您正在创建UnitOfWork的实例,如下所示:

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

In constructor of UnitOfWork , you are creating an instance of AppDbContext (which is derived from DbContext ). UnitOfWork构造函数中,您正在创建AppDbContext的实例(它是从DbContext派生的)。
You also have BankAccounts property there which is an instance of RepositoryBankAccount as below: 您还有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; } } 

Now the problem... 现在问题......

In constructor of RepositoryBankAccount , you are again creating an instance of AppDbContext as below: RepositoryBankAccount构造函数中,您将再次创建AppDbContext的实例,如下所示:

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

Actually, you are pretending that your actions under one UnitOfWork instance are being executed as one database transaction. 实际上,您假装在一个UnitOfWork实例下的操作正在作为一个数据库事务执行。 But, as you are creating different instance of AppDbContext in repository, this is not the case. 但是,当您在存储库中创建不同的AppDbContext实例时,情况并非如此。 Your unit of work is detached from repository. 您的工作单元与存储库分离。 You have to connect them. 你必须连接它们。 It should be same instance of AppDbContext everywhere. 它应该是AppDbContext相同实例。

So, what is the solution? 那么,解决方案是什么?

Do NOT create an instance of AppDbContext in any repository. 不要在任何存储库中创建AppDbContext的实例。 Instead, inject the existing instance from unit of work. 相反,从工作单元注入现有实例。

public AppDbContext context { get; }

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

Then, in your UnitOfWork class, change the property BankAccounts as below: 然后,在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;
    }
}

By the way, avoid all these unnecessary wrappers over wrappers. 顺便说一句,避免所有这些不必要的包装器超过包装器。

Have a look at this answer that explains why such wrappers are not needed. 看看这个答案 ,解释为什么不需要这样的包装。

Just in case you decide to go on with your existing design, I have already suggested a solution above. 如果您决定继续使用现有设计,我已经提出了上述解决方案。

Additionally, I will suggest your one unit of work should be one database transaction. 另外,我建议你的一个工作单元应该是一个数据库事务。 So, your database transaction starts when you create an instance of unit of work and ends (commit or rollback) when you dispose it. 因此,在创建工作单元实例时会启动数据库事务,并在处理它时结束(提交或回滚)。 Either everything flushes to database or none. 要么所有内容都刷新到数据库,要么都没有。 Everything that happens in between this should be part of one database transaction. 在这之间发生的一切都应该是一个数据库事务的一部分。 In case of exception, rollback the unit of work all together. 如果发生异常,请将所有工作单元一起回滚。

try this format for any db transactions. 为任何数据库事务尝试此格式。 creating multiple instance of 创建多个实例

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

just mean that you create instance everytime and it does not get saved to database. 只是意味着您每次都创建实例并且它不会保存到数据库。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM