一.事務 (1) 事務接著上篇繼續講完。如果使用了多種數據訪問技術,來訪問關係型資料庫,則可能希望在這些不同技術所執行的操作之間共用事務。下麵示例顯示瞭如何在同一事務中執行 ADO.NET SqlClient 操作和 Entity Framework Core 操作。 (2) 使用 System.T ...
一.事務
(1) 事務接著上篇繼續講完。如果使用了多種數據訪問技術,來訪問關係型資料庫,則可能希望在這些不同技術所執行的操作之間共用事務。下麵示例顯示瞭如何在同一事務中執行 ADO.NET SqlClient 操作和 Entity Framework Core 操作。
using (var connection = new SqlConnection(connectionString)) { //使用ado.net 打開資料庫連接 connection.Open(); //使用ado.net 開啟事務 using (var transaction = connection.BeginTransaction()) { try { // Run raw ADO.NET command in the transaction var command = connection.CreateCommand(); command.Transaction = transaction; command.CommandText = "DELETE FROM dbo.Blogs"; command.ExecuteNonQuery(); // Run an EF Core command in the transaction var options = new DbContextOptionsBuilder<BloggingContext>() .UseSqlServer(connection) .Options; using (var context = new BloggingContext(options)) { //EF事務結合ado.net事務 context.Database.UseTransaction(transaction); context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" }); context.SaveChanges(); } // Commit transaction if all commands succeed, transaction will auto-rollback when disposed if either commands fails transaction.Commit(); } catch (System.Exception) { // TODO: Handle failure } } }
(2) 使用 System.Transactions
如果需要跨大作用域進行協調,則可以使用分散式事務(跨庫事務)TransactionScope,它可協調跨多個資源管理器的事務。存在於ADO.NET 中的System.Transactions命令空間。此功能是 EF Core 2.1 中的新增功能。雖然該功能在 .NET Framework 的 ADO.NET 提供程式之間十分常見,但最近才將 API 添加到 .NET Core,因此支持並未得到廣泛應用。
//設置事務隔離級別IsolationLevel.ReadCommitted using (var scope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })) { using (var connection = new SqlConnection(connectionString)) { connection.Open(); try { // Run raw ADO.NET command in the transaction var command = connection.CreateCommand(); command.CommandText = "DELETE FROM dbo.Blogs"; command.ExecuteNonQuery(); // Run an EF Core command in the transaction var options = new DbContextOptionsBuilder<BloggingContext>() .UseSqlServer(connection) .Options; using (var context = new BloggingContext(options)) { context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" }); context.SaveChanges(); } // Commit transaction if all commands succeed, transaction will auto-rollback // when disposed if either commands fails scope.Complete(); } catch (System.Exception) { // TODO: Handle failure } } }
二. 非同步保存
關於使用非同步的註意事項和優勢,在第33篇 EF查詢數據中有講到。Entity Framework Core 提供了 DbContext.SaveChangesAsync() 非同步替代了 DbContext.SaveChanges() 同步方法。下麵是一個保存,使用非同步示例
public static async Task AddBlogAsync(string url) { using (var context = new BloggingContext()) { var blog = new Blog { Url = url }; context.Blogs.Add(blog); await context.SaveChangesAsync(); } }
三.不同上下文的實體狀態判斷
有時會使用一個上下文實例查詢實體,然後使用其他上下文實例對其進行保存。 這通常在“斷開連接”的情況下發生,例如 Web 應用程式,此情況下實體被查詢、發送到客戶端被修改、在請求中發送回伺服器,然後進行保存。 在這種情況下,第二個上下文實例需要知道實體是新實體(應插入)還是現有實體(應更新)。
3.1標識新實體
下麵介紹了幾中方式確定是插入還是更新的實體情況:
(1)使用自動生成的鍵
可以理解為在資料庫端設置ID鍵自增長,可以通過鍵值來判斷是新增還是修改。
/// <summary> ///(1)使用鍵的內置方法來檢查 true: 新增(上下文類中) /// </summary> /// <param name="entity"></param> /// <returns></returns> public bool IsItNew( object entity) => !this.Entry(entity).IsKeySet; // (2)已知類型檢查 true: 新增 public bool IsItNew(Blog blog) => blog.BlogId <= 0; // b is false 修改實體 var blog = BloggingContext.Blogs.First(); bool b = BloggingContext.IsItNew(blog); //b is true 新增實體 var blog = new Blog() { Url = "www.baidu.com" }; bool b = BloggingContext.IsItNew(blog);
(2) 使用其它鍵
未自動生成鍵值時,需要使用其他某種機制來確定新實體。 有以下兩種常規方法(查詢實體 或 從客戶端傳遞標誌)。若要查詢實體,只需使用 Find 方法, 例如下所示:
public static bool IsItNew(BloggingContext context, Blog blog) => context.Blogs.Find(blog.BlogId) == null;
3.2 保存單個實體
如果知道是需要插入還是需要更新,則可以相應地使用 Add 或 Update(之前是新增還是修改,是根據ChangeTracker跟蹤器自動檢測的,因為是同一個下下文而且實體有主鍵)如下所示:
public static void Insert(DbContext context, object entity) { context.Add(entity); context.SaveChanges(); } public static void Update(DbContext context, object entity) { context.Update(entity); context.SaveChanges(); }
如果實體不使用自動生成的鍵,則應用程式必須確定是應插入實體還是應更新實體:例如:
public static void InsertOrUpdate(BloggingContext context, Blog blog) { var existingBlog = context.Blogs.Find(blog.BlogId); if (existingBlog == null) { context.Add(blog); } else { // SetValues 調用將根據需要,標記要更新的實體屬性。原理是:要更新的實體與之前查詢的實體進行比較,只會更新實際發生更改的列 context.Entry(existingBlog).CurrentValues.SetValues(blog); } context.SaveChanges(); }
四. 設置SQL Server IDENTITY列中的顯式值
對於大多數情況,是由資料庫生成自增長ID。如果要將顯式值插入SQL Server IDENTITY
列,需要在調用SaveChanges()
之前,手動啟用IDENTITY_INSERT
。如下所示:
using (var context = new EmployeeContext()) { context.Employees.Add(new Employee { EmployeeId = 100, Name = "John Doe" }); context.Employees.Add(new Employee { EmployeeId = 101, Name = "Jane Doe" }); context.Database.OpenConnection(); try { context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.Employees ON"); context.SaveChanges(); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.Employees OFF"); } finally { context.Database.CloseConnection(); } }
參考文獻
EF第三方擴展工具