LINQ To SQL在N层应用程序中的CUD操作、批量删除、批量更新

原文:LINQ To SQL在N层应用程序中的CUD操作、批量删除、批量更新

0. 说明

    Linq to Sql,以下简称L2S。
    以下文中所指的两层和三层结构,分别如下图所示:

 

    准确的说,这里的分层并不是特别明确:
(1) 生成的DataContext(Linq t0 SQL Runtime)和Entity是放在一个文件中的,物理上不能切割开来;上图只是展示逻辑上的结构。
(2) 拿上图右边的三层结构来说,鉴于第(1)点,UI层就可以跨越BusinessLogic层,直接访问L2S层,这可能不是我们期望的。对于这个问题,可以有如下两种办法:A.建立自己的Entity层(DomainModel,UI与BL之间使用DomainModel,BL将DomainModel转换成L2S的Entity后,再交给L2S Runtime进行数据库更新);  B.告诉UI层开发人员不要访问L2S Runtime-,-

 

1. 生成DataContext和Entity
   1.1 使用VS自带的L2S对象关系设计器(O/R设计器)

    如果程序很简单只是拿来玩儿的,就少数几个表,可以直接使用此法,将表/视图/存储过程从数据源中拖拽到设计器中即可。但如果系统中的表很多,拖着拖着就把自己给拖晕了……

   1.2 使用SqlMeta工具

    用法见MSDN。    

sqlmetal /conn:"Data Source=server;Initial Catalog=database;Persist Security Info=True;User ID=sa;Password=password" /namespace:DataEntity /code:_Entity.cs /language:csharp /context:EntityDataContext /serialization:Unidirectional /views
   1.3 手工建立实体定义或者XML映射

    太多活要干,如果没有特殊场景必须手工Code这堆东西的话,建议还是省省……
具体可以参考MSDN:
使用代码编辑器自定义实体类 (LINQ to SQL)

 

2. 两层应用下的CUD操作

    两层应用下的较为简单,MSDN中有例子,借花献佛:

2.1 新增
   1: Order ord = new Order
   2: {
   3:     OrderID = 12000,
   4:     ShipCity = "Seattle",
   5:     OrderDate = DateTime.Now
   6:     // …
   7: };
   8: db.Orders.InsertOnSubmit(ord);
   9: db.SubmitChanges();
2.2 更新
   1: //1.查询需要修改的纪录[Lazy Load]
   2: IQueryable<Order> query =
   3:     from ord in db.Orders
   4:     where ord.OrderID == 11000
   5:     select ord;
   6: //2.修改
   7: foreach (Order ord in query)
   8: {
   9:     ord.ShipName = "Mariner";
  10:     ord.ShipVia = 2;
  11:     // Insert any additional changes to column values.
  12: }
  13: //3.保存
  14: db.SubmitChanges();
2.3 删除
   1: //1.查询需要修改的纪录[Lazy Load]
   2: IQueryable<Order> query =
   3:     from ord in db.Orders
   4:     where ord.OrderID == 11000
   5:     select ord;
   6: //2.修改
   7: foreach (Order ord in query)
   8: {
   9:     db.Orders.DeleteOnSubmit(ord);
  10: }
  11: //3.提交更改
  12: db.SubmitChanges();

 

 

3. 多层应用下的CUD操作

    新增操作同上。
    但修改和删除操作则需要做调整。在文章起始位置的分层结构图中可以看到,层之间是以Entity作为数据传输对象。由于Entity的状态是有DataContext进行监控的,脱离了DataContext,则L2S无法知道对象是否做了更新、做了什么更新。譬如我的BusinessLogic被封装在一个WCF应用中,在将Entity通过网络序列化到客户端时,这些Entity会与其DataContext分离;当Entity再从客户端传回到服务器端时,服务器端使用的是一个新的DataContext,继续用上面的方法来操作的话,执行时会报错。

   关于对象状态及跟踪,可以参考MSDN:对象状态与更改跟踪 (LINQ to SQL)

    一种解决办法是:将上层传过来的Entity重新Attach到新的DataContext中。

3.1 新增

新增不用Attach,直接按照2.1中同样的操作方式即可。(同2.1)

3.2 删除
   1: public void DeleteOrder(Order order)
   2: {
   3:     NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
   4:  
   5:     db.Orders.Attach(order, false); //重新Attach一次
   6:     db.Orders.DeleteOnSubmit(order);
   7:     try
   8:     {
   9:         db.SubmitChanges(ConflictMode.ContinueOnConflict);
  10:     }
  11:     catch (ChangeConflictException e)
  12:     {
  13:        //处理更改冲突
  14:     }
  15: }
