65.9K
CodeProject 正在变化。 阅读更多。
Home

Tripous - 数据访问

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (9投票s)

2010年6月6日

CPOL

9分钟阅读

viewsIcon

27034

downloadIcon

486

关于使用Tripous数据访问类的教程

引言

Tripous是一个开源应用程序框架,用C#编写,用于快速实现WinForms数据录入应用程序。

官方网站位于 http://tripous-net.com
SourceForge上的项目页面位于 http://sourceforge.net/projects/tripous。 

Tripous数据访问层提供统一的数据访问,即与数据库服务器无关的SQL语句,以及最重要的,与服务器无关的SQL参数。

人们可能会期望像.NET这样的系统在访问不同数据库数据源时提供一定程度的统一性。不幸的是,事实并非如此。.NET在这方面唯一提供的是“数据提供程序”的概念。最初,数据提供程序只是一组接口,如IDbConnectionIDbCommand等。为了“实现”这样的数据提供程序,您必须从头开始编写自己的类来实现所有这些接口。没有提供通用基类。这不是一项容易的任务。那是一个英雄的时代。

正如您可能知道的,执行SQL语句的最佳方法之一是使用参数化SQL语句。Tripous广泛使用参数化SQL语句。好吧,ADO.NET尽最大努力使编写服务器无关的参数化语句变得非常困难。在IDbDataParameter接口和(.NET 2)DbParameter类中有一个DbType属性,类型为System.Data.DbType,但是任何数据提供程序都有自己的一组数据类型。在很多情况下,很难说哪个对应哪个。更糟的是,任何数据提供程序都在语句文本中使用自己的符号作为参数的前缀。一个数据提供程序使用@后跟参数名,而另一个则使用:后跟参数名。然后还有另一个数据提供程序根本不使用命名参数。它只需要在参数位置使用?。看看吧。 

select * from Customer where Id = @Id
select * from Customer where Id = :Id
select * from Customer where Id = ?

那么,如何在该系统中编写服务器无关的参数化语句? 

.NET 2.0之后,数据提供程序类基于通用基类DbConnectionDbCommand等。迟早会实现的。此外,.NET 2.0还为我们带来了DbProviderFactoriesDbProviderFactory类。DbProviderFactories只是一个工厂,用于基于所谓的不变名称创建DbProviderFactory实例。一个string,在大多数情况下,它是特定DbProviderFactory类的完整名称,包括命名空间。

现在,“公开DbProviderFactory的数据提供程序在machine.config文件中注册配置信息和提供程序字符串”。这并非我所设想的。它听起来太“static”了。当然,您无法即时注册自己的DbProviderFactory

无论如何,在获取DbProviderFactory实例后,您将它用于创建DbConnectionDbCommand实例等。因此,您可以编写服务器无关的SQL,前提是您不使用参数。

以上是Tripous必须处理的第一组问题。

Tripous数据提供程序 

Tripous.Data命名空间包含所有与数据访问层相关的类。

Tripous.Data.DataProviders类实际上是Tripous.Data.DataProvider实例的注册表。

public static class DataProviders
{
    public static int Count { get; }
    public static char GlobalPrefix { get; set; }

    public static void Add(DataProvider Provider);
    public static DataProvider ByIndex(int Index);
    public static DataProvider ByName(string Alias);
    public static bool CanCreateDatabases(string Alias);
    public static bool Contains(string Alias);
    public static void Error(string Text);
    public static void Error(string Text, params object[] Args);
    public static DataProvider Find(string Alias);
    public static DataProvider[] GetCreatableProviders();
}

GlobalPrefix属性是您设置在编写参数化SQL语句时用作参数前缀的字符的地方。这是Tripous.Data.DataProvider类。

public abstract class DataProvider
{
    public DataProvider();

    public string Alias { get; }
    public virtual bool CanCreateDatabases { get; }
    public string Description { get; }
    public MidwareType MidwareType { get; }
    public char NativePrefix { get; }
    public OidMode OidMode { get; }
    public PrefixMode PrefixMode { get; }
    public bool RequiresNativeParamNames { get; }
    public ServerType ServerType { get; }
    public bool SupportsGenerators { get; }
    public bool SupportsTransactions { get; }

    public virtual void AssignParameter(IDataParameter Parameter, object Value);
    public virtual void AssignParameter
	(DataColumn Column, IDataParameter Parameter, object Value);
    public virtual void AssignParameters(DbCommand Command, params object[] Params);
    public virtual void AssignParameters
	(IDataParameterCollection Parameters, params object[] Params);
        
