Websharp ORM完成对象同数据库之间的映射,其中用到的主要技术涵盖了以下方面:
- DataSet和EntityData
- 元数据和Attribute
- 动态代码生成和编译
1.DataSet和EntityData
Websharp对DataSet进行了一些扩充,其目的是更加方便对DataSet的使用。EntityData的定义如下:
[Serializable]
public
class EntityData : DataSet |
其中,添加了一些比较有用的方法,例如,GetColumnMap方法,可以获得同某个类属性相映射的数据库字段。
在Websharp框架中,所有的实体类都必须从实现IPersistenceCapable接口。另外,定义了抽象类PersistenceCapable,他实现了IPersistenceCapable接口,因此,在通常情况下,实体类都是直接从PersistenceCapable抽象类派生。在IPersistenceCapable中,只定义了一个EntityData属性,这意味着,实际上,在Websharp中,实体类的内部表现形式就是EntityData,也就是DataSet,这样的处理方法,在同数据库的交互中,可以享受到DataSet数据结构的强大功能。
2.元数据和特性(Attribute)
元数据最本质,最抽象的定义为:data
about data (关于数据的数据)。它是一种广泛存在的现象,在许多领域有其具体的定义和应用。在软件开发领域,元数据被定义为:在程序中不是被加工的对象,而是通过其值的改变来改变程序的行为的数据。它在运行过程中起着以解释方式控制程序行为的作用。在程序的不同位置配置不同值的元数据,就可以得到与原来等价的程序行为。元数据描述数据的结构和意义,就象描述应用程序和进程的结构和意义一样。元数据是抽象概念,具有上下文,在开发环境中有多种用途。
元数据是抽象概念
当人们描述现实世界的现象时,就会产生抽象信息,这些抽象信息便可以看作是元数据。例如,在描述风、雨和阳光这些自然现象时,就需要使用"天气"这类抽象概念。还可以通过定义温度、降水量和湿度等概念对天气作进一步的抽象概括。
在数据设计过程中,也使用抽象术语描述现实世界的各种现象。人们把人物、地点、事物和数字组织或指定为职员、顾客或产品数据。
在软件设计过程中,代表数据或存储数据的应用程序和数据库结构可以概括为开发和设计人员能够理解的元数据分类方案。表或表单由对象派生出来,而对象又由类派生。
在元数据中有多个抽象概念级别。可以描述一个数据实例,然后对该描述本身进行描述,接着再对后一个描述进行描述,这样不断重复,直到达到某个实际限度而无法继续描述为止。通常情况下,软件开发中使用的元数据描述可扩展为二至三级的抽象概念。比如
"loan table"
数据实例可以描述为数据库表名。数据库表又可以描述为数据库表对象。最后,数据库表对象可以用一个抽象类描述,该抽象类确定所有派生对象都必须符合的固定特征集合。
元数据具有上下文
人们通常把数据和元数据的区别称为类型/实例区别。模型设计人员表述的是类型(如各种类或关系),而软件开发人员表述的是实例(如
Table 类或
Table Has Columns 关系)。
实例和类型的区别是上下文相关的。在一个方案中的元数据将在另一个方案中变为数据。例如,在典型的关系型
DBMS
中,系统目录将描述包含数据的表和列。这就意味着系统目录描述数据定义,因而可以认为其中的数据是元数据。但只要使用正确的软件工具,仍然可以象操作其它数据一样对这些元数据进行操作。操作元数据的示例包括:查看数据沿袭或表的版本控制信息,或通过搜索具有货币数据类型的列来识别所有表示财务数据的表。在此方案中,如系统目录这样的标准元数据变为可操作的数据。
元数据有多种用途
可以像使用任何类型的应用程序或数据设计元素一样使用元数据类型和实例信息。将设计信息表达为元数据,特别是标准元数据,可以为再次使用、共享和多工具支持提供更多的可能性。
例如,将数据对象定义为元数据使您得以看到它们是如何构造和进行版本控制的。版本控制支持提供一种查看、衍生或检索任何特定
DTS
包或数据仓库定义的历史版本的方法。开发基于元数据的代码时,可以一次性定义结构,然后重复使用该结构创建可作为特定工具和应用程序的不同版本的多个实例。还可以在现有元数据类型之间创建新关系,以支持新的应用程序设计。
.Net下的元数据
在.Net环境下,按照微软的说明,元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件
(PE) 文件或存储在内存中的程序进行描述。我们将代码编译为
PE 文件时,便会将元数据插入到该文件的一部分中,而将代码转换为
Microsoft 中间语言
(MSIL)
并将其插入到该文件的另一部分中。在模块或程序集中定义和引用的每个类型和成员都将在元数据中进行说明。当执行代码时,运行库将元数据加载到内存中,并引用它来发现有关代码的类、成员、继承等信息。
元数据以非特定语言的方式描述在代码中定义的每一类型和成员。元数据存储以下信息:
- 程序集的说明。
- 标识(名称、版本、区域性、公钥)。
- 导出的类型。
- 该程序集所依赖的其他程序集。
- 运行所需的安全权限。
- 类型的说明。
- 名称、可见性、基类和实现的接口。
- 成员(方法、字段、属性、事件、嵌套的类型)。
- 特性。
对于一种更简单的编程模型来说,元数据是关键,该模型不再需要接口定义语言
(IDL) 文件、头文件或任何外部组件引用方法。元数据允许
.NET
语言自动以非特定语言的方式对其自身进行描述,而这是开发人员和用户都无法看见的。另外,通过使用属性,可以对元数据进行扩展。元数据具有以下主要优点:
自描述文件。
公共语言运行库模块和程序集是自描述的。模块的元数据包含与另一个模块进行交互所需的全部信息。元数据自动提供
COM 中
IDL
的功能,允许将一个文件同时用于定义和实现。运行库模块和程序集甚至不需要向操作系统注册。结果,运行库使用的说明始终反映编译文件中的实际代码,从而提高应用程序的可靠性。
语言互用性和更简单的基于组件的设计。
元数据提供所有必需的有关已编译代码的信息,以供您从用不同语言编写的
PE 文件中继承类。您可以创建用任何托管语言(任何面向公共语言运行库的语言)编写的任何类的实例,而不用担心显式封送处理或使用自定义的互用代码。
特性。
.NET Framework
允许我们在编译文件中声明特定种类的元数据(称为特性Attribute),来批注编程元素,如类型、字段、方法和属性。在整个
.NET Framework
中到处都可以发现特性的存在,特性用于更精确地控制运行时您的程序如何工作。另外,可以通过用户定义的自定义特性向
.NET Framework 文件发出自己的自定义元数据。
为运行库编译代码时,特性代码被转换为
Microsoft 中间语言
(MSIL),并同编译器生成的元数据一起被放到可移植可执行
(PE)
文件的内部。特性使我们得以向元数据中放置额外的描述性信息,并可使用运行库反射服务提取该信息。
.NET Framework
出于多种原因使用特性并通过它们解决若干问题。特性可以描述如何序列化数据,指定用于强制安全性的特性,以及限制实时
(JIT)
编译器的优化以使代码易于调试。特性还可以记录文件名或代码作者,或在窗体开发阶段控制控件和成员的可见性。
可使用特性以几乎所有可能的方式描述代码,并以富有创造性的新方式影响运行库行为。
在.Net中,特性是从System.Attribute
派生的特殊的类的。
3.特性和Websharp的ORM
在Websharp中,对象/关系映射通过Attribute来进行。Websharp定义了以下用来完成映射的Attribute:
ü
TableMapAttribute
ü
ColumnMapAttribute
ü
ReferenceObjectAttribute
ü
XmlFileMapAttribute
ü
WebsharpEntityIncludeAttribute
TableMapAttribute指明相关实体类所对应的数据库表,包含两个主要属性:TableName和PrimaryKeys。TableName指明表名,PrimaryKeys指明相关的关键字。其定义如下:
[AttributeUsage(AttributeTargets.Class)]
public
class TableMapAttribute : Attribute
{
private
string tableName;
private
string[] primaryKeys;
public
TableMapAttribute(string
tableName,params string[]
primaryKeys)
{
this.tableName = tableName;
this.primaryKeys = primaryKeys;
}
public
string TableName
{
get{return
tableName;}
set{tableName = value;}
}
public
string[] PrimaryKeys
{
get{return
primaryKeys;}
set{primaryKeys = value;}
}
} |
ColumnMapAttribute指明实体类的某个属性相对应的数据库字段,包含以下主要属性:
ü
ColumnName:相关的数据库字段名
ü
DbType:相关的数据库字段的数据类型
ü
DefaultValue:相关的数据库字段的默认值。
其代码这里就不列出了,请参见源代码。
ReferenceObjectAttribute的作用是,说明实体类的该属性引用了另外一个实体类,应该将该属性映射成另外一个实体类,而不是某个数据库字段。
XmlFileMapAttribute说明该实体类同数据库的映射通过XML文件来进行。这是Websharp支持的另外一种O/R映射的方法。
WebsharpEntityIncludeAttribute用于实体类在不同的层之间序列化传递。当使用Webservice来传递某个实体类的时候,必须给该实体类加上该特性。
4.动态代码生成及编译
使用Websharp开发系统,开发人员只需要对实体类定义一个抽象类,然后调用EntityManager的CreateObject方法,Websharp会在运行时刻动态为这个抽象类生成一个实现类,如前面的章节所演示的。其中用到的技术就是动态代码生成。在.Net中,这个工作是通过CodeDom来完成的。
4.1.关于CodeDom
.NET推崇这样一种思想:相对于框架而言,语言处于从属、次要的地位。CodeDom名称空间中包含的类是这一思想的集中体现。
.NET Framework 中包含一个名为“代码文档对象模型”(CodeDOM)
的机制,该机制使编写源代码的程序的开发人员可以在运行时,根据表示所呈现代码的单一模型,用多种编程语言生成源代码。
为表示源代码,CodeDOM
元素相互链接以形成一个数据结构(称为
CodeDOM 图),它以某种源代码的结构为模型。
System.CodeDom
命名空间定义可以表示源代码的逻辑结构(与具体的编程语言无关)的类型。System.CodeDom.Compiler
命名空间定义从
CodeDOM
图生成源代码的类型,和在受支持的语言中管理源代码编译的类型。编译器供应商或开发人员可以扩展受支持语言的集合。
当程序需要用多种语言为程序模型或者为不确定的目标语言生成源代码时,与语言无关的源代码建模很有价值。例如,如果语言的
CodeDOM 支持可用,则一些设计器将
CodeDOM 用作语言抽象接口,以用正确的编程语言生成源代码。
.NET Framework 中包含
C#、JScript
和
Visual Basic 的代码生成器和代码编译器。在Websharp中使用的是C#的代码生成器和代码编译器。
使用CodeDom生成类,其一般的操作是:
声明编译单元:
CodeCompileUnit
compileUnit = new CodeCompileUnit();
初始化名称空间:
CodeNameSpace
CurrentNameSpace = new CodeNamespace (Name);
创建类:
CodeTypeDeclaration
ctd = new CodeTypeDeclaration (Name);
创建方法:
CodeEntryPointMethod
method = new CodeEntryPointMethod();
装配出树结构:
compileUnit.
Namespaces.Add(CurrentNameSpace);
CurrentNameSpace.Types.Add
(ctd);
ctd.Members.Add
(mtd);
编译代码:
CodeDomProvider
provider = new CSharpCodeProvider();
ICodeCompiler
compiler = provider.CreateCompiler();
CompilerResults
results = compiler.CompileAssemblyFromDom(cp,compileUnit);
Assembly
createdAssembly = results.CompiledAssembly;
4.2.Websharp的实现
代码实现在EntityClassGenerator类中。
首先,初始化代码生成需要的环境,包括CompileUnit、NameSpace等:
compileUnit = new CodeCompileUnit();
tempdAssemblyNameSpace = new
CodeNamespace(EntityClassGenerator.TempEntityImpNamespace);
tempdAssemblyNameSpace.Imports.Add(new
CodeNamespaceImport("System.Data"));
className = "_Impl_"+entityType.Name;
referencedAssemblies = new
StringCollection();
referencedAssemblies.Add("System.dll");
referencedAssemblies.Add("System.Data.dll");
referencedAssemblies.Add("System.XML.dll");
referencedAssemblies.Add(entityType.Assembly.Location); |
然后,申明要生成的类:
CodeTypeDeclaration generateClass = CreateClass();
tempdAssemblyNameSpace.Types.Add(generateClass);
private
CodeTypeDeclaration CreateClass()
{
CodeTypeDeclaration ctd = new
CodeTypeDeclaration(className);
ctd.IsClass = true;
ctd.Attributes = MemberAttributes.Public|MemberAttributes.Final;
ctd.BaseTypes.Add(entityTypeInterface);
ctd.CustomAttributes.Add(new
CodeAttributeDeclaration("Serializable"));
……
} |
然后,按照要生成的类的每个属性,生成类的各个属性:
foreach(PropertyInfo
pInfo in entityTypeInterface.GetProperties())
{
if(entityTypeInterface.IsInterface
|| (entityTypeInterface.IsAbstract &&
pInfo.GetGetMethod().IsAbstract))
{
CodeMemberProperty
property = CreateProperty(pInfo,ctd);
if(property!=null)
ctd.Members.Add(property);
}
} |
更多具体的实现代码,请参照源代码。
因类的生成和编译是一项比较耗费资源的工作,因此,有必要在类生成以后,将其进行缓存,这样,在第二次需要创建相应的对象的时候,只需要直接从已生成的类中创建。这个工作是通过EntityManager来完成的。在实际应用中,我们通常通过如下方式来获取某的实体对象:
Schdule schdule = EntityManager.CreateObject(typeof(Schdule))
as Schdule; |
其中,EntityManager.CreateObject方法的实现如下:
public
static
object CreateObject(Type entityType)
{
if(!HasCachedEntityType(entityType.FullName))
{
EntityClassGenerator
gen = new EntityClassGenerator();
Type
t = gen.GenerateEntityType(entityType);
AddCachedEntityType(entityType.FullName,t);
}
return Activator.CreateInstance(GetCachedEntityType(entityType.FullName));
} |
可以看到,其中使用了缓存处理。在这里,缓存处理的机制很简单,使用了一个静态的HashTable来保存这些缓存信息。
5.同数据库的交互
当需要同数据库进行交互,例如,需要将一个新的对象保存到数据库中时,通过PersistenceManager接口来实现。
PersistenceManager是对象同数据库交互的主要接口,其基本使用方法在前面已经给出,下面是他的定义:
public
interface PersistenceManager
: IDisposable
{
void Close();
bool IsClosed{get;}
Transaction CurrentTransaction{ get;}
bool IgnoreCache{get;set;}
void
PersistNewObject(EntityData entity);
void PersistNewObject(PersistenceCapable
pc);
void UpdateObject(EntityData
entity);
void UpdateObject(PersistenceCapable
pc);
void UpdateObject(EntityData
entity,params string[]
properties);
void UpdateObject(PersistenceCapable
pc,params string[]
properties);
void DeleteObject(EntityData
entity);
void DeleteObject(PersistenceCapable
pc);
void Reload(EntityData
entity);
void Reload(PersistenceCapable
pc);
void Evict (object
pc);
void EvictAll (object[]
pcs);
void EvictAll (ICollection
pcs);
void EvictAll ();
EntityData FindEntityDataByPrimaryKey(object
id,string entityTypeName);
EntityData FindEntityDataByPrimaryKey(object
id,EntityData entity);
PersistenceCapable FindObjectByPrimaryKey(object
id,PersistenceCapable pc);
PersistenceCapable FindObjectByPrimaryKey(object
id,Type entityType);
Query NewQuery();
Query NewQuery(string
entityTypeName);
Query NewQuery(string
entityTypeName,string
filter);
Query NewQuery(string
entityTypeName,string
filter,QueryParameterCollection paramColletion);
Query NewQuery(Type entityType);
Query NewQuery(Type entityType,string
filter);
Query NewQuery(Type entityType,string
filter,QueryParameterCollection paramColletion);
DataAccess NewDataAccess(); |
目前Websharp,在Websharp.ORM.Service名称空间下,实现了单表的操作,名称为SingleTablePM。在SingleTablePM中,进行数据库操作的时候,会使用SqlGenerator的GenerateSql方法,生成操纵数据库的SQL语句,然后,调用Websharp
DAO的数据库访问功能,将数据保存到数据库中。例如,下面的代码显示了保存一个新对象的功能:
public
void PersistNewObject(EntityData
entity)
{
if(entity.Tables.Count<1)
return;
SqlStruct sql=SqlGenerator.Instance(pp).GenerateSql(entity,0,SqlOperationType.Insert);
ExecuteNonQuery(entity,sql);
entity.AcceptChanges();
CacheEntity(entity);
} |
PersistenceManager的创建,总是通过PersistenceManagerFactory来创建,其目的是提供将来的更好的可扩展性,以及做到数据库无关性。PersistenceManagerFactory的定义如下:
public
class PersistenceManagerFactory
{
private
PersistenceManagerFactory() {}
private
static PersistenceManagerFactory
m_PersistenceManagerFactory
=new
PersistenceManagerFactory();
public
static PersistenceManagerFactory
Instance()
{
return m_PersistenceManagerFactory;
}
public
PersistenceManager CreatePersistenceManager()
{
return p_CreatePersistenceManager
(ApplicationConfiguration.DefaultDatabaseProperty);
}
public
PersistenceManager CreatePersistenceManager(DatabaseProperty
pp)
{
return p_CreatePersistenceManager(pp);
}
private
PersistenceManager p_CreatePersistenceManager(DatabaseProperty
pp)
{
return new
SingleTablePM(pp);
}
} |
6.对象查询
在Websharp中,提供了基本的对象查询能力,这个功能通过Query接口来实现。这个接口的定义如下:
public
interface Query
{
Type
EntityType{get;set;}
string
EntityTypeName{get;set;}
string
Filter{get;set;}
QueryParameterCollection
Parameters{get;set;}
string
Ordering{get;set;}
bool
IgnoreCache{get;set;}
EntityData
QueryData();
PersistenceCapable[]
QueryObjects();
PersistenceManager
PersistenceManager{get;}
bool
IsClosed{get;}
void
Close ();
void
Open();
} |
使用Query接口,你可以指定某个需要查询的对象,然后,指定查询条件,就可以进行查询。查询的结果,通常是一个PersistenceCapable数组,或者是一个包含多条记录的EntityData。
在Query中,条件的指定按照符合ANSISQL92标准的格式来指定。当对象的属性名和数据库字段名不完全相等的时候,Query的实现会进行相应的转换,其转换方法可以参见CommonQueryImp的ModiFilter方法。
7.事务处理
在Websharp
ORM中,提供基本的事务处理能力,这通过Transaction接口实现。这个接口的定义如下:
public
interface Transaction
{
void
Begin();
void
Commit();
void
Rollback();
PersistenceManager
PersistenceManager{get;}
} |
目前Websharp提供的事务处理,是基于数据库连接的事务。其实现代码是Websharp.ORM.Service名称空间下的SingleConnecgtionTransaction类。其主要的代码实现如下:
public
class SingleConnecgtionTransaction : Transaction
{
private
DataAccess dao;
private
IDbTransaction trans;
……
public
void Begin()
{
if(dao.IsClosed)
dao.Open();
trans=dao.BeginTransaction();
}
public
void Commit()
{
trans.Commit();
}
public
void Rollback()
{
trans.Rollback();
}
……
} |
目前Websharp提供的事务处理能力是比较弱的,在将来的升级中,会对事物处理能力提供更强的功能。
8.数据缓存
应当认识到,在基于数据库的应用软件系统中,从数据库查询往往需要较长时间的数据库连接时间,这会影响到系统的性能。为了提高系统性能,一个好的方案就是在应用服务层缓存数据。在Websharp中,也提供了数据缓存的功能,其缓存功能使用了微软提供的Caching
Application Block。关于Microsoft
Caching Application Block的内容,可以参见微软的相关文档。在Websharp中,只做了一个CacheProxy,以方便缓存机制的使用。相关代码,这里就不列出来,可以参考源代码。
9.小结
对象/关系映射,是基于数据库的应用软件系统的重要组成部分,一个结构良好的O/R框架,可以给软件系统的开发带来很大的好处,因此,在处理这个部分的内容的时候,需要我们进行精心的设计。Websharp提供了一个可用的方案,能够简化这方面的开发。 |