公司的ERP框架是用ORM技术来访问数据库的,但有些查询还是会用DataTable保存数据,并且会把用户修改后的数据保存到服务器中。在习惯了ORM的写法后,对于用DataTable的保存用户修改过的数据,然后保存到数据库中反而有些不适应。ORM会自动检测到哪些数据项被改动了,进而生成必要的UPDATE子句,如果没有数据被更改,则不会产生任何UPDATE语句,这是ORM的好处与便利。把这个技巧应用到DataTable中,来看看下面的性能优化技巧。

DataColumn item = new DataColumn("IsChanged", typeof(bool));
item.DefaultValue = false;
itemTable.Columns.Add(item);

有一个临时的查询表itemTable,保存物料的计划信息。在物料计划功能中,我们会修改一下物料的计划数据,然后把它保存到数据库中。因为有很多物料,有的会被修改计划日期,有的不会修改,所以我给物料计划表itemTable加了一个额外的字段IsChanged,以表示这个物料的物料计划是否被修改过。如果被修改过,比如下面的方法

itemTable.Rows[0]["PlanQty"]=200;
itemTable.Rows[0]["IsChanged"]=true;

第二行代码,我会把这个table的IsChange列的值设为true,表示这一行已经被修改过,在保存时,需要生成SQL UPDATE语句。这样,在窗体被关闭时或用户点击保存按钮时,用下面的判断语句

int changed=(from item in itemTable.AsEnumerable()
                    where item.Fields<bool>("IsChanged")==true
                    selectg item).Count();
if(changed)
{
       foreach(item in  itemTable.AsEnumerable()
                                 where item.Fields<bool>("IsChanged")==true)
       //save item and its plan qty
}

changed变量判断当前是否有物料的计划数量被更改过,如果有才保存被改过的物料及其计划,否则不会做任何动作。

以此类推,这个技巧还可以应用于删除或是新增

DataColumn item = new DataColumn("IsNew", typeof(bool));
item.DefaultValue = false;
itemTable.Columns.Add(item);

item = new DataColumn("IsDeleted", typeof(bool));
item.DefaultValue = false;
itemTable.Columns.Add(item);

虽然这是个很小的技巧,却可以解决很多场合的问题。举例说明

1 用户打开物料计划功能,只是看了一下,没有修改任何物料及其计划,在退出功能时,你不应该提示用户保存,因为用户没有作任何数据修改动作。

2 用户新增加了一条物料及其计划,你可以判断是新加的,进而生产INSERT语句,而不是UPDATE语句。如果不用这个技巧,你需要到数据库中去判断是否有这个物料的计划数据,如果有则产生UPDATE语句,否则产生INSERT语句。应用我说的这个技巧(IsNew),你可以明显的减少往返于数据库之间的逻辑,性能会有明显的改善。

3 用户删除了一条物料及其计划,itemTable少了一行,你怎么把它写回到数据库中去呢?应用这个IsDeleted技巧,不对itemTable调用DeleteRow方法,而是把它的IsDeleted设为true,表示这个物料的计划被删除了,需要产生DELETE语句发送到数据库中。

在判断itemTable是否被修改过,也可以应用这个技巧

foreach (DataRow row in itemTable.Rows)
{
         if (row.RowState == DataRowState.Unchanged)
                    continue;
         //save changed item and its plan qty

}

DataRow有一个RowState属性,以表示这个表是否被修改过。你可以不用加上面的IsChanged列而应用RowState来判断,也可以达到这个目的。DataRowState.Added 表示是新增加的一行数据,其它的值是

// Summary:
//Gets the state of a System.Data.DataRow object.
[Flags]
public enum DataRowState
{
        // Summary:
        //     The row has been created but is not part of any System.Data.DataRowCollection.
        //     A System.Data.DataRow is in this state immediately after it has been created
        //     and before it is added to a collection, or if it has been removed from a
        //     collection.
        Detached = 1,
        //
        // Summary:
        //     The row has not changed since System.Data.DataRow.AcceptChanges() was last
        //     called.
        Unchanged = 2,
        //
        // Summary:
        //     The row has been added to a System.Data.DataRowCollection, and System.Data.DataRow.AcceptChanges()
        //     has not been called.
        Added = 4,
        //
        // Summary:
        //     The row was deleted using the System.Data.DataRow.Delete() method of the
        //     System.Data.DataRow.
        Deleted = 8,
        //
        // Summary:
        //     The row has been modified and System.Data.DataRow.AcceptChanges() has not
        //     been called.
        Modified = 16,
}
 

