LINQ(数据库操作增、删、改及并发管理)

本文将演示如何通过 Entity Framework 数据模型创建、修改、删除数据库记录。

Customer cust = new Customer()
 {
     CustomerID = "LAWN",
     CompanyName = "Lawn Wranglers",
     ContactName = "Mr. Abe Henry",
     ContactTitle = "Owner",
     Address = "1017 Maple Leaf Way",
     City = "Ft. Worth",
     Region = "TX",
     PostalCode = "76104",
     Country = "USA",
     Phone = "(800) MOW-LAWN",
     Fax = "(800) MOW-LAWO"
 };
  
 NorthwindEntities db = new NorthwindEntities();
 db.Customers.AddObject(cust);
 db.SaveChanges();

可以多次 AddObject()后调用一次 SaveChanges() 全部写入数据库。

1. 创建部分加载的实体类

       在之前的示例里,我们调用了 Customer 实体类的默认构造函数,它创建的实例没有加载任何数据。不过,我们还可以通过在构造函数里指定必须字段的值来减少数据库错误的风险。

       每个实体类都有一个名为 CreateT 的工厂方法,例如,Customer 的实体类的工厂方法是 CreateCustomer 。看下面示例:

Customer cust = Customer.CreateCustomer("LAWN", "Lawn Wranglers");
 cust.ContactName = "Mr. Abe Henry";
 cust.ContactTitle = "Owner";
 cust.Address = "1017 Maple Leaf Way";
 cust.City = "Ft. Worth";
 cust.Region = "TX";
 cust.PostalCode = "76104";
 cust.Country = "USA";
 cust.Phone = "(800) MOW-LAWN";
 cust.Fax = "(800) MOW-LAWO";
  
 NorthwindEntities db = new NorthwindEntities();
 db.Customers.AddObject(cust);
 db.SaveChanges();

我们倾向于使用默认构造函数,因为可以在一条语句里指定属性的值,但是如果你经常会忘记给必需的字段赋值,那么工厂方法对你就非常有用

2. 插入关联的实体

       可以用实体类的导航属性创建一组关联的对象,然后一次把它们存储到数据库:

Customer cust = new Customer
 {
     CustomerID = "LAWN",
     CompanyName = "Lawn Wranglers",
     ContactName = "Mr. Abe Henry",
     ContactTitle = "Owner",
     Address = "1017 Maple Leaf Way",
     City = "Ft. Worth",
     Region = "TX",
     PostalCode = "76104",
     Country = "USA",
     Phone = "(800) MOW-LAWN",
     Fax = "(800) MOW-LAWO",
     Orders = {
         new Order{
             CustomerID = "LAWN",
             EmployeeID = 4,
             OrderDate = DateTime.Now,
             RequiredDate = DateTime.Now.AddDays(7),
             ShipVia = 3,
             Freight = new Decimal(24.66),
             ShipName = "Lawn Wranglers",
             ShipAddress = "1017 Maple Leaf Way",
             ShipCity = "Ft. Worth",
             ShipRegion = "TX",
             ShipPostalCode = "76104",
             ShipCountry = "USA"            
         }
    }
 };
  
 NorthwindEntities db = new NorthwindEntities();
 db.Customers.AddObject(cust);
 db.SaveChanges();
 如果单独创建 Order 和 Customer 对象,就不得不显式的添加 Order:
Customer cust = new Customer
 {
     CustomerID = "LAWN",
     CompanyName = "Lawn Wranglers",
     ContactName = "Mr. Abe Henry",
     ContactTitle = "Owner",
     Address = "1017 Maple Leaf Way",
     City = "Ft. Worth",
     Region = "TX",
     PostalCode = "76104",
     Country = "USA",
     Phone = "(800) MOW-LAWN",
     Fax = "(800) MOW-LAWO",
 };
  
 Order ord = new Order
 {
     CustomerID = "LAWN",
     EmployeeID = 4,
     OrderDate = DateTime.Now,
     RequiredDate = DateTime.Now.AddDays(7),
     ShipVia = 3,
     Freight = new Decimal(24.66),
     ShipName = "Lawn Wranglers",
     ShipAddress = "1017 Maple Leaf Way",
     ShipCity = "Ft. Worth",
     ShipRegion = "TX",
     ShipPostalCode = "76104",
     ShipCountry = "USA"
 };
  
 NorthwindEntities db = new NorthwindEntities();
 db.Customers.AddObject(cust);
 db.Orders.AddObject(ord);
 db.SaveChanges();

更新

       更新实体类和修改对象的属性一样简单:

NorthwindEntities db = new NorthwindEntities();
 Customer cust = (from c in db.Customers
                  where c.CustomerID == "LAWN"
                  select c).Single();
 cust.ContactName = "John Smith";
 cust.Fax = "(800) 123 1234";
 db.SaveChanges();

Single():返回序列的唯一元素;如果该序列并非恰好包含一个元素,则会引发异常。

删除

       删除也很简单:

NorthwindEntities db = new NorthwindEntities();
  
 IEnumerable<Order_Detail> ods = from o in db.Order_Details
                                 where o.OrderID == 10248
                                 select o;
  
 // 对 LINQ 查询而返回的结果集进行处理
 // 要么使用 Single() 取出单条记录
 // 要么就迭代集合进行处理
 foreach (Order_Detail o in ods)
 {
     db.Order_Details.DeleteObject(o);
 }
  
 db.SaveChanges();

注意:Entity Framework 不会删除关联的实体对象,因此调用 SaveChanges()前必须小心地删除所有通过外键约束关联的对象