    public abstract DbDataAdapter CreateAdapter();
    public DbCommand CreateCommand
	(DbConnection Connection, string SqlText, params object[] Params);
    public virtual DbCommandBuilder CreateCommandBuilder();
    public abstract DbConnection CreateConnection(string ConnectionString);
    public virtual DbParameter CreateParameter();
    public virtual DbConnectionStringBuilder CreateConnectionStringBuilder();
    public virtual ConnectionStringBuilder CreateConnectionStringBuilder
	(string ConnectionString);
        
    public virtual bool CreateDatabase(string ConnectionString);
    public virtual bool CreateDatabase
	(string ServerName, string DatabaseName, string UserName, string Password);
        
    public DataTable Select(DbCommand Command);
    public DataTable Select(string ConnectionString, string SqlText);
    public DataTable Select
	(string ConnectionString, string SqlText, params object[] Params);
                
    public void Exec(string ConnectionString, string SqlText);
    public void Exec(string ConnectionString, string SqlText, params object[] Params);
        
    public virtual string FormatConnectionString(string FileOrDatabaseName);
    public virtual string FormatConnectionString
	(string HostComputer, string FileOrDatabaseName);
    public virtual string NormalizeAlterTableColumnSql(string SqlText);
        
    public void PrefixRemove(DbCommand Command);
    public virtual string PrefixRemove(string ParameterName);
    public void PrefixToGlobal(DbCommand Command);
    public virtual string PrefixToGlobal(string ParameterName);
    public void PrefixToNative(DbCommand Command);
    public virtual string PrefixToNative(string ParameterName);
        
    public virtual string ReplaceDataTypePlaceholders(string SqlText);

    public void SetupCommand(DbCommand Command, string SqlText, params object[] Params);
    public virtual DbType TypeToDbType(Type Source);
}

Tripous中已经有许多DataProvider的派生类(MsSqlProviderFirebirdProvider等),并且DataProviders类的static构造函数会注册所有这些类。您可以创建自己的DataProvider类并以相同的方式注册它们。请将您的DataProvider类发送给我,我将将其添加到Tripous官方源代码中,以便其他人可以受益于您的工作。

DataProvider类主要用于创建DbConnectionDbCommand实例等。

在Tripous中,DataProvider类由其Alias(一个string)唯一标识。Tripous.Data.ConnectionStringBuilder辅助类为所有提供程序别名提供了public常量。一些DataProviders知道如何创建一个新数据库。所有DataProviders都知道如何“格式化”连接字符串。

Tripous连接字符串与ADO.NET连接字符串相同,再加上一个Alias条目。这是MsSql的Tripous连接字符串。

Alias=MsSql; Data Source=localhost; Integrated Security=SSPI; Initial Catalog=DevApp

ConnectionStringBuilder为最常见的情况提供了预定义的连接字符串格式的public常量。这是MsSql的连接字符串。

Alias=MsSql; Data Source={0}; Integrated Security=SSPI; Initial Catalog={1};

可以使用DataProviderConnectionStringBuilder类的FormatConnectionString()方法轻松创建这些Tripous连接字符串。

DataProvider Provider = DataProviders.Find(Alias);
if (Provider != null)
{
    string ConnectionString = Provider.FormatConnectionString
				(HostComputer, FileOrDatabaseName);
    MessageBox.Show(ConnectionString);
}

FormatConnectionString.jpg

如果DataProvider支持该操作,则可以使用DataProvider.CreateDatabase()创建新数据库。在这种情况下,CanCreateDatabases返回true

DataProvider Provider = DataProviders.Find(Alias);
if (Provider != null)
{
    Provider.CreateDatabase(ServerName, DatabaseName, UserName, Password);
    MessageBox.Show("DONE");
}

TripousDataProvider类有许多有用的只读属性和一些辅助方法。

警告:如果您要使用Firebird,则必须先安装它,然后将FirebirdSql.Data.FirebirdClient.dll从Tripous的ThirdParty文件夹复制到您的bin文件夹。

创建数据库模式

在Tripous的说法中,datastore一词表示一组信息,该信息在唯一名称下描述数据库连接和属性。还有一个Datastore类以及一个static Datastores类,该类是所有已注册Datastore项的集合。

Tripous提供了一套类,用于简化数据库模式的创建和更新。SchemaDatastoresSchemaDatastoreSchemaTable是最重要的。

