黎波

Windows Mobile Development for Line of Business
posts - 182, comments - 1175, trackbacks - 43, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

系列文章导航:
如何将数据导入到 SQL Server Compact Edition 数据库中(一)
如何将数据导入到 SQL Server Compact Edition 数据库中(二)

摘要:时隔近半年了,不知道大家是否还记得,我在本系列的第一篇文章的总结中提到,创建 SQL Server CE 数据库表结构的 SQL 语句是可以自动生成的。那么本系列的第三篇文章就向大家介绍一种比较简单的方法。

ADO.NET 中的 IDataReader.GetSchemaTable 方法可以返回一个 DataTable,它描述了 IDataReader 查询结果中各列的元数据。列的元数据包含了列的名称、数据类型、大小、是否为主键字段、是否为自动增长字段……等等。有了这些元数据,我们就可以通过编写几段 C#/VB.NET 代码,实现创建 SQL Server CE 数据库表结构的 SQL 语句的自动生成。以下方法是生成创建表 SQL 语句的主要代码:

/// <summary>
/// 生成创建数据库表结构的 SQL 语句。
/// </summary>
private static string GenerateTableSchemaSql(IDbConnection connection, string queryString)
{
    StringBuilder tableSql 
= new StringBuilder(); 

    IDbCommand command 
= connection.CreateCommand();
    command.CommandText 
= queryString; 

    
try
    {
        
/* 获取查询结果各列的元数据 */
        DataTable schemaTable 
= null;
        
using (IDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo))
        {
            schemaTable 
= reader.GetSchemaTable();
        } 

        
/* 生成创建表定义语句 */
        
string tableName = schemaTable.Rows[0]["BaseTableName"].ToString();
        tableSql.Append(
"CREATE TABLE [").Append(tableName).AppendLine("] ("); 

        
/* 生成各列的定义语句 */
        
string columnName;
        
string allowDBNull;
        DataRow row;
        
bool hasKey = false;
        StringBuilder sbPKFields 
= new StringBuilder();
        
for (int i = 0; i < schemaTable.Rows.Count; i++)
        {
            
if (i != 0) tableSql.AppendLine(","); 

            row 
= schemaTable.Rows[i];
            columnName 
= (string)row["ColumnName"];
            allowDBNull 
= ((bool)row["AllowDBNull"== true ? "NULL" : "NOT NULL"); 

            
if ((bool)row["IsKey"])
            {
                sbPKFields.AppendFormat(
"[{0}],", columnName);
                hasKey 
= true;
            }
            tableSql.AppendFormat(
"  [{0}] {1} {2}", columnName, GetSqlCeDataType(row), allowDBNull);
        } 

        
/* 生成主键约束语句 */
        
if (hasKey)
        {
            
string pkFields = sbPKFields.ToString().TrimEnd(',');
            tableSql.AppendLine(
",");
            tableSql.Append(
"  CONSTRAINT PK_").Append(tableName.Replace(" ", "_")).Append(" PRIMARY KEY(").Append(pkFields).AppendLine(")");
        }
        tableSql.AppendLine(
");");
    }
    
catch (Exception ex)
    {
        Debug.WriteLine(ex);
    } 

    
return tableSql.ToString();
}

同样的,该方法也使用了 ADO.NET 的接口类,不依赖于具体的数据库类型。该方法的核心就是通过 IDataReader.GetSchemaTable 方法获取查询结果各列元数据,相关代码如下:

IDbCommand command = connection.CreateCommand();
command.CommandText 
= queryString;
DataTable schemaTable 
= null;
using (IDataReader reader = command.ExecuteReader(CommandBehavior.KeyInfo))
{
    schemaTable 
= reader.GetSchemaTable();
}

首先,IDbCommand 的 CommandText 属性一般是针对一个表的 SELECT 查询语句,如:SELECT * FROM Customers。其次,IDbCommand.ExecuteReader 方法必须传入 CommandBehavior.KeyInfo 参数,这样才能获取到列的主键元数据。最后,通过 IDataReader.GetSchemaTable 方法返回一个包含查询结果所有列的元数据的 DataTable。关于 IDataReader.GetSchemaTable 方法的详细使用说明,请阅读《HOW TO:使用 DataReader GetSchemaTable 方法和 Visual C# .NET 检索列架构》

