2023年2月

SQLite是一个小型的C程序库,实现了独立的,可嵌入的,零配置的SQL数据库引擎,SQLite用的非常广泛,支持通用的SQL92标准,支持事务操作,

支持最大可达2T的数据库,在小型数据库使用性能上由于微软的Access数据库等等特点。

由于默认的微软企业库EnterpriseLibary不支持

SQLite的数据库访问,因此需要在


企业库EnterpriseLibary中访问这种数据库的话,需要使用一个企业库的扩展类库,该类库可以
http://entlibcontrib.codeplex.com/
上下载,结合一起使用,非常方便,很好的利用了微软企业库的优势和特点。

默认如果没有采用微软

企业库EnterpriseLibary 的话,紧紧使用SQLite的对象来操作数据库,是这样的

///

<summary>


///
执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。

///

</summary>


///

<param name="sql">
SQL语句
</param>


///

<returns>


///
返回查询结果的所有记录的第一个字段,用逗号分隔。

///

</returns>



public
string
SqlValueList2(
string
sql)
{
SQLiteConnection connection =
new
SQLiteConnection(connectionString);
SQLiteCommand cmd =
new
SQLiteCommand(sql, connection);

connection.Open();
StringBuilder result =
new
StringBuilder();
using
(SQLiteDataReader dr = cmd.ExecuteReader())
{
while
(dr.Read())
{
result.AppendFormat(
"
{0},
"
, dr[
0
].ToString());
}
}

string
strResult = result.ToString().Trim(
'
,
'
);
return
strResult;
}

///

<summary>


///
执行SQL查询语句,返回所有记录的DataTable集合。

///

</summary>


///

<param name="sql">
SQL查询语句
</param>


///

<returns></returns>



public
DataTable SqlTable2(
string
sql)
{
DataSet ds =
new
DataSet();
SQLiteDataAdapter adpater =
new
SQLiteDataAdapter(sql, connectionString);
adpater.Fill(ds);

return
ds.Tables[
0
];
}

如果使用上面的做法,只需要指定连接字符串ConnectionString即可,一般可以这样指定其数据库连接字符串。

connectionString =
string
.Format(
@"
Data Source={0}\OrderWater.db;Version=3;
"
, Application.StartupPath);

如果是使用微软的企业库EnterpriseLibary来进行数据库访问的话,那么需要在配置文件中配置数据库访问,如下所示。

注意红色框部分就可以顺利配置好Sqlite数据库的企业库配置信息了。

然后我们在代码中访问使用Sqlite数据库的时候,就非常透明了。


///

<summary>


///
执行SQL查询语句,返回查询结果的所有记录的第一个字段,用逗号分隔。

///

</summary>


///

<param name="sql">
SQL语句
</param>


///

<returns>


///
返回查询结果的所有记录的第一个字段,用逗号分隔。

///

</returns>



public
string
SqlValueList(
string
sql)
{
Database db = DatabaseFactory.CreateDatabase();
DbCommand cmd = db.GetSqlStringCommand(sql);

StringBuilder result =
new
StringBuilder();
using
(IDataReader dr = db.ExecuteReader(cmd))
{
result.AppendFormat(
"
{0},
"
, dr[
0
].ToString());
}
string
strResult = result.ToString().Trim(
'
,
'
);
return
strResult;
}


///

<summary>


///
执行SQL查询语句,返回所有记录的DataTable集合。

///

</summary>


///

<param name="sql">
SQL查询语句
</param>


///

<returns></returns>



public
DataTable SqlTable(
string
sql)
{
Database db = DatabaseFactory.CreateDatabase();
DbCommand cmd = db.GetSqlStringCommand(sql);

return
db.ExecuteDataSet(cmd).Tables[
0
];

}

如果结合我的分页控件使用,那么分页控件的查询数据的使用代码如下所示:

///

<summary>


///
标准的记录查询函数

///

</summary>


///

<param name="where"></param>


///

<param name="pagerInfo"></param>


///

<returns></returns>


private
DataTable FindToDataTable(
string
where
, PagerInfo pagerInfo)
{
WHC.Pager.WinControl.PagerHelper helper =
new
WHC.Pager.WinControl.PagerHelper(
"
All_Customer
"
,
"
*
"
,
"
LastUpdated
"
, pagerInfo.PageSize, pagerInfo.CurrenetPageIndex,
true
,
where
);
string
countSql = helper.GetPagingSql(WHC.Pager.WinControl.DatabaseType.SQLite,
true
);
string
dataSql = helper.GetPagingSql(WHC.Pager.WinControl.DatabaseType.SQLite,
false
);

string
value = SqlValueList(countSql);
pagerInfo.RecordCount = Convert.ToInt32(value);
//
为了显示具体的信息,需要设置总记录数


DataTable dt = SqlTable(dataSql);
return
dt;

}

