完全数据库抽象层生成器






4.74/5 (26投票s)
2003年10月23日
8分钟阅读

281867
又一个DAL生成器,具有SQL生成、多数据库支持、C#代码生成等功能...
引言
本文介绍了另一个数据抽象层生成器(热门话题)。DAL结构直接借鉴了DotNetNuke、DAL文档,SQL生成使用SQLpp完成。
该生成器为您处理几乎所有事情
- SQL代码生成:数据库创建和存储过程。此外,SQL经过专门化处理,以考虑每个提供程序的SQL语法差异,
- Web.Config生成:用于配置DAL的XML条目,
- 业务逻辑层,用于每个表和过程(C#),
- 抽象数据提供程序,用于每个表和过程(C#),
- 专用数据提供程序,为每个数据提供程序定制(C#),
- IDataReader包装器类,用于表和过程(C#)
一些有趣的特性是
- 无需SQL操作:使用生成器,您无需编写一行SQL,
- 多数据库系统支持:MSSQL,MySQL。其他数据库可以轻松实现,
- 通过将查询存储在web.config中(如果需要)来处理存储过程问题,
- 支持复杂的表连接:生成器在后台使用Boost.Graph.Library来生成查询
- C#代码已文档化!
- 非商业和商业用途完全免费!!!
所有下载、文档和跟踪器均可从SourceForge页面获取。
要求
以下提供程序需要一些特定的(免费)库
- MSSQL,Microsoft.ApplicationBlocks.Data
- MySQL,ByteFX
此外,为了充分理解DAL结构,您应该查看DNN AL文档。
说明性示例
生成过程将通过一个简单的模块创建示例进行说明。假设您想要创建一个由以下结构定义的Users-Products-Orders数据库
由于SqlppNet仍然是一个无GUI工具,所以您需要手动编写。
数据库提供程序
第一步是创建一个数据库适配器。例如,MSSQL提供程序构造如下
MsSqlAdaptor msSql = new MsSqlAdaptor(
"MyCatalog",
"providerpath",
"dbo",
"connectionstring"
);
其中
MyCatalog
是数据库名称providerpath
是提供程序路径,dbo
是数据库所有者,connectionString
是此提供程序的连接字符串,
当然,您以后可以创建其他适配器并为其生成代码。
数据库创建
一旦创建了适配器,就会创建一个数据库并将适配器附加到它
Database db = new Database( msSql );
添加表
使用以下函数调用添加表
DbTable users = db.AddTable(
"User", // data name
"dnn_", // object qualifier
"User" // field prefix
);
其中
- 数据名称用于组成表名并创建包装器类
- Users将是表名,
- UserData, UserDataProvider等将是与
Users
关联的类名
- 如DAL文档中提到,对象限定符用于“修饰”表名:dnn_Users是真实的表名,
- 字段前缀用作自动字段前缀,附加到表的每个字段(预期外键字段):如果您添加一个名为
ID
的字段,它将被重命名为“UserID”。
其他表也类似添加
DbTable products = db.AddTable("Product","mytables_","Product");
DbTable orders = db.AddTable("Order","mytables_","Order");
添加主键
添加主键很简单
users.AddPrimaryKey("ID","int"); // adds UserID INT as primary key
注释
- 此方法自动在主键上定义自动递增
- 可以定义更复杂的主键,但这超出了本文的范围
int
字符串由框架内部解析并转换为SQL类型。解析器包含大部分可接受的SQL类型。
同样,其他表也类似处理
orders.AddPrimaryKey("ID","int");
products.AddPrimaryKey("ID","int");
链接表
现在是时候设置表之间的关系了。通过向表添加外键(引用约束)或唯一约束来强制执行关系/约束。
将Users
外键添加到Orders
表如下所示
orders.AddForeignKey( users );
请注意,此方法使用许多默认设置,例如ON DELETE
、ON UPDATE
行为。Orders
表现在包含一个引用Users
表的UserID
字段。
到products
表的外键也类似添加
orders.AddForeignKey( products );
添加字段
遵循相同的逻辑,使用Table.AddField
添加字段
products.AddField(
"Name", // field name (remember that field prefix is added
"nvarchar(50)", // type
true // not null
);
products.AddField("Price","decimal",true);
orders.AddField("Quantity","int",true);
orders.AddField("Timestamp","timestamp",true);
此时,您的数据库结构已准备就绪。
创建一些查询
当然,您需要对上面定义的数据库进行一些查询。这也由SQLppNet处理。假设我们想要创建以下查询
Select user id, product name, quantity, price and order id
from users, products and orders.
- 创建一个查询,
SelectQuery q = db.CreateQuery();
- 添加带别名的表(可选)并链接它们,
QueryTable qusers = q.AddTable( users, // table to add "U" // alias );
其他表也类似添加。表需要连接
q.Join( qorders, // table qusers // reference table ); q.Join(qorders,qproducts);
SQLpp将处理
Join
过程的其余部分。 - 添加字段
q.AddField( FieldExpression.Link( // links qusers and userID field qusers, // users.get_Field("ID") // retreiving UserID field ), "UserID" // giving the AS name );
其他字段也可以用相同的方式添加。在C++版本中,您可以添加更复杂的SQL表达式,例如聚合函数、算术表达式等...请参阅下面的注释。
- 添加
Where
子句(尚未封装,请参阅下面的注释)
注意:SQLppNet是SQLpp的一个薄型托管C++包装器。目前,该包装器不完整。要获得更多功能,请返回C++。
设置生成器
需要创建一个C#生成器对象并进行配置
CsGenerator cs = new CsGenerator(
db, // the database to generator
".", // output file path
"MyNamespace", // created namespace
"StoredProc" // name of the stored procedure class(explained later)
);
C#生成器默认不为所有数据库表生成包装器,您必须将要“生成”的表添加到生成器中(由于Users
表已存在于数据库中,我们不需要生成它)
cs.AddTable(orders);
cs.AddTable(products);
您还需要添加您生成的特定查询
cs.AddProcedure( StoredProcedure.Wrap(q, "GetOrders") );
生成DAL
这无疑是最简单的部分
cs.Generate();
代码摘要
这是您需要编写的代码的简短摘要,以生成简单的数据库结构
using namespace SQLppNet;
using namespace SQLppNet.Adaptors;
using namespace SQLppNet.Generators;
using namespace SQLppNet.Queries;
try
{
// creating MsSql adaptor
MsSqlDatabaseAdaptor msSql = new MsSqlDatabaseAdaptor(
"MyCatalog",
"",
"dbo",
"connectionstring"
);
// creating db
Database db = new Database( msSql );
// add tables
DbTable users = db.AddTable("User","","User");
DbTable products = db.AddTable("Product","ex_","Product");
DbTable orders = db.AddTable("Order","ex_","Order");
// add pk
users.AddPrimaryKey("ID","int");
orders.AddPrimaryKey("ID","int");
products.AddPrimaryKey("ID","int");
// link tables
orders.AddForeignKey( users );
orders.AddForeignKey( products );
// add fields
products.AddField("Name","nvarchar(50)",true);
products.AddField("Price","decimal",true);
orders.AddField("Quantity","int",true);
orders.AddField("Timestamp","timestamp",true);
// create generator
CsGenerator cs = new CsGenerator(
db,
"c:\\Inetpub\\wwwroot\\DnnFramework\\DotNetNuke\\DesktopModules",
"UserQuotes",
"StoredProc"
);
// adding tables to generate
cs.AddTable(orders);
cs.AddTable(products);
// a query
SelectQuery q = db.CreateQuery();
// adding tables
QueryTable qusers = q.AddTable(users,"U");
QueryTable qorders = q.AddTable(orders,"O");
QueryTable qproducts = q.AddTable(products,"P");
// joining tables
q.Join(qorders, qusers);
q.Join(qorders,qproducts);
q.AddField( FieldExpression.Link(qusers,
users.get_Field("ID")),"UserID" );
q.AddField( FieldExpression.Link(qproducts,
products.get_Field("Name")),"ProductName" );
q.AddField( FieldExpression.Link(qorders,
products.get_Field("Price")),"Price" );
q.AddField( FieldExpression.Link(qorders,
orders.get_Field("Quantity")),"Quantity" );
cs.AddProcedure( StoredProcedure.Wrap(q, "GetOrders") );
// generation
cs.Generate();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
所以这不到100行,但它将生成比这更多的行。
生成的代码
本节描述了上述应用程序生成的内容。
生成文件结构
文件/类结构布局如下,并“C#就绪”(准备好包含在C#项目中)
文件/路径 | description |
. | 根路径 |
/Config | 配置类目录(Config 命名空间) |
/Config/webconfig.xml | 要包含在应用程序的web.config中的XML |
/Config/DataProviderConfigurationHandler.cs | 处理web.config文件中数据的类 |
/Data | DAL类目录(Data 命名空间) |
/Data/DbSql.sql | 用于创建Orders 和Products 表的SQL代码。 |
/Data/OrderAbstractDataProvider.cs | Orders 表的抽象数据提供程序类 |
/Data/OrderSqlDataProvider.cs | Orders 表的MSSQL数据提供程序类 |
/Data/OrderStoreProcSql.sql | OrderSqlDataProvider 数据提供程序使用的存储过程 |
/Data/OrderDB.cs | Orders 表的业务逻辑层(OrderDB 类) |
/Data/OrderData.cs | Orders 表的DataRow 包装器 |
products 表和存储过程也一样 |
SQL文件已准备好由OSQL工具运行。
配置项目:web.config条目
将.cs文件添加到项目并执行.sql文件后,您需要使用生成的webconfig.xml文件更新web.config文件,该文件如下所示
<web.config>
<!--add this before system.web-->
<configSections>
<section name="mynamespace"
type="MyNamespace.Config.DataProviderConfigurationHandler, MyNamespace"/>
</configSections>
<system.web/>
<!--add this after system.web-->
<mynamespace>
<data defaultProvider="Sql">
<providers>
<clear/>
<add type="Sql" name="MyDatabase"
connectionString ="connectionstring"
providerPath ="" databaseOwner="dbo"/>
</providers>
<dataProviders>
<clear/>
<add name="Order" objectQualifier="ex_">
<providers>
<provider name="Sql"
type="MyNamespace.Data.OrderSqlDataProvider"/>
</providers>
</add>
<add name="Product" objectQualifier="ex_">
<providers>
<provider name="Sql"
type="MyNamespace.Data.ProductSqlDataProvider"/>
</providers>
</add>
<add name="StoredProc" objectQualifier="">
<providers>
<provider name="Sql"
type="MyNamespace.Data.StoredProcSqlDataProvider"/>
</providers>
</add>
</dataProviders>
<procedures/>
</data>
</mynamespace>
</web.config>
通常,您只需按照指定将此文件集成到您的web.config中。
数据行包装器类
此类不属于DNN DAL文档。该包装器具有以下优点
- 通过包装器属性访问字段
- 避免在检索字段名时出现拼写错误
- 通过Intellisense简化编程。
- 在
Dispose
时关闭读取器,以避免未关闭的数据库连接
ProductData
类定义如下(长代码)
///<summary>A wrapper class for the ex_Products table.</summary>
///<summary>A wrapper class for the ex_Products table.</summary>
public class ProductData : IDisposable
{
private IDataReader m_dr;
private int m_ProductID;
private String m_ProductName;
private decimal m_ProductPrice;
///<summary>Create a data wrapper</summary>
///<param name="dr">a opened data reader.</param>
///<exception cref="ArgumentNullException">if dr is null</param>
public ProductData(IDataReader dr)
{
if (dr == null)
throw new ArgumentNullException("datareader");
m_dr = dr;
}
///<summary>Create a data wrapper</summary>
///<param name="dr">a opened data reader.</param>
///<exception cref="ArgumentNullException">if dr is null</param>
public ProductData()
{
m_dr = null;
}
///<summary>Reads the row data from the data reader</summary>
///<returns>true if data was read, false otherwize</returns>
///<exception cref="System.ArgumentNullException">
///Thrown if dr is null</exception>
public bool Read()
{
if (m_dr == null)
throw new Exception("data reader is null");
if (!m_dr.Read())
{
m_dr.Close();
m_dr = null;
return false;
}
m_ProductID=(int)m_dr["ProductID"];
m_ProductName=(String)m_dr["ProductName"];
m_ProductPrice=(decimal)m_dr["ProductPrice"];
return true;
}
///<summary>Closes the reader, if any</summary>
public void Close()
{
if (m_dr != null)
{
m_dr.Close();
m_dr = null;
}
}
///<summary>Release and close the reader</summary>
public void Dispose()
{
Close();
}
///<summary>ProductID set/get property</summary>
public int ProductID
{
get{ return m_ProductID;}
set{ m_ProductID=value;}
}
///<summary>ProductName set/get property</summary>
public String ProductName
{
get{ return m_ProductName;}
set{ m_ProductName=value;}
}
///<summary>ProductPrice set/get property</summary>
public decimal ProductPrice
{
get{ return m_ProductPrice;}
set{ m_ProductPrice=value;}
}
} // ProductData
如您所见,数据检索只执行一次,并对用户隐藏。
使用生成的类操作表
生成的类完全模拟了DNN DAL提案(参见下面的方案)。让我们从一个简单的示例开始,其中操作Products
表。
DAL结构,摘自DNN DAL文档。
- 创建业务逻辑对象:
// creating the products table business logic ProductDB productDB = new ProductDB();
- 添加新产品。您可以使用包装器或直接将参数传递给
AddProduct
方法。如果表包含自动递增字段,则将返回此字段或更新包装器。// creating a product row wrapper ProductData pd = new ProductData(); // setting fields, each field is a property pd.ProductName = "a product name"; pd.ProductPrice = (decimal)49.99; // add the product productDB.AddProduct(ref pd);
如您所见,
pd
作为引用传递给AddProduct
方法。实际上,ProductID
字段是从数据库中检索(是自动递增字段)并更新到pd
中。您也可以通过直接调用来避免使用ProductData
int pid = productID.AddProduct("name", (decimal)49.99);
- 按ID检索产品。
ProductData
类在创建时存储一个IDataReader
对象,并在释放时关闭它。该包装器有一个Read
方法,其行为类似于IDataReader.Read
。using(ProductData pd = productDB.GetProduct(pid)) { // try to reade the Datareader if (!pd.Read()) throw new Exception("..."); // pd contains the data. string name = pd.ProductName; } // the reader is automatically closed from this loop (exception safe)
- 更新产品:
productDB.UpdateProduct( pd ); productDB.UpdateProduct(pid, name, price); // equivalent
- 按ID删除产品:
productDB.DeleteProduct( pd ); productDB.DeleteProduct( pid );
这些是操作表的主要四个生成方法。
使用生成的类执行查询
查询也有自己的业务逻辑对象。在这种情况下,它被称为StoredProcDB
,其中方法以存储过程名称命名(记住上面的StoredProcedure.Wrap
)。实际上,要调用GetOrders
存储过程,我们这样做
- 使用IDataReader
StoredProcDC spDB = new StoredProcDB(); using (GetOrdersData orderData = new GetOrdersData( spDB.GetOrders() )) { while(orderData.Read()) { // orderData contains the current row data } }
- 使用DataSet
DataSet ds = spDB.GetOrdersDataSet();
至此,生成的代码可见部分的描述结束。让我们看看幕后发生了什么。
幕后
幕后发生的事情与您在DNN DAL描述中发现的非常相似。例如,方法ProductDB.GetProduct
的布局如下
public IDataReader GetProduct(int ProductID)
{
return ProductDataProvider.Instance().GetProduct(ProductID);
}
ProductDataProvider.Instance
是ProductDataProvider.cs中定义的静态方法:它在缓存中查找数据提供程序构造函数,如果找不到。它从Web.config获取数据提供程序类型并将其插入缓存。
static public ProductDataProvider Instance()
{
// Use the cache because the reflection used later is expensive
System.Web.Caching.Cache cache = System.Web.HttpContext.Current.Cache;
string providerKey = m_ProviderName + m_ProviderType + "provider";
if ( cache[providerKey] == null)
{
// Get the name of the provider
DataProviderConfiguration providerConfiguration =
DataProviderConfiguration.GetProviderConfiguration(m_ProviderType);
// The assembly should be in \bin or GAC,
// so we simply need to get an instance of the type
Provider provider =
providerConfiguration.Providers
(providerConfiguration.DefaultProvider);
DataProvider dataProvider =
providerConfiguration.DataProviders("Product");
String type = dataProvider.Type( provider.Name );
// Use reflection to store the constructor of
// the class that implements DataProvider
Type t = Type.GetType(type, true);
// Insert the type into the cache
cache.Insert(providerKey,
t.GetConstructor(System.Type.EmptyTypes));
}
return (ProductDataProvider)
((ConstructorInfo)cache[providerKey]).Invoke(null);
}
提供者
目前,有两个可用的提供程序:MSSQL和MySQL。但是,这可以扩展到其他数据库系统:OleDB、PostgreSQL、Oracle等...
MSSQL 提供程序
此提供程序使用Microsoft应用程序块,如DNN DAL描述中所示。
MySQL 提供程序
此提供程序使用可在SourceForge站点获取的ByteFX库。由于MySQL不支持存储过程,查询存储在web.config中。例如,让我们为前面的示例添加一个MySQL提供程序
MySqlAdaptor mysql = new MySqlDatabaseAdaptor(
"MysqlDB",
"",
"",
"connectionstring");
cs.AddAdaptor(mysql);
以下元素添加到web.config中
<web.config>
...
<mynamespace>
<data defaultProvider="Sql">
<providers>
<clear/>
<add name="Sql" catalog="MyDatabase"
connectionString ="connectionstring"
providerPath ="" databaseOwner="dbo"/>
<add name="MySql" catalog="MysqlDB"
connectionString ="connectionstring"
providerPath ="" databaseOwner=""/>
</providers>
...
<procedures>
<clear/>
<add name="GetOrders">
<versions>
<version provider="MySql">
<![CDATA[SELECT
U.UserID AS 'UserID',
P.ProductName AS 'ProductName',
O.ProductPrice AS 'Price',
O.OrderQuantity AS 'Quantity'
FROM (ex_Orders AS O
INNER JOIN Users AS U
ON U.UserID = O.UserID)
INNER JOIN ex_Products AS P
ON P.ProductID = O.ProductID
;]]>
</version>
</versions>
</add>
</procedures>
</data>
</mynamespace>
想贡献吗?
SQLppNet是一个开源项目,始终需要新人新思想来发展。如果您有兴趣,请在SourceForge页面留言。