使用LinqToSql加载动态column

需求

最近有个小的探索类需求:如何支持在数据库中动态的添加一列?

例如我们有一个type News

news

如果客户说希望多一个属性叫ExpireAt怎么办?

 

方案

想了想,数据库schema方面大概有如下几种办法:

  1. Sharepoint那样的schema,把表定义和rows分离开。确实灵活性很大,但是数据可读性比较差,而且复杂度也高
  2. 给news表创建一列叫个什么ExtendedProperties之类的,专门用于存储键值对,虽然很简单但是bad design。
  3. 在维持news表不动的情况下创建sharepoint式的schema表和values表,用于存储扩展属性的schema和其值
  4. 在news表上真的创建新的column ExpireAt

本文来实现方案4

 

技术选型

  1. 那么具体到方案4,技术上又有很多选择,从orm工具来说
  2. 不用orm,这个当然可以。。。
  3. linq to sql,可以通过使用DataContext.GetTable(Type)方法得到ITable对象,type可以运行时决定,所以满足需求
  4. NHibernate,可以使用dynamic-component,维持Domain model不变的情况下只修改XXXX.hbm.xml完成

当然news的定义中需要加上一个Dictionary用于存储“dynamic-component”

这个方案的问题在于想要使修改过的XXXX.hbm.xml(可以使用序列化或者新的ConfORM)起作用必须重新build SessionFactory(据我所知如此),不知道如何在不影响现有Sessions的情况下做到

  1. 其他,不熟。。。没准儿Entity Framework的Code First模式可以?

 

思路

原计划是在已经加好Table、Column之类Attribute的News基础上运行是动态创建出来一个NewsXXXXX继承于News,添加一个property ExpireAt,加上ColumnAttribute了事。可是实现中发现Linq to Sql不支持这种继承。于是计划变更为只定义POCO的News,需要传入GetTable的时候做一个Wrap的News出来,添加上Table,Column之类的Attribute。当用户创建了新的property,从News中继承出新的POCO的News,然后重新Wrap。最终我的实现大体如下。

news

当然上面只是个示意,后三个类实际上在我的代码中是不存在的,都是运行时创建,名字也是随机的,大家理解个意思就成。。。

注意原始的News类里面属性都要是virtual的了,因为我要在运行时继承它们,这一点上变得像NHibernate了不太爽。。。不过好在在上层的代码之中我只会用到INews而不会用到News,这个后面马上会谈到。

 

模型设计

直接上图

EntitiesClassDiagram

为了屏蔽不同orm之间实现细节,我需要上层只能见到接口见不到实现。需要new的时候就去找IEntityFactory。

其中IContentEntity是所有可以拥有扩展属性的实体类需要实现的接口,例如本例中的INews。上层访问其property的时候就可以调用其indexer,而不关心这个property是真正的class中的property还是从字典中而来(例如NHibernate)。

IProperty自然就是允许用户创建的Property,本例中Property创建成功后会触发一个Added事件,监听方就会继承出XXXNews和WrappedXXXNews。

IView是控制界面上显示哪些property用的,在我的实现中http://site/news/ 只会显示INews中定义的三个property(不包含indexer),而如果访问http://site/news/views/viewId 就会根据viewId来决定显示哪些property。

IListView更进一步的提供了filter的能力。其实filter应该做为另一个单独的接口才是的。。。

 

技术细节

EntityFactory的实现

代码如下

entity factory

 

各个entity(除了contentEntity)都在自己的type initializer里注册自己的constructor,例如

 

type initialier

 


但是content entity不同,是在wrap之后注册的,因为Wrap过的type里才包含orm需要的table、column这些attribute,代码如下

 

wrap

 

 

动态类型生成

本例中采用了两种动态生成类型的方式。

News –> XXXNews这个过程使用了Emit

News –> WrappedNews这个过程使用了codeDom

关于前者,有一点需要说明:

由于XXXNews以后还需要进一步包装为WrappedXXXNews,所以Emit出来的Assembly需要Save到硬盘。存在什么位置呢?我曾经使用了缺省值(Environment.CurrentDirectory),但是XXXNews –> WrappedXXXNews时报告说找不到Emit出来的Assembly(这个异常好隐蔽。。。花了我很久。。。)。于是我尝试Save到bin目录下。但是对bin目录内容的更改又会造成ASP.NET的重编译(听说。具体发生了什么求赐教),于是存到了bin2。。。我也知道这是个很雷的方案。。。在web.config中设置

<probing privatePath="bin;bin2"/>

就可以了。

关于后者,我使用了Expressions to CodeDOM,使用过程实在算不上顺手。。。求推荐更好的codedom类库。

 

Controller的依赖注入

园子里的刘东大人(spring.net达人)已经介绍过了使用spring.net 1.3.1对controller的注入。

我在这里说两个问题

  1. 好像还是要依赖Spring.Core的,估计刘东大把这个assembly加入了GAC?
  2. 还是不知道怎么兑Global.asax进行注入。目前只会比较丑陋的写法
ContextRegistry.GetContext().GetObject("someObj"),而且只能在Application_Start之后使用


接下来的路

本例中还有很多未尽事宜,例如

LambdaExpressionBinder没有完成,所以create一个list view的时候无法把用户输入的lambda表达式转为Func。这个相信用codedom也能做,只是我实在懒得弄了。

还没有实现允许用户输入ExpireAt,也懒得弄了。。。

Wrap content entity type的时候,本来应该读取对应的property,加上相应的column参数的,懒得弄了。。。

还有很多基本的地方,例如NewsProperty、NewsListView根本都没有mapping到数据库。。。。。。懒得弄了。。。

等等等等

你有多懒啊!喂!

不过有些地方不是我懒哦

比如不知道Spring.Net的ControllerFactory是怎么实现的(以后抽个时间看看),我必须要写以下三个Controller,直接在配置文件中指定泛型Controller不行。以后直接上Mvc3试试好了。

NewsPropertyController
NewsListViewController
NewsController

 

虽然如上所述,本例还有非常多的不足,不过我不会再更新了(这不是坑。。。

本来就是个技术学习性质的东西,实际应用可能性很小(比如现在大家都不会用Linq to Sql了吧)。我也真的在这个过程中学到了不少东西,这对我就足够了。

代码下载与声明

本例使用Spring.Net实现IoC和singleton

本例使用了Expressions to CodeDOM实现codedom

本例中的TypeBinder基本照抄自《Pro ASP.NET MVC 2 Framework 2nd Edition》中的实现

运行本例前请自备数据库结构如下(就一个表)

image

并自行修改web.config中的连接串

按F5 首先出现news list

image

然后请进news properties

image

之后Add我们需要的ExpireAt

image

Save成功后再次进入news,这个list不会变化,尝试进入view,就会看到ExpireAt了

image

 

最后给出本例下载

posted @ 2011-01-19 16:08  jiaxingseng  阅读(2086)  评论(8编辑  收藏  举报