效果如下所示:

DevExpress样式版本的分页控件效果如下所示(均支持SQLite分页)

由于Sqlite的开发,在日常的项目中,还是比较常见的,因此,为了方便,我把Sqlite的数据访问也集成到我的Winform框架系统中了,这样通过扩展,支持更多的数据库,项目视图如下所示。

如果你有Sqlite的项目开发,使用这个做好的框架,应该是能事半功倍的。

最后,希望和大家一起共同研究技术,探讨代码。。。

我在前面几篇介绍过我的Winform开发框架的相关内容,其中主要集中在界面展现以及各层的总体设计上,还没有空来得及深入进行各个重要细节的讲解,今天我们来介绍、讨论下我的Winform开发框架之数据访问层的设计方面的知识,希望对大家有所帮助。

前面介绍过,该Winform开发框架支持多种数据库的访问,只需要对配置进行修改即可切换,如下图所示。

其中我们可以看到,里面有几种数据访问层的相关数据,大致知道是通过同一的IDAL数据访问层接口派生出几个对应不同数据库的操作层,由业务逻辑层BLL进行相应的调用,但是具体细节从上图并不能了解到。

其实他们的大致关系如下图所示。

首先BLL层,通过BLLFactory对象,根据配置信息,实例化不同的业务访问对象,转换为数据访问层接口进行调用的,BLLFactory通过反射间接方式,构建出不同数据库对象的数据访问层,而数据访问对象,他们都集成了基类BaseDAL,这个基类封装类了绝大多数的增删改查等基础性操作,并通过高度抽象,只需要继承该基类的子类重载部分函数即可完成较为丰富的操作实现。具体的继承关系如下图所示。

由于每个不同数据库都需要拥有一个BaseDAL,那么很多相同的操作代码就会发生冗余,因为大多数数据库的基础操作是一样的,只有一部分比较特别,需要进行个性化处理,因此对以上的数据访问层进行优化设计,得到下面的设计图,如下所示。

由于把BaseDAL层的通用操作,进一步提升抽象到AbstractBaseDAL 类里面进行管理,因此,BaseDAL虽然在各种数据库的数据访问层中存在,但是,却只是需要实现很少的代码,如下所示。

因为不同的数据库,FindFirst(查找第一条)、FindLast(查找最后一条)、Insert2(插入记录后,返回新增的主键值)、以及分页查询FindToDataTable、FindWithPager等不同。因此数据访问层的BaseDAL子类需要覆盖基类AbstractBaseDAL的这些实现。

通过实现部分个性化数据库操作的函数及强大的基类

AbstractBaseDAL实现,我们可以看到,整个数据访问层基类得到非常强大的操作功能,如下所示。

为了说明问题,我列举几个上面函数不同的实现给读者,以便说明问题。

1)SqlServer的FindFirst语句如下:

string
sql =
string
.Format(
"
Select top 1 {0} From {1} Order by {2} ASC
"
, selectedFields, tableName, GetSafeFileName(sortField));

2)Oracle的FindFirst语句如下:

string
sql =
string
.Format(
@"
Select * from (Select {0} From {1} Order by {2} ASC)   WHERE ROWNUM <= 1 ORDER BY ROWNUM ASC
"
, selectedFields, tableName, GetSafeFileName(sortField));


3)Sqlite的FindFirst语句如下:

string
sql =
string
.Format(
"
Select {0} From {1} Order by {2} ASC LIMIT 1
"
, selectedFields, tableName, GetSafeFileName(sortField));

为了实现功能强大的数据访问基类,并尽可能减少重复代码,高度提炼基类是很有效的方法。适当的集成关系,使得代码量更少,扩展更加容易,这个就是我的Winform框架的优化思想。

在我的Winform开发框架中,底层的数据访问支持多种不同的数据库,在数据库访问的开发过程中,发现Sqlite多数情况下,操作都和SqlServer或者说是和标准Sql差不多的。当然,Sqlite本身也有一些特殊的语句支持,本文主要根据自己的了解,以及在支持的Sqlite基类中的提炼,总结他们常用到的一些不同,以便以后查阅,并和大家分享讨论。

