A Data Access Layer to persist business objects using attributes and reflection - Part III [无常译]
2006-04-12 16:50 无常 阅读(920) 评论(3) 编辑 收藏 举报这是本系列最后一篇文章。在第一篇中我们知道了自给使用特性来给类添加声明信息。第二篇中我们已经知道如何使用System.Reflection namespace来取得声明信息。现在我们将要创建一个DAL库,可以根据任何对象自身的声明信息来实现持久化。
设计DAL库
在创建这个DAL库时,我打算将它设计成既支持SQL Serverdata provider ,也支持OldDbdata provider 。也可以扩展支持其它的data provider。我把这个库分成下面几个部分:
Utility classes
DALQueryBuilder类
在每一篇文章中已经提到过,这个类帮助构建更新对象到数据库中的SQL语句。
DALParameter类
一个普通的参数类,在存储过程中用到。
DALException类
这是一个System.Exception的派生类。在操作数据库操作出错时就会掏出此异常。我提供了有关出错的更多的信息。
Attribute 类
派生于System.Attribute 类,在第一篇中已经介绍过。
DAL itself
DALEngine 类
数据操作的抽象基类,但数据库操作更加容易。它拥有虚拟和抽象方法,不同的data provider会有不同的实现。大多的函数从数据库中返回DataSets、DataReaders,它提供方法来操作使用DAL特性标记过的对象。
DALSqlEngine 类
DALEngine的使用SQL Server的实现。
详细分析DALEngine 类
这是DALEngine类的声明:
{
//
// private data members
//
IDbConnection conn = null;
string connectionString = "";
ArrayList parameters = new ArrayList();
bool canClose = true;
// constructor
public DALEngine(string connectionString);
public bool CanClose;
public string ConnectionString;
protected IDbConnection Connection;
protected ArrayList Parameters;
public void Close();
public void Dispose();
//
// methods that must be override with a specific data provider
// implementation please see the implementation of DALSqlEngine
// or DALOleDbEngine
//
protected abstract IDbConnection GetConnection();
protected abstract IDbCommand CreateCommand(string spName);
public abstract void ExecSP_DataSet(string spName, DataSet dataSet,
string tableName);
public abstract void ExecQuery_DataSet(string query, DataSet dataSet,
string tableName);
//
// related to stored procedure parameters
//
public DALParameter GetParameter(string name);
void UpdateOutputParameters(IDbCommand cmd);
public void AddParameter(DALParameter param);
public void ClearParameters();
//
// for those that use stored procedures
//
public IDataReader ExecSP_DataReader(string spName);
public IDataReader ExecSP_DataReader(string spName,
CommandBehavior behavior);
public object ExecSP_Scalar(string spName);
public int ExecSP_NonQuery(string spName);
//
// methods for those that use plain SQL statements
//
public IDataReader ExecQuery_DataReader(string query,
CommandBehavior behavior);
public IDataReader ExecQuery_DataReader(string query);
public object ExecQuery_Scalar(string query);
public int ExecQuery_NonQuery(string query);
//
// Business objects methods
//
public static object CreateFromReader(IDataReader reader, Type objType);
public object RetrieveObject(object keyValue, Type objType);
public int RetrieveChildObjects(object foreignKeyValue, ArrayList objects,
Type childType);
void UpdateObjectSql(object o, DataTableAttribute dataTable);
void UpdateObjectStoredProcedure(object o, DataTableAttribute dataTable);
public void UpdateObject(object o);
public void UpdateObjects(IEnumerable enumObjects);
}
像你所想象中的一样,我们只需要关注最后的6个函数。其它的是很容易理解的,因为他们很容易实现。
CreateFromReader函数返回一个objType类型的对象的实例。当我们从数据库中取出数据时,就可以使用此函数来返回一个business object实例。它是一个静态函数,所以不需要建立数据库连接,只需要一个DataReader就可以读数据了。
RetrieveObject函数返回一个objType类型的对象的实例。这个实例的属性与表主键列与keyValue参数相等的唯一一行对应。这个函数在底层创建了有WHERE子句的SELECT语句。RetrieveChildObjects创建外键与foreignKeyValue 参数相等的 childType 类型的对象实例。创建的这些实体都被加到objects 参数中。
UpdateObject 和UpdateObjects 函数对数据库进行一个更新操作。更新可以使用储存过程(在此例中的类的DataTable 特性用UpdateStoreProcedure 属性来设置)或是普通SQL语句。二个私有方法UpdateObjectSql 和UpdateObjectStoredProcedure 负责执行这些更新操作。
这样做的好处
所有使用此DAL的都会使数据库编程更加容易。我强烈建议使用这个DAL,因为已经实现了重复冗长的任务,即使你没打算像我一样进行数据库编程。这次你不是必需用到,但以后至少会用到一次。
但是如果你开发数据库程序的时候用business objects ,这个库可以帮你节省很多时间和精力。这只有一个好处,就是在修改数据库时需要重新检查代码,这可以使用你自己的一个工具来完成,但利用在这篇文章的第二部分创建的工具可以完成相反的功能,即先修改business objects然后再根据business objects生成表。
为了让你更容易理解获取和更新对象,我在DAL库的代码中包含有一个样本的应用程序。这里有一些此应用程序的代码片断,你可以从中了解它的功能。
这个DAL库确实存在很多BUG,这也是我在这里发表这篇文章的原因之一。也许你会有更好的方法和代码来实现这个库的目的。有什么建议请告诉我,像我一样乐于改进这相库。
因为我要使用SQL Server,可以我从DALSqlEngine派生了一个DAL类来实现这个样板应用程序。这是我的DAL类的代码:
{
const string CONN_STRING = "server=localhost;uid=sa;pwd=;database=pubs";
public DAL() : base(CONN_STRING)
{
}
public ArrayList GetCustomerDependents(Customer customer)
{
ArrayList result = new ArrayList();
RetrieveChildObjects(customer.Id, result, typeof(CustomerDependent));
return result;
}
public void UpdateCustomerDependents(Customer customer)
{
UpdateObjects(customer.Dependents);
}
}
是不是很简单?现在是这个应用程序的代码,仅仅插入/更新了contact/customers和customer从属的对象。
{
DAL dal = new DAL();
try
{
Contact contact = new Contact();
contact.Name = "Joao Cardoso";
contact.Age = 23;
contact.Address = "Av. Rio Branco, 202/121";
contact.Address2 = "Centro";
contact.PostalCode = "09029-901";
contact.City = "Sao Paulo";
contact.State = "SP";
contact.Country = "Brazil";
dal.UpdateObject(contact);
Console.WriteLine(contact);
Contact joaoCardoso = (Contact)dal.RetrieveObject(1, typeof(Contact));
joaoCardoso.Age++;
Console.WriteLine(joaoCardoso);
Console.WriteLine("");
Customer customer = new Customer();
customer.Name = "Paul Noyter";
customer.Age = 34;
customer.Address = "All St, 2202/2121";
customer.Address2 = "Downville";
customer.PostalCode = "90931";
customer.City = "Los Angeles";
customer.State = "CA";
customer.Country = "United States";
customer.TotalPurchased += 1900.87M;
customer.NumberOfPurchases++;
dal.UpdateObject(customer);
Customer paul = (Customer)dal.RetrieveObject(1, typeof(Customer));
Console.WriteLine(paul);
paul.TotalPurchased += 100M;
paul.NumberOfPurchases++;
dal.UpdateObject(paul);
if (paul.Dependents.Count == 0)
{
CustomerDependent dependent = paul.NewDependent();
dependent.Name = "Marie Noyter";
dependent.Age = 31;
paul.Dependents.Add(dependent);
dependent = paul.NewDependent();
dependent.Name = "Mark Noyter";
dependent.Age = 10;
paul.Dependents.Add(dependent);
dependent = paul.NewDependent();
dependent.Name = "Claudia Snorg";
dependent.Age = 32;
dependent.Relationship = CustomerRelationship.Friend;
paul.Dependents.Add(dependent);
dal.UpdateCustomerDependents(paul);
}
else
{
Console.WriteLine("Dependents of {0}", paul.Name);
foreach(CustomerDependent dependent in paul.Dependents)
{
Console.WriteLine("<Dependent>{0} - {1} [{2}]", dependent.Id,
dependent.Name, dependent.Relationship);
dependent.Relationship = CustomerRelationship.Family;
}
dal.UpdateCustomerDependents(paul);
}
}
finally
{
dal.Dispose();
}
}
局限性
当然这个库也有一定的局限性。部分可以通过扩展DAL或attributes来解决。如果你不使用自动增长列,那么就必须使用存储过程来更新数据。这是因为DAL不能断定这个对象是更新还是插入到数据库。你可以扩展DAL来检测已经的特性或接口来提供需要的信息;一般不支持复合主键的表;性能要比直接使用SqlCommand要差些,但是你会有更多的时间来使用business classes替换数据库编程;我没有实现使用存储过程从数据库中检索对象,但这很容易实现。
结论
在.NET世界里,有很多途径来进行数据库编程。Visual Studio .NET允许你使用强类型的DataSet,可以节省你很多生成代码的时间。使用DataSets也一点不错,但是我更喜欢用business objects。当我开始开发这样的一样类的时间,我就可以知道写其它的类需要多长时间。这个库介绍到这里,你应该知道了怎么完善它来开发使用business objects的数据库应用程序。
---------------------------------------------------by wuchang
终于完成了这篇文章的翻译。
很多外文的文章,阅读没什么问题,意思都能看懂,但想用文字翻译流畅出来,却要费一番工夫了。
翻译得不好的地方欢迎指出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架