实体框架中的一半事务执行

本文关键字:事务 执行 一半 框架 实体 | 更新日期: 2023-09-27 18:36:43

我有以下型号:

public class CardAccount
{
    public int ID { get; set; }
    [Index(IsUnique = true)]
    [Required]
    [StringLength(10)]
    public string CardNumber { get; set; }
    [Required]
    [StringLength(4)]        
    public string CardPIN { get; set; }
    [Required]
    public decimal CardCash { get; set; }
}

-

[Table("TransactionHistory")]
public class TransactionHistory
{
    public int ID { get; set; }
    [Index(IsUnique = false)]
    [Required]
    [StringLength(10)]                
    public string CardNumber { get; set; }
    [Required]
    public DateTime TransactionDate { get; set; }
    [Required]
    public decimal Ammount { get; set; }
}

数据库上下文:

public class ATMDbContext : DbContext
{
    public ATMDbContext()
        : base("ATM")
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<ATMDbContext, Configuration>());
    }
    public IDbSet<CardAccount> CardAccounts { get; set; }
    public IDbSet<TransactionHistory> TransactionHistory { get; set; }
}

以及具有提款方法的ATM类:

public class ATMClient
{
    private const int CardNumberLength = 10;
    private const int CardPINLength = 4;
    private ATMDbContext dbContext;
    private IOutputProvider outputProvider;
    public ATMClient(ATMDbContext dbContext)
        : this(dbContext, new ConsoleOutputProvider())
    {
    }
    public ATMClient(ATMDbContext dbContext, IOutputProvider outputProvider)
    {
        this.dbContext = dbContext;
        this.outputProvider = outputProvider;
    }
    public void WithdrawMoney(string cardNumber, string cardPIN, decimal money)
    {
        if (string.IsNullOrWhiteSpace(cardNumber))
        {
            throw new ArgumentNullException("The card number is null.");
        }
        if (cardNumber.Length != CardNumberLength)
        {
            throw new ArgumentException(String.Format("The card number is invalid. Please, state a proper {0} digit card number.", CardNumberLength));
        }
        if (string.IsNullOrWhiteSpace(cardPIN))
        {
            throw new ArgumentNullException("The card pin is null.");
        }
        if (cardPIN.Length != CardPINLength)
        {
            throw new ArgumentException(String.Format("The card pin is invalid. Please, state a proper {0} digit card pin.", CardPINLength));
        }
        if (money < 0)
        {
            throw new ArgumentException("Invalid amount to withdraw. Please, state a valid positive number.");
        }
        using (var transaction = dbContext.Database.BeginTransaction(IsolationLevel.RepeatableRead))
        {
            var currentAccount = dbContext.CardAccounts.Where(x => x.CardNumber == cardNumber).FirstOrDefault();
            try
            {
                if (currentAccount == null)
                {
                    throw new ArgumentException("Invalid card number.");
                }
                if (currentAccount.CardPIN != cardPIN)
                {
                    throw new ArgumentException("Invalid pin number.");
                }
                if (currentAccount.CardCash < money)
                {
                    throw new InvalidOperationException("Insufficient funds.");
                }
                currentAccount.CardCash -= money;
                var transactionLog = new TransactionHistory();
                transactionLog.CardNumber = cardNumber;
                transactionLog.TransactionDate = DateTime.Now;
                transactionLog.Ammount = money;
                dbContext.TransactionHistory.Add(transactionLog);
                dbContext.SaveChanges();
            }
            catch (Exception ex)
            {
                outputProvider.PrintLine(ex.Message);
                transaction.Rollback();
            }
            //transaction.Commit();
        }
    }
}

当我取消注释"交易。提交()' 一切正常。但是,当它被注释时,更新语句将执行,但事务日志不会添加到数据库中。如何在不提交事务的情况下提交更新语句?保存更改是否过早提交事务?

这是我正在使用的代码:

    public static void Main()
    {
        var dbContext = new ATMDbContext();
        var atm = new ATMClient(dbContext);
        var account = dbContext.CardAccounts.Where(x => x.CardCash >= 10000).FirstOrDefault();
        Console.WriteLine(account.CardNumber);
        Console.WriteLine(account.CardCash);
        atm.WithdrawMoney(account.CardNumber, account.CardPIN, 10000m);
        Console.WriteLine(account.CardNumber);
        Console.WriteLine(account.CardCash);
        var transactionHistory = dbContext.TransactionHistory.Where(x => x.CardNumber == account.CardNumber).FirstOrDefault();
        Console.WriteLine(transactionHistory.CardNumber);
    }

这会导致NullReferenceException,如前所述:

    var transactionHistory = dbContext.TransactionHistory.Where(x => x.CardNumber == account.CardNumber).FirstOrDefault();
    Console.WriteLine(transactionHistory.CardNumber);

输出:

2504478325
21835.56
2504478325
11835.56

编辑:

好的,更多信息:似乎

currentAccount.CardCash -= money;

实际上不会修改实际记录。当您检查数据库中的值时,它是旧的值。但是,控制台上打印的值会递减,即使我尝试使用

account = dbContext.CardAccounts.Where(x => x.CardNumber == account.CardNumber).FirstOrDefault();

在打印第二张卡片信息之前。


编辑2:

偏差来自上下文本身。上下文中的值被修改,而数据库中的值不会被修改。如何规避上下文的无效状态(删除事务除外..)?上下文的重新创建是唯一的解决方案吗?

实体框架中的一半事务执行

我认为您不需要为您的场景创建事务。我认为这样做更容易:

using (var dbContext = new ATMDbContext())
{
    var currentAccount = dbContext.CardAccounts.FirstOrDefault(x => x.CardNumber == cardNumber);
    try
    {
        if (currentAccount == null)
        {
              throw new ArgumentException("Invalid card number.");
        }
        if (currentAccount.CardPIN != cardPIN)
        {
           throw new ArgumentException("Invalid pin number.");
        }
        if (currentAccount.CardCash < money)
        {
            throw new InvalidOperationException("Insufficient funds.");
        }
        currentAccount.CardCash -= money;
       var transactionLog = new TransactionHistory();
       transactionLog.CardNumber = cardNumber;
       transactionLog.TransactionDate = DateTime.Now;
       transactionLog.Ammount = money;
       dbContext.TransactionHistory.Add(transactionLog);
       dbContext.SaveChanges();
   }
   catch (Exception ex)
   {
      outputProvider.PrintLine(ex.Message);
   }
}

调用 SaveChanges 方法时,EF 会创建一个事务来保存之前执行的所有操作。

此外,您无需担心内部事务是否失败。 DbContextTransaction.Dispose方法将在using块的末尾调用,如果事务未成功提交,它将自动回滚事务。

当您需要对 SaveChanges 进行多次调用时,请使用事务:

using(var scope = new TransactionScope(TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    // Do something 
    context.SaveChanges();
    // Do something else
    context.SaveChanges();
    scope.Complete();
}