在我的Winform开发框架中,是支持Sqlite等这样的数据库接入的,示例图如下所示。

现在根据我整理的一些特殊地方,和大家分享讨论:

1)特殊字符

每种数据库,基本上在操作SQL语句中,都有自己的一些特殊的分隔符号,如防止和关键字重名的特殊引用字符,SqlServer是[], 这方面Sqlite也沿用这个。参数化语句的占位符,SqlServer和Access是@,Oracle是:,而Sqlite则支持$和@。

2)获取返回刚刚插入的主键字段值(自增长)

这种操作也是经常用到的,一般Sqlserver是用SELECT SCOPE_IDENTITY() 语句操作,Access用的是SELECT @@IDENTITY,Oracle由于用的是序列,所以是SELECT Seq_TestTable.Currval ID From Dual这样的操作,Sqlite呢,他也有自己的特殊语句了,就是:Select LAST_INSERT_ROWID() ,是不是比较容易记住呢。

3)Limit语句使用

在SqlServer中,我们常用Top语句来操作数据,以便获取必要的记录信息,这个在Sqlite不支持的,而它和MySql一样,是使用LIMIT语句来实现相同的效果,下面就是我Winform开发框架中,获取第一条记录和最后一条记录的基类方法,我在这里分享一下。

///

<summary>


///
查找记录表中最旧的一条记录

///

</summary>


///

<returns></returns>


public
override
T FindFirst()
{
string
sql =
string
.Format(
"
Select {0} From {1} Order by {2} ASC LIMIT 1
"
, selectedFields, tableName, GetSafeFileName(sortField));
T entity =
null
;

Database db = CreateDatabase();
DbCommand command = db.GetSqlStringCommand(sql);

using
(IDataReader dr = db.ExecuteReader(command))
{
if
(dr.Read())
{
entity = DataReaderToEntity(dr);
}
}
return
entity;
}

///

<summary>


///
查找记录表中最新的一条记录

///

</summary>


///

<returns></returns>


public
override
T FindLast()
{
string
sql =
string
.Format(
"
Select {0} From {1} Order by {2} DESC LIMIT 1
"
, selectedFields, tableName, GetSafeFileName(sortField));
T entity =
null
;

Database db = CreateDatabase();
DbCommand command = db.GetSqlStringCommand(sql);

using
(IDataReader dr = db.ExecuteReader(command))
{
if
(dr.Read())
{
entity = DataReaderToEntity(dr);
}
}
return
entity;

}


4、分页实现

前面我介绍了一篇文章,介绍整合了Sqlite语句分页逻辑的分页控件,随笔名称是《
Winform分页控件更新之集成Sqlite数据库分页
》,其实Sqlite分页逻辑也不神秘,无非也是利用Limit语句实现所需要的记录获取,如下所示。

///

<summary>


///
不依赖于存储过程的分页(SQLite)

///

</summary>


///

<param name="isDoCount">
如果isDoCount为True,返回总数统计Sql;否则返回分页语句Sql
</param>


///

<returns></returns>


private
string
GetSQLiteSql(
bool
isDoCount)
{
string
sql =
""
;
if
(
string
.IsNullOrEmpty(
this
.strwhere))
{
this
.strwhere =
"
(1=1)
"
;
}

if
(isDoCount)
//
执行总数统计


{
sql =
string
.Format(
"
select count(*) as Total from {0} Where {1}
"
,
this
.TableOrSqlWrapper,
this
.strwhere);
}
else
{
//
SELECT * FROM 表名称 LIMIT M,N


string
strOrder =
string
.Format(
"
order by {0} {1}
"
,
this
.fieldNameToSort,
this
.isDescending ?
"
DESC
"
:
"
ASC
"
);

int
minRow = pageSize * (pageIndex -
1
);
int
maxRow = pageSize * pageIndex;
sql =
string
.Format(
"
select {0} from {1} Where {2} {3} LIMIT {4},{5}
"
,
fieldsToReturn,
this
.TableOrSqlWrapper,
this
.strwhere, strOrder, minRow, maxRow);
}

return
sql;

}

代码生成工具一直是很多从事开发人员的必备,一般开发人员都会选择一款高效、适合自己开发模式的代码生成工具,我也不例外,我一直让我的Database2Sharp(
http://www.iqidi.com/database2sharp.htm
)代码生成工具围绕我的开发框架来生成代码,至今已经经过快7个年头的洗礼,主要是提供高效、快速的一键生成整个项目框架源码的操作。

