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

时间:2015-01-08 16:19:31

标签: c# sql-server entity-framework transactions code-first

我有以下型号:

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

当我取消注释'transaction.Commit()'时,一切正常。但是,在注释时,update语句会执行,但transactionLog不会添加到数据库中。如果没有事务提交,update语句是如何提交的? SaveChanges是否过早地提交了交易?

以下是我正在使用的代码:

    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:

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

1 个答案:

答案 0 :(得分:0)

我认为您不需要为您的方案创建交易。我认为这样做更容易:

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