对于Add或Delete的情况,我还是习惯于加IsNew或IsDeleted列,尽管这不是必要的。

在应用ORM框架保存实体时,它会检测实体的字段属性是否被修改,只有修改过的属性,才会出现在ORM框架生成的UPDATE语句中,这样确实有效率,与判断DataTable的列值是否被修改过相似,看似乎一点点的改进,对于系统的性能提升是有好处的。

posted @ 2011-12-23 17:59 James Li 阅读(1191) 评论(3) 编辑

对于习惯于用ORM来开发系统的开发人员来说,几乎不用写SQL语句,但是也要针对ORM框架,来设计合适的查询,ORM框架会生成合适的T_SQL语句并发送到SQL Server中。由于ORM框架有好几种,比如NHibernate,LLBL Gen,Entity Framwork,掌握熟练的SQL查询技术在这里没有用武之地,真是可惜。这篇文章是介绍我的Management Console中的一个工具程序,把SQL查询语句转化为ORM代码片段,相当于一个代码生成工具。

选定Northwind数据库的客户表为例子,它的脚本是这样的,这里省略了表的约束代码。

CREATE TABLE [dbo].[Customers]
(
[CustomerID] [nchar] (5) COLLATE Chinese_PRC_CI_AS NOT NULL,
[CompanyName] [nvarchar] (40) COLLATE Chinese_PRC_CI_AS NOT NULL,
[ContactName] [nvarchar] (30) COLLATE Chinese_PRC_CI_AS NULL,
[ContactTitle] [nvarchar] (30) COLLATE Chinese_PRC_CI_AS NULL,
[Address] [nvarchar] (60) COLLATE Chinese_PRC_CI_AS NULL,
[City] [nvarchar] (15) COLLATE Chinese_PRC_CI_AS NULL,
[Region] [nvarchar] (15) COLLATE Chinese_PRC_CI_AS NULL,
[PostalCode] [nvarchar] (10) COLLATE Chinese_PRC_CI_AS NULL,
[Country] [nvarchar] (15) COLLATE Chinese_PRC_CI_AS NULL,
[Phone] [nvarchar] (24) COLLATE Chinese_PRC_CI_AS NULL,
[Fax] [nvarchar] (24) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]


现在要开发一个查询客户信息的程序,找出客户ID为ANATR的地址,公司名称和联系人信息

image

由于使用了LLBL Gen ORM框架作为数据访问技术,上面的SQL语句显然不能直接拼凑成C#代码,需要进行转化,封装

的过程比较相对容易,但是也需要耗费一些时间和精力。来看看我设计代码生成工具ORM Querier

image

在上面的文本框中输入SQL语句,点击Generate生成按钮,同下面自动生成了C#代码。如果你熟悉LLBL Gen框架的API接口,拷贝这片代码到C#中是可以直接运行的,相当于我们的SQL查询语句的ORM版形式。

目前因为只用到了LLBL Gen,所以它只生成了基于LLBL Gen的代码片段。它的基本原理是解析T_SQL语句,生成映射的C#语句,所以,推广到生成NHibernate或是Entity Framework不是难题。

这个工具我酝酿了一段时间,一方面要推广自己的EPN快速开发框架,面对大量的不熟悉LLBL Gen这个框架的用户。另一方面,把理论变成代码工具实践,也需要一些技术上的判断与实作。

我在接触LLBL Gen这个ORM框架时,也会经常忘记它的SELECT是如何的写,UPDATE如何写,于是要经常查资料,后来干脆把资料整理出来,打印放到桌子上,经常看一下也不会忘记。如下图所示

image

这样的工作方法,效率又高了很多。但是也不是每天都会和代码打交道,有时候几个月的时间都是在修改报表,这一块的内容又忘记了。既然是有规律可寻的,就可以探索出合适的工具出来,把规律变成鼠标点击的简单动作,这样大大减少出错的机率,于是就产生了这个工具。试想一下,SQL查询技术,你不会忘记,在熟悉的SQL Server Management Studio中输入要查询的SQL语句,把它拷贝到ORM Querier中,执行Generate转化命令,就得到你需要的ORM代码片段。经过这样的改善,做事的效率又提高了很多,而且准确率高,不会出错。


