ASP.NET 3.5核心编程学习笔记(22):Linq-to-SQL 数据的更新、事务、存储过程、函数
时间:2011-04-20 来源:辛勤的代码工
一旦实体对象被加载到内存中,插入新订单的工作就非常简单,无非是创建Order类的实例并将其添加到客户对象的Orders集合中。Linq-to-SQL会跟踪数据上下文中的更改,并在调用数据上下文对象的SubmitChanges时将那些更改传回数据库。下例显示了一个更改数据的过程:
string id = "ALFKI";
Customer c = dataContext.Customers.SingleOrDefault(c => c.CustomerID == id);
if(c != null)
{
//修改数据
c.Address = "123 Flowers Streat);
//提交更改到数据库中
dataContext.SubmitChanges();
}
对象的添加与删除
数据上下文对象会自动跟踪添加到上下文的对象,并跟踪已被更新、删除或添加的对象。调用SubmitChanges方法会指示数据上下文类,针对底层的数据库,将挂起的更改转化为更新语句。
添加新记录示例:
Customer customer = new Customer();删除示例:
customer.CustomerID = "KOEN2";
customer.CompanyName = "...";
...
dataContext.Customers.InsertOnSubmit(customer);
dataContext.SubmitChanges();
Customer koen2 = dataContext.Customers.SingleOrDefault(c => c.CustomerID == "KOEN2");
if(koen2 != null)
{
dataContext.Customers.DeleteOnSubmit(koen2);
dataContext.SubmitChanges();
}
在成功执行SubmitChanges后,已删除对象在数据上下文中会被标记为Deleted,而不会将其从内部缓存中实际删除。
跨表更新
Linq-to-SQL使我们能够在数据上下文中对表间关系进行建模,也支持跨表更新。我们要做的只是从相应的Table对象中将对象移除,其余的工作由SubmitChanges完成。
//创建客户删除上例中添加的客户与订单:
Customer mands = new Customer();
//mands的属性赋值
...
dataContext.Customers.InsertOnSubmit(mands);
//添加订单
Order order = new Order();
//为order的属性赋值
...
//将订单添加到mands的Orders属性中
mands.Orders.Add(order);
//提交更改到数据库中
dataContext.SubmitChanges();
//取出客户的订单
var orders = from o in dataContext.Orders
where o.CustomerID == "MANDS"
select o;
//删除订单
foreach(var o in orders)
dataContext.Orders.DeleteOnSubmit(o);
//取出客户对象
Customer mands = dataContext.Customers.SingleOrDefault(c => c.CustomerID == "MANDS");
if(mands != null)
{
//删除客户并提交更改到数据库中
dataContext.Customers.DeleteOnSubmit(mands);
dataContext.SubmitChanges();
}
注意,我们应该首先删除客户订单,然后再删除该客户。如果先删除客户,因为客户表与订单表存在表间约束,将引发异常。
开放式并发
以上例为例,我们在删除订单时如果使用SQL Profile工具跟踪SQL实际执行的语句,可发现它执行的是如下的语句:
Delete From dbo.Customers
Where CustomerID = 'MANDS'
and CompanyName = 'Managed Design'
and ContactName = 'Dino'
and ContactTitle is NULL
and Address = 'Via dei Tigli'
and City = 'Milan'
and Region is NULL
and PostalCode is NULL
and Country = 'Italy'
and Phone is NULL
and Fax is NULL
这种技术称为“开放式并发”,它要求在更新和删除记录前,检测是否有其他事务对该记录做了修改。“保守式并发”在更新或删除时,为避免冲突,会对记录加锁。默认情况下,Linq-to-SQL使用的是开放式并发。
我们可以将一个参数传给SubmitChanges,指示它忽略冲突异常并继续执行:
dataContext.SubmitChanges(ConflictMode.ContinueOnConflict);
默认值为ConflictMode.FailOnFirstConflict。如果选择继续更新,如何获知哪些命令失败呢?
为此,我们可以遍历数据上下文的ChangeConflicts集合:
try
{
dataContext.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch(ChangeConflictException e)
{
foreach(ObjectChangeConflict occ in dataContext.ChangeConflicts)
{}
}
ObjectChangeConflict类中定义的属性使我们能了解到底发生了什么,在哪张表上发生的,以及涉及了哪个实体对象。
更新操作的自定义
为删除某个客户的所有订单,如果不使用LINQ,我们可能会这样做:
Delete From orders Where customerid = 'MANDS'
Linq-to-SQL提供了一种方法,可以更改实体对象更新操作的实现。由于Linq-to-SQL构建于分部类之上,我们只要添加一个分部方法就可以了。
下面是Customer实体的数据上下文类,可以通过分部方法扩展列表:
partial void InsertCustomer(Customer instance);这类方法的形式为InsertXxx、UpdateXxx、DeleteXxx,其中Xxx代表数据上下文中实体的名称。这样,在删除客户记录时,我们可以这样做:
partial void UpdateCustomer(Customer instance);partial void DeleteCustomer(Customer instance);
partial void DeleteCustomer(Customer instance)
{
this.ExecuteCommand("Delete From customers Where customerid=@id", instance.CustomerID);
}
使用此扩展方法后,在删除客户记录时自动删除相关订单。
我们还可以用更新操作的自定义特性,用存储过程代替T-SQL命令来执行数据库操作。
使用事务
通过SubmitChanges提交的所有更新都会在一个事务中进行。调用该方法后,Linq-to-SQL会验证当前调用是否处于其他事务的作用范围,确保用户发起的事务没有显式地绑定到数据上下文。如果没有现有事务,Linq-to-SQL会启动一个本地事务,并使用该事务执行所有T-SQL命令。当所有命令都执行完毕后,Linq-to-SQL会提交该事务。
我们可以在自己的代码中使用TranscationScope对象封装由SubmitChanges方法引发的任何数据库操作。如,通过外部的TranscationScope对象,我们可以将其他的非数据库资源引入到事务中,可以向MSMQ发送消息,也可以是更新文件系统。若SubmitChanges检测到自身处于某个现有事务的作用域内,它不仅会避免新事务的创建,而且还会避免连接的关闭。
我们还可自行发起一个事务,将多个SubmitChanges的命令与该事务关联。为此,我们可使用数据上下文对象的Transaction属性。提交或回滚完全取决于开发者。如果事务的连接字符串与数据上下文所使用的不匹配,则会有异常抛出。
使用存储过程
我们可以在分部方法中使用ExecuteCommand方法执行存储过程。示例:
partial void DeleteCustomer(Customer instance)
{
this.ExecuteCommand("exec delete_customer @id", instance.CustomerID);
}
此外,我们可以在VS2008的O/R设计器将存储过程添加到数据上下文中,就可将存储过程当成数据上下文的方法来调用。
假设我们已将名为CustOrderHist的存储过程添加到数据上下文中,调用方法如下:
var orders = dataContext.CustOrderHist(customerID);
我们可使用var关键字对结果类型进行推断。若存储过程返回多个值或不同类型的值,数据上下文中该存储过程的封装器会返回IMultipleResults类型,在这种情况下,我们可使用GetResult<T>来访问给定的结果。其中T用于指定特定结果的类型。
使用用户定义的函数
与存储过程类似,用户在数据库中的自定义函数也可附加到数据上下文中,并在页面中以方法的形式调用。