IDataReader.GetSchemaTable 返回的 SchemaTable 对列数据类型的描述是用相应的 .NET 数据类型,如 SQL Server CE 的 int 类型对应的是 .NET 的 System.Int32 类型。另外需要注意的是,由于 Windows Mobile 只支持 Unicode 编码,因此 SQL Server CE 只支持 NChar, NVarChar 和 NText 等 Unicode 字符数据类型,而不支持 Char, VarChar 和 Text 等非 Unicode 字符数据类型。所以,我们需要编写一个方法,它根据列的 .NET 数据类型找到对应的 SQL Server CE 数据类型。这个方法的代码如下所示:

/// <summary>
/// 从 .NET 数据类型获取对应的 SQL Server CE  类型名称。
/// </summary>
private static string GetSqlCeNativeType(Type systemType)
{
    
string typeName = systemType.ToString();
    
switch (typeName)
    {
        
case "System.Boolean":
            
return "bit";
        
case "System.Byte":
            
return "tinyint";
        
case "System.Byte[]":
            
return "image";
        
case "System.DateTime":
            
return "datetime";
        
case "System.Decimal":
            
return "numeric";
        
case "System.Double":
            
return "float";
        
case "System.Guid":
            
return "uniqueidentifier";
        
case "System.Int16":
            
return "smallint";
        
case "System.Int32":
            
return "integer";
        
case "System.Int64":
            
return "bigint";
        
case "System.Single":
            
return "real";
        
case "System.String":
            
return "nvarchar";
        
default:
            
throw new ApplicationException(string.Format("找不到 {0} 类型对应的 SQL Server CE 数据类型。", typeName));
    }
}

当然,仅仅知道列的数据类型还不够,我们需要为某些列的数据类型加上长度、精度或小数位数等列大小信息。可以通过下面的方法实现:

/// <summary>
/// 从 ColumnSchemaRow 获取 SQL Server CE 数据类型。
/// </summary>
private static string GetSqlCeDataType(DataRow columnSchemaRow)
{
    Type type 
= columnSchemaRow["DataType"as Type;
    
string dataType = GetSqlCeNativeType(type);
    
switch (dataType)
    {
        
case "numeric":
            Int16 precision 
= (Int16)columnSchemaRow["NumericPrecision"];
            Int16 scale 
= (Int16)columnSchemaRow["NumericScale"];
            Int32 colsize 
= (Int32)columnSchemaRow["ColumnSize"];
            
if (precision != 0 && scale != 0 && scale != 255)
            {
                dataType 
= string.Format("{0}({1},{2})", dataType, precision, scale);
            }
            
else if (scale == 255 && colsize == 8)
            {
                dataType 
= "money";
            }
            
break;
        
case "nvarchar":
            
int columnSize = (int)columnSchemaRow["ColumnSize"];
            
if (columnSize > 4000)
            {
                dataType 
= "ntext";
            }
            
else
            {
                dataType 
= string.Format("{0}({1})", dataType, columnSize);
            }
            
break;
    }
    
return dataType;
}

关于 SQL Server 2005 Compact Edition 数据类型的描述,详细请参考联机丛书。使用上面的几段代码,对 SQL Server 2000 自带的 Northwind 数据库的 Customers 表生成创建数据库表的 SQL 语句,生成结果如下:

CREATE TABLE [Customers] (
  
[CustomerID] nvarchar(5NOT NULL,
  
[CompanyName] nvarchar(40NOT NULL,
  
[ContactName] nvarchar(30NULL,
  
[ContactTitle] nvarchar(30NULL,
  
[Address] nvarchar(60NULL,
  
[City] nvarchar(15NULL,
  
[Region] nvarchar(15NULL,
  
[PostalCode] nvarchar(10NULL,
  
[Country] nvarchar(15NULL,
  
[Phone] nvarchar(24NULL,
  
[Fax] nvarchar(24NULL,
  
CONSTRAINT PK_Customers PRIMARY KEY([CustomerID])
);

对于 SQL Server 2000,我们可以从信息架构视图查询 INFORMATION_SCHEMA.TABLES 出数据库有哪些表,并一次性对所有表进行生成。以下是 INFORMATION_SCHEMA.TABLES 视图各列的说明:
列名 数据类型 说明
TABLE_CATALOG nvarchar(128) 表限定符。
TABLE_SCHEMA nvarchar(128) 包含该表的架构的名称。
TABLE_NAME sysname 表名。
TABLE_TYPE varchar(10) 表的类型。可以是 VIEW 或 BASE TABLE。

我们可以通过以下方法获得 Northwind 数据库所有用户表名的数组:

/// <summary>
/// 从一个打开的 SQL Server 数据库连接获取数据库的表名数组。
/// </summary>
private static string[] GetTableNames(IDbConnection connection)
{
    IDbCommand command 
= connection.CreateCommand(); 

    
// 从 SQL Server 信息架构视图获取 Northwind 数据库所有表的名称
    command.CommandText = @"SELECT * FROM INFORMATION_SCHEMA.TABLES 
    WHERE TABLE_TYPE='BASE TABLE' AND TABLE_CATALOG='Northwind'
"

    List
<string> tableNames = new List<string>();
    
using (IDataReader reader = command.ExecuteReader())
    {
        
while (reader.Read())
        {
            tableNames.Add(reader[
"TABLE_NAME"].ToString());
        }
    }
    
return tableNames.ToArray();
}

有了 GetTableNames 方法,我们就可以一次性对 Northwind 数据库的所有用户表生成相应的创建 SQL Server CE 数据库表结构的 SQL 语句。

static void Main(string[] args)
{
    
string connectionString = "Data Source=(local);Initial Catalog=Northwind;Integrated Security=True";
    IDbConnection connection 
= new SqlConnection(connectionString);
    connection.Open(); 

    
string[] tableNames = GetTableNames(connection); 

    
string queryString, createTableSql;
    
foreach (string tableName in tableNames)
    {
        queryString 
= string.Format("select * from [{0}]", tableName);
        createTableSql 
= GenerateTableSchemaSql(connection, queryString); 

        Console.WriteLine(createTableSql);
        Debug.WriteLine(createTableSql);
    } 

    connection.Close();
    Console.Read();
}

示例程序运行效果如下图所示:


总结:阅读完本文,相信你已经了解了如何利用 ADO.NET 的 IDataReader.GetSchemaTable 方法获得服务器端数据库表的元数据,并用于生成对应的创建 SQL Server CE 数据库表的 SQL 语句。本系列文章可能还会有更精彩的续篇,我会将平时积累的关于 SQL Server CE 数据导入的一些经验充实到本系列中。

示例代码下载:sqlce_data_import3.rar

更新记录:
2008-2-9 修正对money数据类型的支持,修正对包含空格的表名的支持。

作者:黎波
博客:http://upto.cnblogs.com/
日期:2008年1月31日

Feedback

#1楼    回复  引用    

2008-01-31 13:34 by A.Z! [未注册用户]
ms sync fw

#2楼    回复  引用    

2008-01-31 16:32 by 台州啦 [未注册用户]
非常好 我正好用得着

#3楼    回复  引用  查看    

2008-02-04 00:54 by fox23      
恩,支持一下,尽管现在我已经大部分工作已经脱离数据库了:-)

#4楼    回复  引用  查看    

2008-03-13 11:27 by 大双      
发现一个问题..
当我在oracle中做的时候,我不知道怎么获得信息架构表中所有的表名..
还望指点...
oracle中有像sql server中的INFORMATION_SCHEMA.TABLES 吗??
有点急...

#5楼 [楼主]   回复  引用  查看    

2008-03-13 21:07 by 黎波      
@大双
SELECT owner, object_name, created
FROM all_objects
WHERE (owner in (select USERNAME from user_users)) AND (object_type = 'TABLE')
ORDER BY owner, object_name

#6楼    回复  引用  查看    

2008-03-15 14:42 by 大双      
--引用--------------------------------------------------
黎波: @大双
SELECT owner, object_name, created
FROM all_objects
WHERE (owner in (select USERNAME from user_users)) AND (object_type = 'TABLE')
ORDER BY owner, object_name
--------------------------------------------------------
发现读出来的list里面的tablename是空的..
但是我确定数据库中是有表存在的..
不知道是什么原因了...

#7楼 [楼主]   回复  引用  查看    

2008-03-15 14:47 by 黎波      
@大双
我手头没有Oracle数据库可供测试,我想你可以问问熟悉Oracle的朋友吧。

#8楼    回复  引用  查看    

2008-03-16 12:36 by 大双      
// 通过 DataReader 获取 SchemaTable 信息
srcReader = srcCommand.ExecuteReader(CommandBehavior.KeyInfo);

我现在可以 获取到oracle中的表,也可以写到list中...
只是在创建sqlce的表的时候,这句报错...
提示表名无效...
跟踪发现dest的table都是正确的..
这个是什么原因呢??

#9楼 [楼主]   回复  引用  查看    

2008-03-16 15:19 by 黎波      
@大双
你把生成的表创建脚本贴出来吧,是不是有空格或特殊字符?

#10楼    回复  引用  查看    

2008-03-16 15:27 by 大双      
create table test1
(
L0 varchar2(16),
L1 number(8,2),
L2 varchar2(16),
L3 varchar2(32)
);
create table test2
(
L0 varchar2(16),
L1 date,
L2 varchar2(16)
);

我觉得不需要写这些脚本的吧...
我是按照第四篇文章中的方法做的..
直接是从oracle中获取这些信息然后创建sqlce中的表结构
oracle中已经存在表数据

我也是怕有空格等东西,全部删除了的..
因为是做测试,所以表结构创建的很简单,插入的测试数据也不多..

#11楼    回复  引用  查看    

2008-03-16 15:49 by 大双      
详细的错误信息如下:
在 System.Data.OracleClient.OracleConnection.CheckError(OciErrorHandle errorHandle, Int32 rc)
在 System.Data.OracleClient.OracleCommand.Execute(OciStatementHandle statementHandle, CommandBehavior behavior, Boolean needRowid, OciRowidDescriptor& rowidDescriptor, ArrayList& resultParameterOrdinals)
在 System.Data.OracleClient.OracleCommand.Execute(OciStatementHandle statementHandle, CommandBehavior behavior, ArrayList& resultParameterOrdinals)
在 System.Data.OracleClient.OracleCommand.ExecuteReader(CommandBehavior behavior)
在 System.Data.OracleClient.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
在 System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
在 CopyTableDemo.Form1.CopyTable(IDbConnection srcConnection, SqlCeConnection destConnection, String queryString, String destTableName) 位置 E:\sql_sqlce

#12楼    回复  引用    

2008-03-17 16:16 by suyuanxin [未注册用户]
黎波,你好,像你请教一个问题
我是和oracle相连,通过webservers将查询出来的数据集放在DataSet里,然后再插入sqlce里,目前出现问题是:插入速度太慢需要进行优化,代码如果下:
sql = "select * from XXtable";
ds=new DataSet();
//通过调用Web Service 得到数据集
ds = gt.GetDataSetBySql("strConnection_USER",sql,ConfigurationSettings.AppSettings.Get("device_id"),code_fm_main.login,code_fm_main.user,code_fm_main.pas);

if(ds.Tables[0].Rows.Count > 0)
{
for(int i=0;i<ds.Tables[0].Rows.Count;i++)
{
cmd.CommandText = "INSERT INTO PDA_STAND_CONTENT (CATEGORY_ID ,CONTENT) VALUES (?,?)";

cmd.Parameters.Add(new SqlCeParameter("p1", SqlDbType.Int));
cmd.Parameters.Add(new SqlCeParameter("p2", SqlDbType.NText));
cmd.Parameters["p2"].Size = ds.Tables[0].Rows[i]["CONTENT"].ToString().Length;
cmd.Prepare();
cmd.Parameters["p1"].Value = ds.Tables[0].Rows[i]["CATEGORY_ID"].ToString().Trim();
if(ds.Tables[0].Rows[i]["CONTENT"].ToString().Length > 0)
cmd.Parameters["p2"].Value = ds.Tables[0].Rows[i]["CONTENT"].ToString().Trim();
else
cmd.Parameters["p2"].Value = DBNull.Value;

cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
}
不知道有没有其他更快的插入方法,方便下载数据,目前数据量一大,下载就需要1个多小时,表有七八张,每张有十几个字段。


#13楼    回复  引用    

2008-04-11 11:29 by newer [未注册用户]
我用sql脚本文件建了个库,执行插入的记录的语句,在management studio看记录都在了,当我用mobile连接到sdf数据库做应用的时候,一个记录都没有.
请问这是什么问题?难道记录没保存到*.sdf文件中?

#14楼 [楼主]   回复  引用  查看    

2008-04-12 10:32 by 黎波      
@newer
首先确保您的Management Studio和Mobile打开的sdf数据库是不是一致,你可以用SQL Server Compact Query Analyzer 3.5在Mobile上打开数据库。不可能有这么玄的情况出现的。

#15楼    回复  引用    

2008-07-08 11:48 by allen.feng [未注册用户]
黎老师,学生急求....

我连接RF服务器,在将它与IIS进行同步的时候一直出错,不能连接,老师帮忙想个办法啊

我的连接代码如下:
class AppConstant
{
private AppConstant()
{
}
public const string sqlAgent = "http://rb-it-allen/RainBow/sqlcesa30.dll";
public const string rdaOleDbConnstr = "Provider=sqloledb;Persist Security Info=False;;Data Source=RB-IT-AIIEN;Initial Catalog=Northwind;" +
"User Id=sa";

public const string Publisher = "RB-IT-AIIEN";
public const string PublisherDatabase = "Northwind";
public const string PublisherLogin = "sa";
public const string PublisherPassword = "";
public const string Publication = "RainBow";

public const string Subscriber = "BaseData";
public const string localDBPhysicalFile = "\\Program Files\\RBInformsRF\\RainBowCEDB.sdf";
public const string localConnection = @"Data Source= \Program Files\RBInformsRF\RainBowCEDB.sdf";
public const Boolean translateFlag = true;
}

---------------------------------------------
public void BaseDataSynchronize()
{
try
{
// 实例化并配置 SqlCeReplication 对象
SqlCeReplication repl = new SqlCeReplication();
repl.InternetUrl = AppConstant.sqlAgent;
repl.Publisher = AppConstant.Publisher;
repl.PublisherSecurityMode = SecurityType.DBAuthentication;
repl.PublisherDatabase = AppConstant.PublisherDatabase;
repl.PublisherLogin = AppConstant.PublisherLogin;
repl.PublisherPassword = AppConstant.PublisherPassword;
repl.Publication = AppConstant.Publication;
repl.Subscriber = AppConstant.Subscriber;
repl.SubscriberConnectionString = AppConstant.localConnection;

repl.AddSubscription(AddOption.ExistingDatabase);
// 跟 SQL Server 数据库进行同步
repl.Synchronize();

// 清理 repl 对象
repl.Dispose();
}
catch (SqlCeException ex)
{
MessageBox.Show(ex.ToString());
}/**/
}

在执行repl.Synchronize();的时候就出错,"将数据发送到运行IIS的计算机的请求失败,有关详细信息,请参阅HRESULT"..

#16楼 [楼主]   回复  引用  查看    

2008-07-08 13:00 by 黎波      
@allen.feng
一般这种问题是权限引起的,合并复制的配置很麻烦,你可以参考微软这个LOB实例里面关于配置合并复制的文档。
http://www.cnblogs.com/upto/archive/2006/08/18/MobileLineofBusinessSolutionAccelerator.html

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-02-09 22:46 编辑过