3.3 修改

    Attach可以解决删除问题,但并不能解决所有的更新问题。因为在L2S中,需要支持开放式并发检查。L2S支持以下三种开放式并发方案中的更新:(有关开放式并发的更多信息,请参见MSDN:开放式并发概述 (LINQ to SQL)
(1).基于时间戳或 RowVersion 号的开放式并发。
(2).基于实体属性子集的原始值的开放式并发。
(3).基于完整原始实体和已修改实体的开放式并发。

    通俗的讲,就是如下根据如下三中处理并发的方式:
(1). 数据库表结构定义中包含时间戳(timestamp)字段:

    由于时间戳的值惟一,每次更新记录时其值会同时进行更新,因此只用根据主键+时间戳,就能判断记录是否被其他人更新过;如果没有被更新过,则可以进行更新;如果被更新过,则引发ChangeConflictException异常。

    时间戳字段对应的Entity定义中属性的Attribute会长成这个样子:[Column(Storage="_TimestampFieldName", AutoSync=AutoSync.Always, DbType="rowversion NOT NULL", CanBeNull=false, IsDbGenerated=true, IsVersion=true, UpdateCheck=UpdateCheck.Never)]。

   此时可以按照3.1类似的方式来Attach修改后的Entity,并提交更改到数据库:(可以用Sql Server的Profile来查看生成的TSQL)

   1: public void UpdateOrder(Order order)
   2: {
   3:     NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString);
   4:  
   5:     db.Orders.Attach(order, true);
   6:     try
   7:     {
   8:         db.SubmitChanges();
   9:     }
  10:     catch (ChangeConflictException e)
  11:     {
  12:        //处理更改冲突
  13:     }
  14: }


(2). 如果数据库表结构定义中不包含时间戳(timestamp)字段:

    如果继续用上面(1)的方式来更新,编译时不会报错,但执行时会报错。
    此时L2S不知道该咋更新了,于是就需要人为地告诉L2S该更新啥,具体包含如下两种方式
    (a). 提供原始对象及更新后的属性值

   1: public void UpdateOrder(Order o, short? newValue1, short? newValue2)
   2: {
   3:     using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
   4:     {
   5:         db.Orders.Attach(o, false);//o必须是原始的没有更改过的实体对象
   6:  
   7:         o.XXOO = newValue1;
   8:         o.OOXX = newValue2;
   9:         try
  10:         {
  11:              db.SubmitChanges();
  12:         }
  13:         catch (ChangeConflictException e)
  14:         {
  15:             // 处理更改冲突
  16:         }
  17:     }
  18: }

    (b). 提供原始对象及更新后的对象

   1: public void UpdateOrder(Order originalOrder, Order newOrder)
   2: {
   3:     using (NorthwindClasses1DataContext db = new NorthwindClasses1DataContext(connectionString))
   4:     {
   5:         db.Orders.Attach(newOrder, originalOrder);//originalOrder必须是原始的没有更改过的实体对象
   6:  
   7:         try
   8:         {
   9:              db.SubmitChanges();
  10:         }
  11:         catch (ChangeConflictException e)
  12:         {
  13:             // 处理更改冲突
  14:         }
  15:     }
  16: }

    第一种方式只需要对表增加TimeStamp类型的字段;而后面两种更新方式都需要一些额外的代码来进行处理;相比之下,还是第一种方式相对省力点儿。

 

    如果表比较多,逐个表增加TimeStamp字段就比较讨人厌了,于是有了下面的SQL,来为所有的表增加时间戳列:

   1: DECLARE @Table Table(ID int identity(1,1), TableName nvarchar(100));
   2: DECLARE @Index int, @TotalCount int;
   3: DECLARE @TableName nvarchar(100);
   4:  
   5: INSERT INTO @Table(TableName)
   6:     SELECT TOP 100 PERCENT T.name FROM dbo.sysobjects T WHERE (T.xtype = 'u');
   7: SET @TotalCount = @@RowCount;
   8:  
   9: SET @Index = 1;
  10: WHILE(@Index <= @TotalCount)
  11: BEGIN
  12:     SELECT @TableName = TableName FROM @Table WHERE ID=@Index;
  13:     
  14:     IF(NOT Exists(SELECT 1 FROM syscolumns WHERE id = object_id(@TableName) AND number = 0 
  15:                 AND type_name(xusertype) = 'timestamp'))
  16:     BEGIN
  17:         EXEC('ALTER TABLE ' + @TableName + ' ADD [Timestamp] timestamp')
  18:     END
  19:     
  20:     SET @Index = @Index + 1;
  21: END

尤其需要注意的是:

在尝试进行更新之前,不要将从数据库中检索数据作为一种获取原始值的方式。

 