代码生成工具Database2Sharp自一开始,就采用基于模板方式的代码生成方式,这样提高生成效率,并且减少维护成本(相对某些硬编码代码生成的方式更优),方便用户对模板进行适当的修改等特点,不过虽然是提供了模板修改的功能,但是由于在工具的主界面上,并未提供对自定义模板文件的管理,因此以前的版本一直没有完成用户自定义模板管理维护的功能,本次版本更新就是弥补这一不足,发挥模板应有的魅力和功能。

在实际开发当中,自定义模板生成是很多代码生成工具生成代码的重要补充,例如有些项目代码片段需要(
例如控件赋值语句,或者控件数据显示语句等
),如果能通过自定义模板方式,利用模板引擎的灵活特点,以及已有数据库的结构信息,就很方便生成重复性强、有一定规律的代码。

因此,本次Database2Sharp版本更新,主要就是提供一个对自定义模板管理,方便利用自定义模板灵活、高效的特点,并且利用后台数据库表、字段等信息,为实际的项目代码片段生成服务,本次还调整了整体的软件界面布局,利用成熟的Weifenluo布局控件,更好展示多文档的信息。


我们来看看自定义代码生成模块的功能是如何实现的,首先提供一个自定义模板列表进行维护,可以通过右键菜单进行添加、修改、重命名、删除等操作,模板代码可以进行【直接执行模板】和【选表进行代码生成】两种方式。

利用【选表进行代码生成】方式,可以很好利用后台的数据库表、字段等信息,结合模板生成高效的代码,如下所示。


另外,为了方便大家对模板引擎NVelocity的了解,在软件工具安装的时候,附带了几个常见的例子,如下所示。

例子的代码大致如下所示,主要是让大家快速了解Nvelocity的模板语言VTL的使用。如果对模板引擎更加深入的了解,可以查看我之前的随笔《
强大的模板引擎开源软件NVelocity
》进一步了解。

遍历及注释,赋值语句例子。

##Foreach语法操作函数

#
set
( $criteria = [
"
name
"
,
"
address
"
] )
#
foreach
( $criterion
in
$criteria )
$criterion
#end


##注释操作

注释
单行注释
## This
is
a single line comment

多行注释
#*
Thus begins a multi-line comment. Online visitors won
'
t


see
this
text because the Velocity Templating Engine will
ignore it.
*#


条件结构的例子

##此处为注释说明
##简单例子(主要规则:引用以$开头用于取得什么东西,而指令以# 开始用于做什么事情)
##在VTL中,所有变量标识符的开头要加上$字符,如$Name,也可以用一种更加明确的方法表示,例如${name}。

#
set
($foo =
false
)
#if
($foo)
this
is
true
#elseif ($bar)
this
is
false
#elseif (
true
)
this
should be followed by two blank lines
#end

##
this
is
a single line comment

#*
this
is
a multi line comment
#if
(
*#


#
set
($user =
"
jason
"
)
#
set
($login =
false
)
#
set
($count =
5
)

#if
($user == "jason")
the user $user
is
logged
in
!
#end

#if
($count == 5)
the count
is
5
!
#end

#if
($login == false)
the user isn
'
t logged in.


#end

#if
($count != 3)
\$count
is
not equal to
3

#end


宏脚本例子

## #macro 脚本元素允许模板设计者在VTL 模板中定义重复的段。
## Velocimacros 不管是在复杂还是简单的场合都非常有用。
## 下面这个Velocimacro,仅用来节省击键和减少排版错误,介绍了一些NVelocity宏的概念。
## 可以带参数,参数放在宏名称的后面,空格隔开

#macro( d )
<tr><td></td></tr>
#end

#d()


以及一些常用的赋值及数据显示代码片段

///

<summary>


///
初始化

///

</summary>


///

<param name="info">
实体类信息
</param>


private
void
InitData(${ClassName}Info info)
{
#
foreach
($ColumnInfo
in
${TableInfo.ColumnList.Values})
#if
(${ColumnInfo.AutoIncrement} == false)
#if
(${ColumnInfo.NetType} == "System.String" )
this
.txt${ColumnInfo.Name.Alias.ToCapit()}.Text = info.${ColumnInfo.Name.Alias.ToCapit()};
#elseif(${ColumnInfo.NetType} ==
"
System.DateTime
"
)
this
.txt${ColumnInfo.Name.Alias.ToCapit()}.Text = info.${ColumnInfo.Name.Alias.ToCapit()}.ToShortDateString();
#else
this
.txt${ColumnInfo.Name.Alias.ToCapit()}.Text = info.${ColumnInfo.Name.Alias.ToCapit()}.ToString();
#end
#end #
#endif
#end
}