SchemaDatastore实例代表特定数据存储的特定版本模式。您使用数据存储名称(例如“MAIN”)和版本号。SchemaDatastoresSchemaDatastore项的集合,并提供了一个Execute()方法来将这些更改应用于一个或多个数据库。

对于在SchemaDatastores中以特定名称注册的每个SchemaDatastore项,必须有一个在Datastores下以相同名称注册的Datastore。否则,SchemaDatastores.Execute()将失败。

成功调用SchemaDatastores.Execute()会创建已注册的模式以及一个“ini”表,即SYS_INI,该表类似于Microsoft Windows的*.ini文件。Datastore类提供了一个类型为DbIniIni属性。

DbIni类提供了一些方便的方法,例如以下方法

public class DbIni
{
    ...
    
    public void WriteString(string Entry, string Value);
    public void WriteInteger(string Entry, int Value);
    public void WriteFloat(string Entry, double Value);
    public void WriteDateTime(string Entry, DateTime Value);
    public void WriteBool(string Entry, bool Value);
    
    public string ReadString(string Entry, string Default);
    public int ReadInteger(string Entry, int Default);
    public double ReadFloat(string Entry, double Default);
    public DateTime ReadDateTime(string Entry, DateTime Default);
    public bool ReadBool(string Entry, bool Default);
    
    ...
    ...
}

SchemaDatastores.Execute()执行后,该ini表中会有一个关于刚刚创建的模式及其版本的条目。

让我们回到数据库创建。

为了在数据库中创建数据库表,您首先创建一个SchemaDatastores实例。然后,您在特定名称和版本下Add()一个SchemaDatastore。除了Add()之外,SchemaDatastores还提供了ForceFind()方法,该方法返回具有指定Name且版本等于指定Version的第一个SchemaDatastore(如果存在)。如果不存在这样的SchemaDatastore,则会创建一个新的并返回。

这是一个创建新的空Sqlite数据库、注册Datastore、创建SchemaDatastoresSchemaDatastore、添加SchemaTable并在数据库中创建模式的方法。

        void CreateDatabase()
        {          
            
            string DatabaseName = Path.GetFullPath(@"..\..\bin\Debug\Test.db3");
            if (!File.Exists(DatabaseName))
            {
                string Alias = ConnectionStringBuilder.Sqlite;
                DataProvider Provider = DataProviders.Find(Alias);

                /* create the database */
                Provider.CreateDatabase(string.Empty, DatabaseName, 
			string.Empty, string.Empty);

                /* add a Datastore */
                string ConnectionString = Provider.FormatConnectionString(DatabaseName);
                Datastores.Add(Sys.MAIN, Alias, ConnectionString);

                /* schema */
                int Version = 1;
                SchemaDatastores SchemaDatastores = new SchemaDatastores();
                SchemaDatastore Schema = SchemaDatastores.Add(Sys.MAIN, Version);

                string TableName = "Person";
                string SqlText = @"
create table Person (
   Id                        @PRIMARY_KEY
  ,Name          varchar(32) @NOT_NULL
  ,Birthday      @DATE       @NULL
  ,Salary        double      @NULL
  ,Rank          integer     @NULL
  ,Notes         @BLOB_TEXT  @NULL
);
";
                Schema.AddTable(TableName, SqlText);

                /* create the table */
                SchemaDatastores.Execute();
            }
        }

正如您所见,创建表的脚本包含一些占位符。这些词以at(@)字符开头。Tripous致力于实现服务器无关性。这些占位符就是为此目的而存在的。DataProvider.ReplaceDataTypePlaceholders()方法根据实际使用的服务器替换这些占位符。

请随意深入研究这些类,因为还有很多东西需要发现。希望您能找到任何错误并发送一些代码给我纠正。 

数据存储、数据存储和执行程序 

Datastores类是一个static类,它充当Datastore实例的注册表。

public static class Datastores
{
    public static int Count { get; }
    public static Datastore Main { get; }
    
    public static void Add(Datastore Datastore);
    public static Datastore Add
	(string Name, string ProviderAlias, string ConnectionString);
    public static Datastore ByIndex(int Index);
    public static Datastore ByName(string Name);
    public static bool CanCreateDatabase(Datastore Datastore);
    public static bool CanCreateDatabase(string ConnectionString);
    public static void Clear();
    public static bool Contains(string Name);
    public static bool CreateDatabase(Datastore Datastore);
    public static bool DatabaseExists(Datastore Datastore);
    public static Datastore Find(string Name);
    public static void LoadFrom(DatastoreDescriptors Descriptors);
    public static string NextDatastoreName(string Prefix);
    public static void Remove(Datastore Datastore);
    public static void Remove(string Name);
    public static Datastore[] ToArray();
}