4. 批量更新、批量删除

    然而,上面的处理方式只是处理的最基本的CUD操作,而实际应用中的业务逻辑不会这么简单。如果涉及到稍为复杂点儿的应用,譬如我要根据一个外部传进来的条件,执行批量修改、批量删除,该咋处理呢?

    关于批量删除和批量更新,老赵曾提出过自己的方案:扩展LINQ to SQL:使用Lambda Expression批量删除数据,其思路就是实现一个Expression<Func<T, bool>>解析器,并将Expression解析为最终需要执行的TSQL。在老赵的同篇英文博客中,有人在回复中提出了另一个思路,对L2S生成的TSQL进行包装,将其作为Update/Delete语句中的子查询,这样就可以直接复用L2S的查询解析器,使用L2S解析器提供的全部功能了;接下来有人将这个思路进行了实现:Batch Updates and Deletes with LINQ to SQL。原文作者写的非常详细了,并提供了源代码,可以下载回来学习或者直接复用。

   1: string productName = new StringBuilder("test").Append("productName").ToString();
   2: int productID = new Random().Next(10000000, Int32.MaxValue);
   3: Expression<Func<Products, bool>> predicate = p => p.ProductName.Contains(productName);
   4: Expression<Func<Products, Products>> evaluator = 
   5:     p => new Products() { UnitPrice = p.UnitPrice + 1, ProductName = "Test" };
   6:  
   7: UpdateBatch(predicate, evaluator);  //批量更新
   8: DeleteBatch(predicate);  //批量删除
   9: MultiSelect();           //批量查询
  10: DeleteByPK(productID);   //按主键删除
  11:  
  12: public void UpdateBatch(Expression<Func<Products, bool>> predicate,
  13:     Expression<Func<Products, Products>> evaluator)
  14: {
  15:     using (NorthWindDataContext context = new NorthWindDataContext())
  16:     {
  17:         context.Products.UpdateBatch(predicate, evaluator);
  18:     }
  19: }
  20:  
  21: public void DeleteBatch(Expression<Func<Products, bool>> predicate)
  22: {
  23:     using (NorthWindDataContext context = new NorthWindDataContext())
  24:     {
  25:         context.Products.DeleteBatch(predicate);
  26:     }
  27: }
  28:  
  29: public void MultiSelect()
  30: {
  31:     using (NorthWindDataContext context = new NorthWindDataContext())
  32:     {
  33:         var query1 = (from P in context.Products select P).Take(2);
  34:         var query2 = (from S in context.Suppliers select S).Take(2);
  35:         IMultipleResults result = context.SelectMutlipleResults(query1, query2);
  36:  
  37:         List<Products> products = result.GetResult<Products>().ToList();
  38:         List<Suppliers> suppliers = result.GetResult<Suppliers>().ToList();
  39:         ObjectDumper.Write(products);
  40:         ObjectDumper.Write(suppliers);
  41:     }
  42: }
  43:  
  44: public void DeleteByPK(int productID)
  45: {
  46:     using (NorthWindDataContext context = new NorthWindDataContext())
  47:     {
  48:         context.Products.DeleteByPK(productID);
  49:     }
  50: }

    具体生成的SQL,可以用SQL  Server Profile来查看。

 

参考《MSDN》:
1.做出和提交数据更改 (LINQ to SQL)
2.N 层应用程序中的数据检索和 CUD 操作 (LINQ to SQL)
3.开放式并发概述 (LINQ to SQL)
4.对象状态与更改跟踪 (LINQ to SQL)

时间: 2024-10-12 22:50:46

LINQ To SQL在N层应用程序中的CUD操作、批量删除、批量更新的相关文章

使用linq to sql 把数据库绑定到listview中