管理并发

       Entity Framework 默认使用乐观并发模型,也就是说在读取数据后,它不检查是否有人修改了数据库中的数据。调用 SaveChanges()时,所有待更新数据全部被写到数据库中,即使他人已经更新了有冲突的记录时也是如此。

       乐观并发会导致痛苦的数据一致性问题

       可以让 Entity Framework 在更新前检查数据库是否由第三方执行了更改,虽然这还是乐观并发,因为实体对象不会锁住数据库的任何对象。但至少它可以在发生问题时给你提醒。

       打开实体数据模型,选中字段,在属性中设置“并发模式”为“Fixed”,如下图:

技术分享

 

处理并发冲突

       为实体对象启用并发冲突检查后,试图更新已经被更新过的数据时,会得到一个 OptimisticConcurrencyException 。为了模拟并发异常,我们使用 Entity Framework 执行更新,然后通过 ExecuteStatementInDb 方法直接执行会造成冲突的 SQL 语句:

protected void Page_Load(object sender, EventArgs e)
 {
     NorthwindEntities db = new NorthwindEntities();
     Customer cust = db.Customers
         .Where(c => c.CustomerID == "LAZYK")
         .Select(c => c).First();
  
     Response.Write(string.Format("Initial value {0}<br />", cust.ContactName));
  
     // change the record outside of the entity framework
     string sql = string.Format(@"update Customers set ContactName = ‘Samuel Arthur Sanders‘ 
                 where CustomerID = ‘LAZYK‘");
     ExecuteStatementInDb(sql);
  
     // modify the customer
     cust.ContactName = "John Doe";
  
     // save the changes
     try
     {
         db.SaveChanges();
     }
     catch (OptimisticConcurrencyException)
     {
         Response.Write("Detected concurrency conflict - giving up<br />");
     }
     finally
     {
         sql = string.Format(@"select ContactName from Customers 
             where CustomerID = ‘LAZYK‘");
         string dbValue = GetStringFromDb(sql);
         Response.Write(string.Format("Database value: {0}<br />", dbValue));
         Response.Write(string.Format("Cached value: {0}<br />", cust.ContactName));
     }
 }
  
 private void ExecuteStatementInDb(string sql)
 {
     string conStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
     SqlConnection conn = new SqlConnection(conStr);
     SqlCommand cmd = new SqlCommand(sql, conn);
     try
     {
         conn.Open();
         Response.Write("Executeing Sql statement against database with ADO.NET ...<br />");
         cmd.ExecuteNonQuery();
         Response.Write("Database updated.<br />");
         conn.Close();
     }
     catch (Exception err)
     {
         throw new ApplicationException(err.Message);
     }
     finally
     {
         conn.Close();
     }
 }
  
 private string GetStringFromDb(string sql)
 {
     string conStr = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
     SqlConnection conn = new SqlConnection(conStr);
     SqlCommand cmd = new SqlCommand(sql, conn);
     try
     {
         conn.Open();
         object obj = cmd.ExecuteScalar();
         conn.Close();
         return obj.ToString();
     }
     catch (Exception err)
     {
         throw new ApplicationException(err.Message);
     }
     finally
     {
         conn.Close();
     }
 }

技术分享

       因为在 ContactName 字段上做了并发检查,因此在 Entity Framework 更新时捕获了这个异常,最终更新并没有成功。

       不过,我们除了要检查数据的差异,还是想把数据写回数据库,这时可以调用 ObjectContext.Refresh()来解决这一问题,可以在捕获异常时增加这样一条代码:

catch (OptimisticConcurrencyException)
 {
     Response.Write("Detected concurrency conflict - giving up<br />");
     db.Refresh(RefreshMode.StoreWins, cust);
 }

Refresh():

  • 参数一:RefreshMode 枚举
  • 参数二:要刷新的对象

RefreshMode.StoreWins 表示用数据库中的值更新实体对象的值

RefreshMode.ClientWins 表示用实体对象的值更新数据库中的值

       让我们回顾一下这里所发生的一切。我们试图向数据库写入在其他某处已经被更新了的数据。Entity Framework 检测到了并发冲突并抛出 OptimisticConcurrencyException 异常,让我们知道发生了问题。我们用数据库里的数据刷新修改了实体对象,它使得我们重新回到一致的状态。

       但我们的更新发生了什么?嗯,什么也没有发生。如果我们一定要应用自己的修改的话,就应该使用 RefreshMode.ClientWins 枚举值,并再次调用 SaveChanges():

catch (OptimisticConcurrencyException)
 {
     Response.Write("Detected concurrency conflict - giving up<br />");
     db.Refresh(RefreshMode.ClientWins, cust);
     db.SaveChanges();
 }

       这一回,就好像说“我知道有并发冲突发生了,但我也坚持我的更新”那样。为妥善的处理并发冲突时我们要指出一点:刷新实体对象时可能会有人再次修改数据,也就是说第二次调用 SaveChanges()可能会引发另一个 OptimisticConcurrencyException 异常,为了解决这个问题,我们可以循环尝试应用更新:

 

// modify the customer
 cust.ContactName = "John Doe";
  
 int maxAttempts = 5;
 bool recordsUpdated = false;
  
 for (int i = 0; i < maxAttempts && !recordsUpdated; i++)
 {
     try
     {
         db.SaveChanges();
         recordsUpdated = true;
     }
     catch (Exception)
     {
         db.Refresh(RefreshMode.ClientWins, cust);
     }
 }

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。