最近搞Linq To Sql(以下简称LS)上瘾了,越玩越好玩,废话不多说。切入正题批更新和批删除早就被作为LS的软肋被广大程序员们嗤之以鼻,园子里也有人做了些扩展来满足批操作需求,我本来打算直接COPY这些代码在项目中使用的,遗憾的是我没找到批更新的,批删除的有几种,但我都觉得要么太取巧,要又是不完善,我这里也献丑一下,做个批操作扩展,既然大家都做了批删除,我挑个冷门的,批更新吧,其实删除和更新都差不多吧。
      我们想要的其实很简单,UPDATE [TABLE] SET ..... WHERE....,尴尬的是这样的语句在LS面前几乎成了奢求,Attach方法勉强可以用用,但是每次重新生成LS实体都要重新设置非条件属性的UpdateCheck = UpdateChek.Never让人无法接受,况且他也就只能做做 WHERE TABLE.XXX = XXX,如果我需要 WHERE TABLE.XXX != XXX或者其他更复杂的条件时这种方法就显得很无力了,扩展,自我扩展,这似乎是我最近在LS娱乐城里玩的最多的游戏了,今天我就要实现这么一个调用Table<TEntity>.Update(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TEntity>> evaluator):
      还是得说说JeffreyZhao(他动作太快了,每次我想写的内容一到他博客里看都是成年旧醋了), 他已经做过批更新的,具体可看这里,遗憾的是如他所说:“放弃了对于复杂表达式树的解析,不支持item.Introduction.Length < 10这种条件”,另有BLACK JACK通过正则式匹配查询语句,可看这里,但是我对那个正则表达式不是很放心,毕竟SQL的变化太多。结合了前面两种做法,我也提出我的思路,我们知道IQueryable的查询语句是可以获得的,那么我们能否在批操作时候直接运用这个SQL呢?答案是肯定的,例如我有这么一个SELECT的SQL(OrderId是主键):
                           SELECT * from Order WHERE Order.OrderId = 123;
     那么可以想办法更新这些查询结果中的记录,SQL语句可以这么写:

                          UPDATE [Order]
                          SET [XXX] = xxxx

                          FROM [dbo].[Order ] AS T1 INNER JOIN (

                          SELECT *
                          FROM [Order]
                          WHERE [Order].[OrderId ] = 123
                         ) AS T2 ON (T1.[OrderId] = T2.[OrderId])

     很显然,子查询里的语句是直接获取的,这样我们的任务就变成构造SET [XXX] = xxxx....的字符串和join语句了,SET后面会说,这里直接给JOIN语句的构造代码:
     
老调新弹,也玩Linq To Sql批操作private static string GetJoinCondition<TEntity>(this Table<TEntity> table)
老调新弹,也玩Linq To Sql批操作            
where TEntity : class

       按照上面的举例,直接调用上面的GetFormatCmdText扩展方法我们将得到如下SQL:

                          UPDATE [Order]
                          SET {0}

                          FROM [dbo].[Order ] AS T1 INNER JOIN (

                          SELECT *
                          FROM [Order]
                          WHERE [Order].[OrderId ] = 123
                         ) AS T2 ON (T1.[OrderId] = T2.[OrderId])

     现在的问题就是有SET后的赋值SQL串了,这也是批更新的难点,表达式树的解析很复杂,所幸的是有这么一个老外的博客提供了现成的代码给我们用:

     http://blogs.msdn.com/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx

     里面有ExpressionVisitor类可供使用,代码我就不贴出来了,需要的朋友可浏览上面的链接。通过代码我们可以获取赋值表达式的MemberInitExpression对象,把它转换成SET的SQL语句片段也很简单:
      
                          
老调新弹,也玩Linq To Sql批操作private static string GetDbSetString<TEntity>( MemberInitExpression memberInitExpression, Table<TEntity> table, DbCommand updateCommand ) where TEntity : class
        }

    需要注意的是,上面的代码参数中的updateCommand,由于我们的updateCommand其实是通过IQueryable获得的,所以里面可能带了参数,因此我们赋值用的参数必须向这个Command中添加,最后就是语句拼接和参数处理了,也就是完成扩展方法:
老调新弹,也玩Linq To Sql批操作public static int Update<TEntity>(this Table<TEntity> table, Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TEntity>> evaluator) where TEntity : class

     有了这个扩展我们就可以这样更新数据了:
     
老调新弹,也玩Linq To Sql批操作using (DataClasses1DataContext context = new DataClasses1DataContext(ConnectionString))
            }
     
     这个扩展并非完美无暇,可能有朋友注意了上面代码中GetDbSetString方法有这么一句:
      else
                {
                    throw NotSupportedException("not support the method call expression");
                }
     经发现有时候Expression<Func<TEntity, TEntity>> evaluator表达式的可能产生内嵌的MethodCallExpression的,对于这样的表达式我没有更好的方式来解析,因此放弃了这种情况的处理,例如:

          o=> new Order() {Number= o.Number.Number+ " AAA"}

     对于这种内联调用这个扩展暂时无能为力,好了,文章完,有了UPDATE的思路DELETE也就可以同样的方式扩展了。

相关文章:

  • 2021-11-30
  • 2021-11-16
  • 2022-12-23
  • 2021-07-09
  • 2021-06-21
  • 2021-07-22
猜你喜欢
  • 2022-12-23
  • 2021-12-17
  • 2021-06-05
相关资源
相似解决方案