///

<summary>


///
获取数据

///

</summary>



private
${ClassName}Info GetData()
{
${ClassName}Info info =
new
${ClassName}Info();
#
foreach
($ColumnInfo
in
${TableInfo.ColumnList.Values})
#if
(${ColumnInfo.AutoIncrement} == false)
#if
(${ColumnInfo.NetType} == "System.Decimal" )
info.${ColumnInfo.Name.Alias.ToCapit()} = Helper.SafeConvertDecimal(
this
.txt${ColumnInfo.Name.Alias.ToCapit()}.Text);
#elseif(${ColumnInfo.NetType} ==
"
System.DateTime
"
)
info.${ColumnInfo.Name.Alias.ToCapit()} = Helper.SafeConvertDate(
this
.txt${ColumnInfo.Name.Alias.ToCapit()}.Text);
#else
info.${ColumnInfo.Name.Alias.ToCapit()} =
this
.txt${ColumnInfo.Name.Alias.ToCapit()}.Text;
#end
#end #
#endif
#end

}


当然,了解这些可能还不够,还需要了解模板后台,能够利用的数据属性等信息,包括数据库、表、字段等相关的属性,方便在模板引擎中进行调用,生成更加强大的代码片段。

软件安装后,有一个帮助文件,是提供给开发者进行了解模板后台数据的相关属性和方法的,如下所示。

运行后如下所示:

除了以上新增的功能外,一键生成基于我的Winform框架结构的项目代码,是最为重要的功能,可以体验一下。另外,代码生成工具生成的数据库文档,快速生成实体类信息等模块,也是开发非常常用的功能,如果想了解更加关于我的代码生成工具的信息,可以参考我博客里面的标签【
代码生成工具
】。

在很久之前,由于对淘宝的API开发比较感兴趣,写了几篇关于淘宝API开发方面的随笔《
淘宝API开发系列--开篇概述
》和《
淘宝API开发系列--商家的绑定
》等文章,近期由于一客户需要一些淘宝API的例子,而我原来开发的例子已经过去两年了,之前我是基于一个中间组件top4net来进行开发的,这个模块就是在淘宝API SDK的基础上做了一些封装,不过原来的例子确已经不能运行了。

因此重新捡起行头,继续研究有啥变化的地方,不过发现虽然时间过去已经快两年了,淘宝API的使用模式,一直没有变化过,可能变化的就是API的一些细微的地方吧,因此这次,就摒弃了中间组件的方式,就直接使用淘宝官方提供的taobao-sdk-net.zip(http://dl.open.taobao.com/sdk/taobao-sdk-net.zip) 来进行开发,发现其实模式一样,操作也差不多而已,但是处理起来的细节有很多不同的地方。

为了进行淘宝的API开发,首先我们需要做下面几件事情。

1)

开发者注册一个账号,

2)然后为每个淘宝应用注册一个应用程序键(App Key)

3)下载淘宝API的SDK并掌握基本的API基础知识和调用,具体可以参考论坛的信息(
http://dev.open.taobao.com/bbs/forum.php
)里面的技术分享区等。

4)利用SDK接口和对象,传入AppKey或者必要的时候获取并传入SessionKey来进行程序开发。

5)利用淘宝平台的文档中心和API测试工具,对接口进行测试。从而了解返回信息,方便程序获取。

以上是一般淘宝API应用开发的关键流程,其中通过淘宝文档中心和API测试工具,进行概念了解和接口信息查询很重要。因为这些数据就是我们需要在逐步显示出来的。淘宝的API很多,不过都分为了好几个领域,慢慢就会了解深入了。

然后我们也可以通过淘宝平台的API测试工具来了解返回的数据信息,每个淘宝API最下面一般都有一个API工具的内容,如下所示。其中我们选择【API Tools在线测试工具】就可以测试返回的接口数据了。

对于以上这些淘宝API接口,不需要Session就可以访问的,就直接利用【系统分配】的内容就可以访问并获取到数据了。注意其中红色圈中部分即可。

对于有一些用户方面的私密数据,如交易数据,买入或者卖出的数据,我们就要自定义指定自己的SessionKey了,如下所示。

卖出的信息,也需要传入SessionKey的信息,在程序中处理,得到下面的结果。