最重要的red-only方法是Add()方法。

Datastore类是一个abstract类。有两个派生类:AdoNetDatastoreClientDatastore类。

现在先忽略ClientDatastore类。只需说它使用XML TCP命令通过套接字连接将请求发送到AdoNetDatastore。简而言之,它充当服务器端AdoNetDatastore的客户端代理。您可以从Pda(Compact Framework)应用程序使用ClientDatastore来访问远程数据库,就像访问桌面系统中的数据库一样。

警告Datastores.Add(string Name, string ProviderAlias, string ConnectionString)仅创建AdoNetDatastore实例。

这是Datastore

public abstract class Datastore
{
    public abstract bool ConnectionChecked { get; }
    public virtual DbIni Ini { get; }
    public abstract MidwareType MidwareType { get; }
    public abstract string Name { get; }
    public abstract OidMode OidMode { get; }
    public Dictionary<string, DataTable> SchemaTables { get; }
    public abstract ServerType ServerType { get; }
    public abstract bool SupportsGenerators { get; }
    public abstract bool SupportsTransactions { get; }
    public object SyncRoot { get; }
    public virtual int Version { get; }
    
    public abstract Executor CreateExecutor();
    public abstract void EnsureConnection();
    public void GeneratorAdd(string GenaratorName);
    public virtual bool GeneratorExists(string GenaratorName);
    public abstract DataTable GetSchema();
    public abstract DataTable GetSchema(string collectionName);
    public abstract DataTable GetSchema
	(string collectionName, string[] restrictionValues);
    public virtual string QSDate(DateTime Value);
    public override string ToString();
}

如您所见,它是一个简陋的类。只是一组只读信息属性和几个方法。如前所述,Datastore类代表一个在唯一名称下的数据库连接,该名称在其他已注册的Datastore项中是唯一的,以及一些特征。最重要的方法是CreateExecutor(),它创建并返回一个Executor实例。Executor类是这个级别上最重要的类。

public abstract class Executor
{
    public Executor(Datastore Datastore);
    
    public Datastore Datastore { get; }
    public virtual MidwareType MidwareType { get; }
    public virtual OidMode OidMode { get; }
    public virtual ServerType ServerType { get; }
    public virtual bool SupportsGenerators { get; }
    public virtual bool SupportsTransactions { get; }
    
    public abstract DataTransaction BeginTransaction();
    public abstract Executor CreateExecutor();
    
    public void CreateGenerator(string GeneratorName);
    public abstract bool CreateTable(string SqlText);
    
    public DataTable Select(string SqlText);
    public virtual DataTable Select(string SqlText, params object[] Params);
    public DataTable Select(DataTransaction Transaction, string SqlText);
    public abstract DataTable Select
	(DataTransaction Transaction, string SqlText, params object[] Params);
    
    public int SelectTo(DataTable Table, string SqlText);
    public int SelectTo(DataTable Table, string SqlText, params object[] Params);
    public int SelectTo(DataTransaction Transaction, DataTable Table, string SqlText);
    public int SelectTo(DataTransaction Transaction, 
	DataTable Table, string SqlText, params object[] Params);
            
    public void Exec(string SqlText);
    public virtual void Exec(string SqlText, params object[] Params);
    
    public void Execute(DataTransaction Transaction, string SqlText);
    public abstract void Execute(DataTransaction Transaction, 
	string SqlText, params object[] Params);
    
    public void ExecuteNested(DataTransaction ParentTransaction, string SqlText);
    public void ExecuteNested(DataTransaction ParentTransaction, 
	string SqlText, params object[] Params);
    
    public DataRow SelectResults(string SqlText);
    public DataRow SelectResults(DataTransaction Transaction, string SqlText);
    public DataRow SelectResults(string SqlText, params object[] Params);
    public DataRow SelectResults(DataTransaction Transaction, 
	string SqlText, params object[] Params);
    
    public object SelectResult(string SqlText, object Default);
    public object SelectResult(DataTransaction Transaction, 
	string SqlText, object Default);
    
    public int IntegerResult(string SqlText, int Default);
    public int IntegerResult(DataTransaction Transaction, string SqlText, int Default);
    
    public bool TableExists(string TableName);
    public bool TableIsEmpty(string TableName);
    public bool FieldExists(string TableName, string FieldName);
    public bool IndexExists(string IndexName);
    public bool GeneratorExists(string GeneratorName);
    