经常地思考如何改善工作效率,把一些规律性的知识和技术转化为软件或程序,经过几年的积累就形成了我的开发工具系列工具集Management Console。我在epn.codeplex.com中开放了一部分的Management Console的源代码,欢迎下载使用,希望能对你的工作和学习有所启发和帮助。

posted @ 2011-12-23 13:32 James Li 阅读(1283) 评论(0) 编辑

园子里这两天讨论的比较多的是CSDN-中文IT社区 600万用户数据在互联网上的传播,作为一名技术人员,暂且放下各自的想法的意见,仅仅从技术的角度来讲,分析和考验一下我们的编程基础。

把下载到的压缩文件释放到硬盘中,得到文件www.csdn.net.sql。作为技术人员,对这个文件产生好奇心和编程的冲动。于是提出了下面的两条编程题目:

1 如何把600万数据导入到SQL Server中?
2 如何选择合适的密码加密方法?

这个SQL文件有273MB,用SQL Server Management Studio打开它,会产生OutOfMemory异常,用记事本打开也很慢,推荐用Ultra Edit源代码编辑软件打开。

估计大家的硬盘里面都有这个文件了,我也就不必给密码和邮件加上阴影了。

用自己曾经注册到的邮件找一下,果然找到了自己的帐号和密码,确实是明文的。

分析一下SQL文件,它是由换行符号分开的,以#分隔用户帐号,密码,电子邮件的格式。这和逗号分隔文件格式很相似,于是动手把它导入到SQL Server中。请参考下面的两行代码

CREATE TABLE CsdnUser ([USER] NVARCHAR(MAX) )
  BULK INSERT CsdnUser FROM N'F:\www.csdn.net.sql'

然后就是漫长的等待了,在4G内存,4核的Windows 7 x64的机器上,也跑了近5分钟才把数据从文本文件导入到SQL Server的CsdnUser表中。平时喜欢研究性能,性能改善研究,心中一阵窃喜。执行完成后,看到Messages中显示

(6428632 row(s) affected),果然有600多万行数据,放数据库文件的磁盘一下子耗费了近6G的空间。

跑一下SQL语句 SELECT [User] FROM CsdnUser,用户数据就已经存放到了SQL Server数据库文件中了。

这样完成了文本文件到SQL Server的导入过程。如果我还想进一步的分解每一行的数据,像下面的结果显示这样

SELECT  dbo.GetSplitOfIndex(User,'',1) AS [UserId]

             dbo.GetSplitOfIndex(User,'',2) AS [Password]

            dbo.GetSplitOfIndex(User,'',3) AS [Email]

FROM  CsdnUser

则还需要写一个split函数,以分解开用#分开的用户数据。GetSplitOfIndex函数来源于互联网,地址是

http://www.fengfly.com/plus/view-168590-1.html, 作者秩名,感谢他的智慧。函数的源代码如下所示

CREATE FUNCTION [dbo].[GetSplitOfIndex]
(
      @String NVARCHAR(MAX) ,  --要分割的字符串
      @split NVARCHAR(10) ,  --分隔符号
      @index INT --取第几个元素
)
RETURNS NVARCHAR(1024)
AS 
    BEGIN
        DECLARE @location INT
        DECLARE @start INT
        DECLARE @next INT
        DECLARE @seed INT
 
        SET @String = LTRIM(RTRIM(@String))
        SET @start = 1
        SET @next = 1
        SET @seed = LEN(@split)
  
        SET @location = CHARINDEX(@split, @String)
        WHILE @location <> 0
            AND @index > @next 
            BEGIN
                SET @start = @location + @seed
                SET @location = CHARINDEX(@split, @String, @start)
                SET @next = @next + 1
            END
        IF @location = 0 
            SELECT  @location = LEN(@String) + 1 
  
        RETURN SUBSTRING(@String,@start,@location-@start)
    END

自SQL Server 2005起就开始集成CLR,允许.NET托管代码运行于SQL Server中,用我们熟悉的.NET API可以轻易的完成任务。打开Visual Stdio 2010,创建一个SQL Server CLR项目,SQL Server CLP函数的源代码清单如下

