In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
Today, I would like to share with you how Entity Framework uses Code First mode to manage affairs. The content is detailed and the logic is clear. I believe most people still know too much about this, so share this article for your reference. I hope you can get something after reading this article. Let's take a look at it.
I. what is a transaction
Another important topic when dealing with data-centric applications is transaction management. ADO.NET provides a very clean and efficient API for transaction management. Because EF runs on top of ADO.NET, EF can use the transaction management capabilities of ADO.NET.
When talking about transactions from a database perspective, it means that a series of operations are treated as an indivisible operation. All operations either succeed or fail. The concept of a transaction is a reliable unit of work, and all database operations in a transaction should be regarded as a unit of work.
From an application perspective, if we have multiple database operations that are treated as one unit of work, we should wrap them in a transaction. To be able to use transactions, the application needs to perform the following steps:
1. Start the transaction.
2. Execute all queries and all database operations, which are regarded as a unit of work.
3. If all transactions are successful, commit the transaction.
4. If any of the operations fail, roll back the transaction.
Second, create a test environment
1. When it comes to transactions, the most classic example is bank transfer. We also use this example here to understand the concepts related to transactions. In order to simply simulate the bank transfer scenario, suppose the bank uses different tables for different accounts. Accordingly, we create two entity classes, OutputAccount and InputAccount, which are defined as follows:
OutputAccount entity class:
Using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFTransactionApp.Model {[Table ("OutputAccounts")] public class OutputAccount {public int Id {get; set;} [StringLength (8)] public string Name {get; set;} public decimal Balance {get; set;}}
InputAccount entity class:
Using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFTransactionApp.Model {[Table ("InputAccounts")] public class InputAccount {public int Id {get; set;} [StringLength (8)] public string Name {get; set;} public decimal Balance {get; set;}}
2. Define the data context class
Using EFTransactionApp.Model;using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFTransactionApp.EF {public class EFDbContext:DbContext {public EFDbContext (): base ("name=AppConnection") {} public DbSet OutputAccounts {get; set;} public DbSet InputAccounts {get; set;}}
3. Use data migration to generate database and fill in seed data
Namespace EFTransactionApp.Migrations {using EFTransactionApp.Model; using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration: DbMigrationsConfiguration {public Configuration () {AutomaticMigrationsEnabled = false;} protected override void Seed (EFTransactionApp.EF.EFDbContext context) {/ / This method will be called after migrating to the latest version. / / You can use the DbSet.AddOrUpdate () helper extension method / / to avoid creating duplicate seed data. / / fill in seed data context.InputAccounts.AddOrUpdate (new InputAccount () {Name = "Li Si", Balance = 0m}) Context.OutputAccounts.AddOrUpdate (new OutputAccount () {Name= "Zhang San", Balance=10000M});}}
4. Run the program
From the application's point of view, whenever a user transfers money from OutputAccount to InputAccount, this operation should be treated as a unit of work, and the amount of OutputAccount should never be deducted, while the amount of InputAccount has not increased. Next let's take a look at how EF manages transactions.
Before running the program, look at the database data:
Now, we try to use the transaction of EF to transfer from Zhang San of OutputAccount to Li Si of InputAccount.
Use EF default transaction execution
The default behavior of EF is that whenever any query involving Create,Update or Delete is executed, a transaction is created by default. When the SaveChanges () method on the DbContext class is called, the transaction commits.
Using EFTransactionApp.EF;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFTransactionApp {class Program {static void Main (string [] args) {using (var db = new EFDbContext ()) {int outputId = 1, inputId = 1; decimal transferAmount = 1000m / / 1 retrieve the account involved in the transaction var outputAccount = db.OutputAccounts.Find (outputId); var inputAccount = db.InputAccounts.Find (inputId); / / 2 deduct 1000 outputAccount.Balance from the output account-= transferAmount; / / 3 add 1000 inputAccount.Balance + = transferAmount from the input account / / 4 commit transaction db.SaveChanges ();}
After running the program, you will find that the data in the database has changed:
You can see that user Li Si's account is more than 1000, and user Zhang San's account is 1000 less. Therefore, these two operations are effectively wrapped in a transaction and executed as a unit of work. If any of the operations fail, the data will not change.
Some people may wonder: the above program executed successfully and did not see the effect of the transaction. Can you modify the code to let the above program fail and see the effect of the transaction? The answer is yes, let's modify the above code.
By looking at the database table structure, you will find that the data type of Balance is
It means that the maximum input length of the Balance column is 16 digits (the maximum length is 18 digits minus 2 decimal places). If the input length is greater than 16 digits, the program will report an error, so modify the above code as follows:
Using EFTransactionApp.EF;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFTransactionApp {class Program {static void Main (string [] args) {using (var db = new EFDbContext ()) {int outputId = 1, inputId = 1; decimal transferAmount = 1000m / / 1 retrieve the account involved in the transaction var outputAccount = db.OutputAccounts.Find (outputId); var inputAccount = db.InputAccounts.Find (inputId); / / 2 deduct 1000 outputAccount.Balance-= transferAmount from the output account / / 3 increase 1000 * 30000000000000000 times from the input account inputAccount.Balance + = transferAmount*3000000000000000; / / 4 commit transaction db.SaveChanges ();}
The next time you run the program, you will find that the program has reported an error:
At this time, I checked the database and found that 9000 of the balance of user Zhang San had not changed, indicating that the transaction worked.
5. Use TransactionScope to handle transactions
If there is a scene with multiple DbContext objects, then we want to associate operations involving multiple DbContext objects as a unit of work, and we need to wrap the call to the SaveChanges () method inside the TransactionScope object. To describe this scenario, we use two different instances of the DbContext class to perform deductions and collections, as follows:
Int outputId = 1, inputId = 1 new EFDbContext decimal transferAmount = 1000m using (var ts = new TransactionScope (TransactionScopeOption.Required)) {var db1 = new EFDbContext (); var db2 = new EFDbContext (); / / 1 account var outputAccount = db1.OutputAccounts.Find (outputId); var inputAccount = db2.InputAccounts.Find (inputId); / / 2 deduct 1000 outputAccount.Balance-= transferAmount from the output account / / 3 add 1000 inputAccount.Balance + = transferAmount; db1.SaveChanges (); db2.SaveChanges (); ts.Complete ();} from the input account
In the above code, we use two different DbContext instances to perform deduction and collection operations. Therefore, the default EF behavior does not work. When the respective SaveChanges () methods are called, the context-sensitive transactions are not committed. Conversely, because they are all inside the TransactionScope object, the transaction commits when the Complete () method of the TransactionScope object is called. If any of the operations fail, an exception occurs and TransactionScope does not call the Complete () method to roll back the changes. The case of transaction execution failure can also be modified as above so that the length of the Balance column exceeds the maximum length, which is not demonstrated here.
Third, use EF6 to manage transactions
Starting with EF6, EF provides the Database.BeginTransaction () method on the DbContext object, which is especially useful when using context classes to execute native SQL commands in a transaction.
Let's take a look at how to use this new method to manage transactions. Here, we use native SQL to deduct money from OutputAccount account, and use model class to collect money for InputAccount. The code is as follows:
Int outputId = 1, inputId = 1; decimal transferAmount = 1000m var sql (var db = new EFDbContext ()) {using (var trans = db.Database.BeginTransaction ()) {try {var sql = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@outputId" Db.Database.ExecuteSqlCommand (sql, new SqlParameter ("@ amountToDebit", transferAmount), new SqlParameter ("@ outputId", outputId)); var inputAccount = db.InputAccounts.Find (inputId); inputAccount.Balance + = transferAmount; db.SaveChanges () Trans.Commit ();} catch (Exception ex) {trans.Rollback ();}
Explain the above code a little bit: you first create an instance of the DbContext class, and then use this instance to start a transaction by calling the Database.BeginTransaction () method. This method returns a handle to the DbContextTransaction object that allows you to commit or roll back the transaction. Then use the native SQL to deduct money from the OutputAccount account and use the model class to collect money for InputAccount. Calling the SaveChanges () method affects only the second operation (after the transaction commits), but does not commit the transaction. If both operations are successful, the Commit () method of the DbContextTransaction object is called; otherwise, we handle the exception and call the Rollback () method of the DbContextTransaction object to roll back the transaction.
Fourth, use existing transactions
Sometimes we want to use an existing transaction in the DbContext class of EF. There may be several reasons:
1. Some operations may be done in different parts of the application.
2. EF is used for the old project, and this old project uses a class library that provides us with handles to transactions or database links.
For these scenarios, EF allows us to use an existing connection associated with the transaction in the DbContext class. Next, write a simple function to simulate the handle provided by the class library of the old project, which uses pure ADO.NET to perform the debit operation. The function is defined as follows:
Static bool DebitOutputAccount (SqlConnection conn, SqlTransaction trans, int accountId, decimal amountToDebit) {int affectedRows = 0; var command = conn.CreateCommand (); command.Transaction = trans; command.CommandType = CommandType.Text; command.CommandText = "Update OutputAccounts set Balance=Balance-@amountToDebit where id=@accountId" Command.Parameters.AddRange (new SqlParameter [] {new SqlParameter ("@ amountToDebit", amountToDebit), new SqlParameter ("@ accountId", accountId)}); try {affectedRows = command.ExecuteNonQuery () } catch (Exception ex) {throw ex;} return affectedRows = = 1;}
In this case, we cannot use the Database.BeginTransaction () method because we need to pass the SqlConnection object and the SqlTransaction object to the function and put the function in our transaction. In this way, we need to first create a SqlConnection, and then start SqlTransaction, with the following code:
Int outputId = 2, inputId = 1; decimal transferAmount = 1000m connectionString connectionString = ConfigurationManager.ConnectionStrings ["AppConnection"] .ConnectionString; using (var conn = new SqlConnection (connectionString)) {conn.Open (); using (var trans = conn.BeginTransaction ()) {try {var result = DebitOutputAccount (conn, trans, outputId, transferAmount) If (! result) throw new Exception ("No normal deduction!") ; using (var db = new EFDbContext (conn, contextOwnsConnection: false)) {db.Database.UseTransaction (trans); var inputAccount = db.InputAccounts.Find (inputId); inputAccount.Balance + = transferAmount; db.SaveChanges () } trans.Commit ();} catch (Exception ex) {trans.Rollback ();}
At the same time, the data context class needs to be modified, and the database context class code is modified as follows:
Using EFTransactionApp.Model;using System;using System.Collections.Generic;using System.Data.Common;using System.Data.Entity;using System.Linq;using System.Text;using System.Threading.Tasks;namespace EFTransactionApp.EF {/ / contextOwnsConnection / / false: indicates that the context has no relationship with the database connection, the context has been released, and the database connection has not been released; / / true: if the context is released, the database connection will be released. Public class EFDbContext:DbContext {/ / public EFDbContext () /: base ("name=AppConnection") / / {/ /} public EFDbContext (DbConnection conn, bool contextOwnsConnection): base (conn, contextOwnsConnection) {} public DbSet OutputAccounts {get; set;} public DbSet InputAccounts {get; set;}} V. Choose the appropriate transaction management
We already know several ways to use EF to get out of a transaction, and here's one by one:
1. If you have only one DbContext class, you should try to use EF's default transaction management. We should always combine all operations into a unit of work that is executed in the scope of the same DbContext object, and the SaveChanges () method commits the transaction.
2. If multiple DbContext objects are used, the best way to manage transactions may be to place the call in the scope of the TransactionScope object.
3. If you want to execute native SQL commands and want to associate these operations with transactions, you should use the Database.BeginTransaction () method provided by EF. However, this method only supports later versions of EF6, not previous versions.
4. If you want to use EF for an old project that requires SqlTransaction, you can use the Database.UseTransaction () method, which is available in EF6.
These are all the contents of the article "how Entity Framework uses Code First mode to manage transactions". Thank you for reading! I believe you will gain a lot after reading this article. The editor will update different knowledge for you every day. If you want to learn more knowledge, please pay attention to the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.