2015-01-08 3 views
1

У меня есть следующие модели:Половина выполнения транзакции в Entity Framework

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, но транзакционный журнал не добавляется в базу данных. Как выполняется инструкция обновления без транзакции? Сохраняет ли 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 

EDIT:

Хорошо, немного больше информации: кажется

currentAccount.CardCash -= money; 

фактически не изменять фактическую запись. Когда вы проверяете значение в базе данных, оно является старым. Тем не менее, значение печатается на консоли декрементируются, даже если я пытаюсь получить объект из базы данных снова с помощью

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

до второй информации карты печати.


EDIT 2:

Отклонение исходит от самого контекста. Значение в контексте изменяется, а значение в базе данных - нет. Как избежать недопустимого состояния контекста (кроме удаления транзакции ..)? Является ли рекреация контекста единственным решением?

+0

Почему вы используете транзакции в методе WithdrawMoney? – octavioccl

+0

Ну, за одну остановку проглотить ошибки. Эта практика может привести к тому, что приложение хромает в странном состоянии и вызывает всевозможные странные записи. Я передумаю этот вопрос, когда вы это исправите. – usr

+0

@usr Извините, я не понял. – zhulien

ответ

0

Я не думаю, что вам нужно создать транзакцию для вашего escenario. Я думаю, что более легко сделать это:

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

Я отправлю комментарий еще раз, если вы еще не видели его. => Это образец курсовой работы. Вся идея системы состоит в том, чтобы понять концепцию транзакций. Именно по этой причине мне нужно использовать транзакцию в методе вывода! – zhulien

Смежные вопросы