问题描述 使用linq to sql 把数据库绑定到listview中 请问怎么使用linq to sql 把数据库绑定到listview中,谢谢 解决方案 使用LINQ to SQL更新数据库ASP.NET MVC 学习 --- 第五课(使用Linq to SQL数据库)LINQ To SQL 中 聚合函数的使用问题 解决方案二: var query = from x in db.Table where 条件 select x; foreach (var item in query) { Li

[请教]计算机中不装MS Word可以在程序中显示和操作.doc文档吗?

问题描述 就是说本地计算机中不安装微软的Word程序,但是可以在本地计算机的程序中显示和操作word的.doc文档,有这样的插件和方案吗??本人写了一个调用word程序来显示和处理word文档的程序,但是客户嫌使用这个程序要先装word很麻烦,所以希望不装word就能在本人的程序中显示和处理word文档,不知道是否有高手做过类似的东西,提供个思路也行,拜谢!!! 解决方案 解决方案二:网上找找看有没有第三方控件支持,支持显示的应该能找到,支持编辑的可能就比较难找了解决方案三:好像没有什么特别好的

限制程序中某类操作的执行次数的算法设计及C代码实现

需求描述 编写程序实现限制程序中某类操作的执行次数的需求.为了便于说明,要求程序每天创建一个与上一天不同的文件目录.如果欲创建的文件目录已存在,则不用再创建.文件目录的命名格式为:FileDir_YYYYMMDD,如:FileDir_20160830. 程序流程 对于此类需求,最关键的问题是要设定一个标识来限制操作的执行次数.也就是说,当程序执行完一次操作之后,要有机制来限制它执行第二次操作. 因为本需求要求每天执行一次操作,所有我们自然想到了用日期来限制程序的执行次数.我们可以用一个全局时间变

Linq to Sql:N层应用中的查询(下): 根据条件进行动态查询

如果允许在UI层直接访问Linq to Sql的DataContext,可以省去很多问题,譬如在处理多表join的时 候,我们使用var来定义L2S查询,让编译器自动推断变量的具体类型 (IQueryable<匿名类型>), 并提供友好的智能提示:而且可以充分应用L2S的延迟加载特性,来进行动态查询.但如果我们希望将业 务逻辑放在一个独立的层中(譬如封装在远程的WCF应用中),又希望在逻辑层应用Linq to sql,则情况 就比较复杂了:由于我们只能使用var(IQueryable<匿

Linq to Sql:N层应用中的查询(上) : 返回自定义实体

如果允许在UI层直接访问Linq to Sql的DataContext,可以省去很多问题,譬如在处理多表join的时 候,我们使用var来定义L2S查询,让IDE自动推断变量的具体类型 (IQueryable<匿名类型>),并 提供友好的智能提示:而且可以充分应用L2S的延迟加载特性,来进行动态查询.但如果我们希望将业务 逻辑放在一个独立的层中(譬如封装在远程的WCF应用中),又希望在逻辑层应用Linq to sql,则情况就 比较复杂了:由于我们只能使用var( IQueryable<

LINQ to SQL实现数据访问通用基类

LINQ to SQL让人着迷,在.Net应用程序当中,.它提供了一种安全,强大和非常灵活的方式执行数据 访问,在当前微软传道者介绍上看,很容易上手. 不幸的是,当你对LINQ进行仔细研究后,我发现在多层架构中使用LINQ的并不是十分容易. 本文介绍用LINQ to SQL实现数据层的典型的问题点 ,并提供了一个简单,方便和灵活的方式来克服 它们. 本文附带的LING to SQL 实现数据访问通用类有以下的特点: 实现了存储库模式,你可以用不到10行代码执行LINQ实体类型的CRUD (Cre

在LINQ to SQL中使用Translate方法以及修改查询用SQL

目前LINQ to SQL的资料不多--老赵的意思是,目前能找到的资 料都难以摆脱"官方用法"的"阴影".LINQ to SQL最 权威的资料自然是MSDN,但是MSDN中的文档说明和实例总是显得"大开大 阖",依旧有清晰的"官方"烙印--这简直是一 定的.不过从按照过往的经验,在某些时候如果不按照微软划定的道道来走,可 能就会发现别样的风景.老赵在最近的项目中使用了LINQ to SQL作为数据层的基础,在LINQ to S

基于LINQ TO SQL的多层架构中,如何将实体附加至不同的DataContext

注意: 1.本文中所提到的"实体"均为由LINQ TO SQL生成的(即.dbml) 2.你需要了解LINQ TO SQL对表关联的实现方式,EntitySet 和 EntityRef 也许你看到标题后,会觉得问题比较抽象,那么我举个实例来具体说明一下问题. 在基于LINQ TO SQL的N层架构中,假如我们需要对一个实体进行更新,那么流程应是这样: 流程 BLL.GetModel(p=>p.id==1) --> 修改相应属性(字段)值 --> BLL.Update(

步步学LINQ to SQL:将类映射到数据库表

该系列教程描述了如何采用手动的方式映射你的对象类到数据表(而不是使用象SqlMetal这样的自动化工具)以便能够支持数据表之间的M:M关系和使用实体类的数据绑定.即使你选择使用了自动生成类的工具,理解这一实现过程可以让你更加方便地对你的应用程序加以扩展. 下面阐述本文的目标以及该示例程序为初级开发人员介绍如何学习LINQ的基本要点: ·使用LINQ to SQL将SQL Server数据库表映射到与之关联的对象上. ·执行一些简单的LINQ查询来检索数据. 本文详细为你阐述了如何在你的应用程序中