使用数据库元数据为Entity Framwork实体生成注释
众所周知Entity Framwork自动生成的实体上没有注释。
img1
没错,我使用的是Entity Framwork的各种应用模式中的数据库先行模式。本文不讨论Entity Framwork的哪种应用模式更好的问题而是讨论Entity Framwork提供的T4模板没有生成实体注释的问题。
通常架构数据库的时候会书写描述架构的元数据。如
img2
SqlServer把这些元数据存储在哪里我不关心。但是从下图可以发现貌似这种元数据是个键值对结构。并且该类元数据被称作Extended Properties
img3
通常数据库会以数据库爱好的方式提供操作数据的接口。操作这里的Extended Properties类别的元数据的接口是什么呢?是下面一个系统元数据函数和三个系统存储过程:
img4
img5
img6
img7
这四个接口刚好完成我们所需要的对Extended Properties类别元数据的CRUD操作。可惜,Entity Framwork的实体数据模型中并不包含这类属性。虽然这类元数据与ORM无关但是它们对我们的代码有利,如果能把这类元数据以注释的形式加到实体类代码上的话。
为了确认Entity Framwork的实体数据模型上不包含这类元数据,现在然我们以数据库优先的方式创建一个Entity Framwork项目,随便找一个数据库建好了后从数据库更新元数据
img8
在点击“Update Model From Database”之前开启SqlServer Profiler程序
img9
通过监视Entity Framwork更新元数据的sql代码可以发现Entity Framwork并没有查询Extended Properties类别的元数据。上面说了这类元数据与ORM这件事情无关,所以干正经事的Entity Framwork根本不需要查询这类元数据。但是我们想要利用数据库中的Extended Properties元数据为实体代码自动添加注释,这件事对于我们来说是正经事。想要利用实体数据模型上的元数据为实体代码添加注释的道路是难以走通了因为Ef都根本没去查这类元数据。
那么,我们自建模型。通过监视Entity Framwrok从数据库更新元数据的Sql代码我们领会到了SqlServer元数据数据访问方面的知识。研究发现为生成实体数据模型元数据Entity Framwork查询下如下结构的四种数据:
img10
上图四种元数据分别对应SqlServer的表、视图、表列和视图列。当然这四类元数据现在表现为四张数据库视图这是经过我封装好了的。我们将这四张视图作为提供元数据的“接口”。没错就是“接口”,谁规定过接口必须是C#中的interface关键字定义的结构?接口是契约,这四张视图就是我们的数据库契约,现在我建的每个数据库都实现了这个接口,它们都具有这四张视图。这四张视图是通过参考Entity Framwork获取元数据的Sql语句建立的。这些Sql查询太过复杂,尤其查询表列和视图列的元数据的查询语句复杂的让人难以入眼,下面只贴一个表视图吧:
img11
有了上面四张视图,再加上三个系统存储过程sys.sp_addextendedproperty、sys.sp_updateextendedproperty、sys.sp_dropextendedproperty和一个系统元数据函数::fn_Listentendedproperty,如果这些知识都是我们熟悉的或者留意过的则大部分人可以在头脑中有一个大致的解决方案了。但作为善终,本文还得继续。废话少说,下面开始架构和编码。
讨论到现在发现一直都是在围绕着数据库说事,可见数据库这个概念很重要,所以我想把它抽象一下先,建个Db类:
img12
Db类很简单基本没有什么可说的,唯有它的GetConnection方法值得一说,我们将上面的类图换个视角看一看:
img13
Db类的GetConnection方法构建数据库连接的任务是通过委托ADO.NET的DbProviderFactory类型的对象完成的,而dbProviderFactory作为DbProviderFactory类型的实例是通过ADO.NET的DbProviderFactories.GetFactory(this.ProviderName)静态方法传入ProviderName字符串构建的
img14
当然除了Db类外,我们也需要为表、表列、视图、视图列类别的元数据建模,不过它们四个并没有出彩的地方:
img15
还记得前文提到的数据库上的那四个视图接口么?上图的这四个对象模型映射的就是数据库中的那四个视图(关系模型)。Db类型的对象不需要持久化吗?当然需要,它持久化到数据库中的Database表:
img16
同样Database表也没有什么出彩的地方,前文有一张图上提到数据库连接“引导库”,Database表所在的库就是这个引导库。
模型建好的,为了简化问题,就把上面的Db模型、DbTable模型、DbView模型、DbTableColumn模型和DbViewColumn模型当数据访问模型。现在基于这四个模型定义一套元数据访问接口:
img17
目前来说有意义的是图上红色矩形圈住的几个方法。这几个方法就是用以实现对Extended Properties类别的数据库元数据的增删改操作的,它们都是通过调用一个很臭的私有方法实现的:
img18
至此,终于我们实现了对数据库的Extended Properties类别的元数据的管理。元数据填好了就可以供我们所用了。现在让我们把元数据读成下图所示的形式。这张图完整的记录的数据库、表、表列、视图、视图列的元数据以及Extended Properties类别的元数据。
img19
好了,现在我们可以修改Entity Framwork的T4实体类代码生成模板了。
img20
首先构建数据库元数据提供程序,然后调用它的GetDbDoc方法传入数据库名查询出给定的数据库的文档。该文档是一张对象图,其对象间的关系参见上面贴过的图。
沿着Entity Framwork的T4模板代码往下找,下面是生成类的代码,我们在上面添加类注释:
img21
下面是生成普通属性的代码,我们在上面添加属性注释:
img22
好了,现在运行自定义过Entity Framwork的T4代码模板,看一下效果:
img23
如愿的生成了注释。
后话:
其实设计这个数据库元数据管理模块本来的目的不是生成实体代码注释,而是为了生成数据库文档。之所以要在查看文档时自动生成是因为众所周知的一个问题,文档很容易过期,没人维护。数据库文档模块做好后发现数据库文档上的信息对实体代码同样有意义,为Entity Framwork的实体代码生成注释只是数据库元数据管理模块的意外惊喜。生成的文档效果如下:
img24
img25
img26
(完)