[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString Split(string UserLine,int Index)
{
        if (!string.IsNullOrEmpty(UserLine))
        {
            string[] lines = Regex.Split(UserLine, " # ");
            if (lines.Length == 3)
                return new SqlString(lines[Index]);
            else
                throw new ApplicationException("invalid input line");
        }
        return new SqlString("");
}

它的调用方法和TSQL函数的方式一样,如下图所示

SELECT  dbo.Split(User,0) AS  [User Id]

              dbo.Split(User,1)  AS [Password]

              dbo.Split(User,2) AS [Email]

      FROM CsdnUser

顺利完成第一题,如果你有更好的方法,欢迎留言。

 

对于密码是否用明文,是否要加密,各种情况下所采取的方法都有道理。不给密码加密,明文传送是不值得推荐,不过冒似很多系统都是用明文保存在数据库中的。如果你担心明文不好,容易被人家获取到,可以采用可逆的加密。请参考我的文章《数据加密小工具》,它对需要保护的数据进行简单的加密,也很容易的逆反到当初的明文

image

在我的知识管理系统Data Solution的用户反馈功能中,用户输入反馈的内容,发送到指定的邮箱中,因为使用的是smtp的方式,所以要把用户名和密码写到程序中去,但为了防止别人很轻易的从IL代码中读到到我的邮件的帐号和密码,我就使用了上面的可逆加密的方法,把邮件帐号和密码进行加密后,再放到程序代码中,这样可以增加被破解的难度。也没有把很重要的资料放到这个用于接受用户反馈的邮件帐号中,毕竟还是可能被人家破解的。

再进一步的,可以采用.NET提供的算法,进行Hash运算或是加密。MD5是公认的不可逆的加密算法,它的调用方法如下

byte[] Original = Encoding.Default.GetBytes(txt_Source.Text); 
MD5 s1 = MD5.Create(); //使用MD5 
byte[] Change = s1.ComputeHash(Original);  加密 
txt_Result.Text = Convert.ToBase64String(Change);

请参考文章《使用MD5將字串加密 C# VS2005 Sample Code》学习MD5的调用方法。

如果你需要MD5的调用例子代码,下面的代码应该可以帮助到你,代码来原于园子里的朋友,
地址是http://www.cnblogs.com/konooo/archive/2009/01/22/1379920.html 

 /// MD5 16位加密 加密后密码为大写
 /// </summary>
/// <param name="ConvertString"></param>
/// <returns></returns>
public static string GetMd5Str(string ConvertString)
{
      MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
      string t2 = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8);
      t2 = t2.Replace("-", "");
      return t2;
} 
/// <summary>
/// MD5 16位加密 加密后密码为小写
/// </summary>
/// <param name="ConvertString"></param>
/// <returns></returns>
public static string GetMd5Str(string ConvertString)
{
      MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
      string t2 = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8);
      t2 = t2.Replace("-", "");
      t2 = t2.ToLower();
      return t2;
}

/// <summary>
/// MD5 32位加密
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
static  string UserMd5(string str)
{
      string cl = str;
      string pwd = "";
      MD5 md5 = MD5.Create();//实例化一个md5对像
      // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择 
      byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(cl));
      // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得
      for (int i = 0; i < s.Length; i++)
      {
       // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符 
       pwd = pwd + s[i].ToString("X");
      }
      return pwd;
}

前几年是经常在CSDN上逛,也喜欢它的下载频道,可以找到很多资源,后来下载资源要积分的,没有分就不能下载,于是自己注册了多个CSDN的帐号,只用来下载资源。这里我有两句微词,既然是共享目的,为何要限制别人下载呢?新浪的爱问知识人就没有这么复杂的下载规则,即使被下载的文件需要积分,你也可以找到不要分的,免限制下载的资源。还有一条规则,如果你的CSDN帐号不够一定的级别,只能上传,但不能删除你上传的文件。我自己也曾在CSDN上发布过软件程序,后来有新版本发布,无法自己删除旧版本的程序,要到论坛去发贴子,依靠管理员才能删除,就这一条,使我彻底放弃了把CSDN作为程序代码的发布渠道。 不过,CSDN还是对我们的技术有过很大的帮助,遇到问题用Baidu搜索到的头条通常就是CSDN的论坛的贴子,这一点要对CSDN表示感谢和致敬。

posted @ 2011-12-23 09:09 James Li 阅读(10533) 评论(80) 编辑