    public abstract IList<string> GetFieldNames(string TableName);
    public abstract IList<string> GetIndexNames();        
    public abstract IList<string> GetTableNames();
    
    public void GetNativeSchema(string SqlText, string TableName, 
	string SchemaName, DataTable Table);
    
    public int LastId(string TableName);
    public int LastId(DataTransaction Transaction, string TableName);
    public int NextId(string TableName);
    public int NextId(DataTransaction Transaction, string TableName);
    public int NextIdByGenerator(string GeneratorName);
    public int NextIdByGenerator(DataTransaction Transaction, string GeneratorName);
    
    public void ResetTable(string TableName);
    public void ResolveSql(ref string SqlText);
    
    public void SetGeneratorTo(string GeneratorName, int Value);
    public void SetTableGeneratorTo(string TableName, int Value);        
}

在Tripous中,应用程序程序员从不直接使用DbConnectionDbCommand等类。它使用DatastoreExecutor

Datastore类一样,有一个AdoNetExecutor和一个ClientExecutor类。

您从不直接创建Executor。您始终使用Datastore.CreateExecutor()调用。

Executor类可以使用各种SelectXXX()方法执行SELECT语句,以及使用Exec()Execute()方法执行INSERTUPDATEDELETECREATE TABLE等语句。其中一些重载接受一个用params关键字标记的最后一个参数。Executor对处理参数一无所知。相反,它将该信息传递给它的DataProvider SetupCommand()AssignParameters()方法。

正如AssignParameters()方法的文档所述,参数可以是

  • 逗号分隔的参数列表
  • 或者Params[0]元素,即Params中的第一个元素,可能是一个DataRow、通用IDictionaryIListArray,在这种情况下,将不使用其他Params元素。

如果您传递一个DataRow,那么Tripous使用Column名称来匹配SQL参数名称。如果您传递一个IDictionary,那么Tripous期望它是一个通用的,其中第一个类型参数是string(SQL参数名称),第二个是object(值)。在所有其他情况下,都会使用位置逻辑。

这是一个演示窗体中的private方法,它使用Executor将一行插入数据库表中。

        void InsertRow()
        {
            EnsureDatastore();

            if (tblParams == null)
            {
                string TableName = "Person";
                string SchemaName = string.Empty;
                string SqlText = string.Empty;

                tblParams = new DataTable(TableName);

                executor.GetNativeSchema(SqlText, TableName, SchemaName, tblParams);
            }

            tblParams.Rows.Clear();

            DataRow Row = tblParams.NewRow();
            tblParams.Rows.Add(Row);

            Row["Name"] = edtName.Text;
            Row["Birthday"] = edtBirthday.Value;
            Row["Salary"] = edtSalary.Text;
            Row["Rank"] = edtRank.Text;
            Row["Notes"] = edtNotes.Text;


            string SqlInsert = @"
insert into Person 
(
    Name
   ,Birthday
   ,Salary
   ,Rank
   ,Notes
)
values
(
    :Name
   ,:Birthday
   ,:Salary
   ,:Rank
   ,:Notes
)
";

            executor.Exec(SqlInsert, Row);

            RefreshData();
        }

上述方法准备一个包含单行的DataTable。该DataRow用作执行参数化SQL INSERT语句的Executor.Exec()调用的第二个参数。

Executor_Exec.jpg

Executor.GetNativeSchema()用于从数据库获取有关表或SELECT语句的模式信息。

为数据库表构建SQL语句

Tripous.Data.Db类是一个static辅助类,包含一些方便的方法。BuildSql()可以构建SQL语句(SELECTINSERTUPDATEDELETE)。使用方法如下。

void BuildSql()
{
    SqlStatements Statements = new SqlStatements();
    Db.BuildSql("Person", "Id", false, executor, Statements, true);
    
    StringList List = new StringList();
    List.Add("================= Insert ================= ");
    List.Add(Statements.Insert);
    List.Add("");
    List.Add("================= Update ================= ");
    List.Add(Statements.Update);
    
    Sys.CourierBox(List.Text);
}

BuildSql()需要一个SqlStatements实例,以及其他参数。调用后,该SqlStatements实例将被填充语句。

StringList只是一个方便的string列表类,Tripous在许多地方使用它。

Sys static类位于Tripous系统之上,它只是一个包含大量有用实用方法和属性的集合。

历史

  • 2010年6月6日:初次发布
© . All rights reserved.