我有这个银行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上找到。
答案 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();
}