对于多个数据库表对应一个Model问题的思考
2015-03-03 22:34
106 查看
最近做项目遇到一个场景,就是客户要求为其下属的每一个分支机构建一个表存储相关数据,而这些表的结构都是一样的,只是分属于不同的机构。这个问题抽象一下就是多个数据库表对应一个Model(或者叫实体类)。有了这个问题,我就开始思考在现有的代码中解决问题,最早数据采集部分是用EF来做数据存储的,我查了一下,资料并不多,问了一下对EF比较熟悉的朋友,得出的结论是EF实现这个功能比较复杂,不易实现。EF不能实现就要去找其他的框架,在PDF.NET的讨论群跟大家讨论这个问题的时候,@深蓝医生说PDF.NET可以支持这个,在医生的指导下,我研究了PDF.NET的源码,确实可以实现这个功能。在PDF.NET的源码中,有一个EntityBase的类,这是所有实体的基础类,该类里面有以下两个方法:
看到这两个方法,大家应该就基本明白了,有了这两个方法就可以很方便的根据需要将同一个实体也就是Model指向不同的表。如果对PDF.NET不了解可能看着比较糊涂,我这里简单的解释一下,在PDF.NET中,实体的就像一个个的表结构,而这个表结构具体属于哪个真实的表是需要通过EntityBase这个基础类提供的TableName属性来设置的,而PDF.NET又支持将实体类通过自己特有的OQL方式拼写成SQL语句再执行,所以,在执行SQL之前,我们可以很方便的通过修改实体类的TableName属性让我们的SQL语句最终指向不同的表,是不是很简单?
另外,对于一个项目来说,能做到一个Model对应多个表还不够,因为在实际情况下,你是无法预知会有多少表的,即便你已经知道这些表对应的Model只有一个,随着业务的开展,表也在增加。那怎么解决这个问题呢?有了表对应的Model,那用什么方式来动态增加表呢?目前最常用的就是CodeFirst的方式,还好最新版的PDF.NET已经开始支持CodeFirst的方式,不过,我要用的时候发现还不能支持Postgresql的CodeFirst方式,主要问题是主键的自增,大家都知道,Postgresql并不像SQL Server那样原生支持自增主键,要实现Postgresql的自增主键一般是借助于序列,在数据库中新建一个序列,然后自增主键取值于这个序列,思路比较清晰,直接动手改源码
我在建表之前,先新建一个序列,新建的表的自增主键引用这个序列即可。
在修改源码的过程中,我发现了一个问题,如果实体中字段的类型为String,它在表中可能对应char,varchar或者text,怎么解决这个问题呢?思考无果后,我想到EF中对这个的支持很好,那EF中是怎么解决这个问题的呢,翻了半天代码,终于找到了相应的源码,贴出来看看:
可能看了这么长的一段源码有点头疼,不知道什么意思,没关系,我们只看需要的部分
很明显这一段的功能是区分char,varchar和text,怎么区分的呢?IsFixedLength,MaxLength是不是很熟悉,对了,这就是EF实体类中字段上的元标记,可惜PDF.NET并不支持元标记,思考了半天,只能用一个折中的办法,代码如下:
PDF.NET虽然不支持元标记,但是它支持给字符串类型的字段设置字段最大长度,所以,这里的解决办法就是如果用户设置了字段长度就用varchar(n)的方式建表,如果没有设置就用text或者varcahr(max)建表。
说到这里,PDF.NET不光可以解决我的一个Model对应多个表的问题,还可以解决表的动态增加问题。
开源就是这样,自己动手,丰衣足食!
/// <summary> /// 将实体类的表名称映射到一个新的表名称 /// </summary> /// <param name="newTableName">新的表名称</param> /// <returns>是否成功</returns> public bool MapNewTableName(string newTableName) { if (EntityMap == EntityMapType.Table) { this.TableName = newTableName; return true; } return false; } /// <summary> /// 获取表名称。如果实体类有分表策略,那么请重写该方法 /// </summary> /// <returns></returns> public virtual string GetTableName() { return _tableName; ; }
看到这两个方法,大家应该就基本明白了,有了这两个方法就可以很方便的根据需要将同一个实体也就是Model指向不同的表。如果对PDF.NET不了解可能看着比较糊涂,我这里简单的解释一下,在PDF.NET中,实体的就像一个个的表结构,而这个表结构具体属于哪个真实的表是需要通过EntityBase这个基础类提供的TableName属性来设置的,而PDF.NET又支持将实体类通过自己特有的OQL方式拼写成SQL语句再执行,所以,在执行SQL之前,我们可以很方便的通过修改实体类的TableName属性让我们的SQL语句最终指向不同的表,是不是很简单?
另外,对于一个项目来说,能做到一个Model对应多个表还不够,因为在实际情况下,你是无法预知会有多少表的,即便你已经知道这些表对应的Model只有一个,随着业务的开展,表也在增加。那怎么解决这个问题呢?有了表对应的Model,那用什么方式来动态增加表呢?目前最常用的就是CodeFirst的方式,还好最新版的PDF.NET已经开始支持CodeFirst的方式,不过,我要用的时候发现还不能支持Postgresql的CodeFirst方式,主要问题是主键的自增,大家都知道,Postgresql并不像SQL Server那样原生支持自增主键,要实现Postgresql的自增主键一般是借助于序列,在数据库中新建一个序列,然后自增主键取值于这个序列,思路比较清晰,直接动手改源码
/// <summary> /// 获取创建表的命令脚本 /// </summary> public string CreateTableCommand { get { if (_createTableCommand == null) { string script = @" CREATE TABLE @TABLENAME( @FIELDS ) "; if (this.currDb.CurrentDBMSType == PWMIS.Common.DBMSType.PostgreSQL && !string.IsNullOrEmpty(currEntity.IdentityName)) { string seq = "CREATE SEQUENCE " + currEntity.TableName + "_" + currEntity.IdentityName + "_" + "seq INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;"; script = seq + script; } var entityFields = EntityFieldsCache.Item(this.currEntity.GetType()); string fieldsText = ""; foreach (string field in this.currEntity.PropertyNames) { string columnScript =entityFields.CreateTableColumnScript(this.currDb as AdoHelper, this.currEntity, field); fieldsText = fieldsText + "," + columnScript+"\r\n"; } string tableName =this.currDb.GetPreparedSQL("["+ currTableName+"]"); _createTableCommand = script.Replace("@TABLENAME", tableName).Replace("@FIELDS", fieldsText.Substring(1)); } return _createTableCommand; } }
我在建表之前,先新建一个序列,新建的表的自增主键引用这个序列即可。
在修改源码的过程中,我发现了一个问题,如果实体中字段的类型为String,它在表中可能对应char,varchar或者text,怎么解决这个问题呢?思考无果后,我想到EF中对这个的支持很好,那EF中是怎么解决这个问题的呢,翻了半天代码,终于找到了相应的源码,贴出来看看:
// Npgsql.NpgsqlMigrationSqlGenerator private void AppendColumnType(ColumnModel column, StringBuilder sql, bool setSerial) { switch (column.Type) { case PrimitiveTypeKind.Binary: sql.Append("bytea"); return; case PrimitiveTypeKind.Boolean: sql.Append("boolean"); return; case PrimitiveTypeKind.Byte: case PrimitiveTypeKind.SByte: case PrimitiveTypeKind.Int16: if (setSerial) { sql.Append(column.IsIdentity ? "serial2" : "int2"); return; } sql.Append("int2"); return; case PrimitiveTypeKind.DateTime: { byte? precision = column.Precision; if ((precision.HasValue ? new int?((int)precision.GetValueOrDefault()) : null).HasValue) { sql.Append("timestamp(" + column.Precision + ")"); return; } sql.Append("timestamp"); return; } case PrimitiveTypeKind.Decimal: { byte? precision2 = column.Precision; if (!(precision2.HasValue ? new int?((int)precision2.GetValueOrDefault()) : null).HasValue) { byte? scale = column.Scale; if (!(scale.HasValue ? new int?((int)scale.GetValueOrDefault()) : null).HasValue) { sql.Append("numeric"); return; } } sql.Append("numeric("); sql.Append(column.Precision ?? 19); sql.Append(','); sql.Append(column.Scale ?? 4); sql.Append(')'); return; } case PrimitiveTypeKind.Double: sql.Append("float8"); return; case PrimitiveTypeKind.Guid: sql.Append("uuid"); return; case PrimitiveTypeKind.Single: sql.Append("float4"); return; case PrimitiveTypeKind.Int32: if (setSerial) { sql.Append(column.IsIdentity ? "serial4" : "int4"); return; } sql.Append("int4"); return; case PrimitiveTypeKind.Int64: if (setSerial) { sql.Append(column.IsIdentity ? "serial8" : "int8"); return; } sql.Append("int8"); return; case PrimitiveTypeKind.String: if (column.IsFixedLength.HasValue && column.IsFixedLength.Value && column.MaxLength.HasValue) { sql.AppendFormat("char({0})", column.MaxLength.Value); return; } if (column.MaxLength.HasValue) { sql.AppendFormat("varchar({0})", column.MaxLength); return; } sql.Append("text"); return; case PrimitiveTypeKind.Time: { byte? precision3 = column.Precision; if ((precision3.HasValue ? new int?((int)precision3.GetValueOrDefault()) : null).HasValue) { sql.Append("interval("); sql.Append(column.Precision); sql.Append(')'); return; } sql.Append("interval"); return; } case PrimitiveTypeKind.DateTimeOffset: { byte? precision4 = column.Precision; if ((precision4.HasValue ? new int?((int)precision4.GetValueOrDefault()) : null).HasValue) { sql.Append("timestamptz("); sql.Append(column.Precision); sql.Append(')'); return; } sql.Append("timestamptz"); return; } case PrimitiveTypeKind.Geometry: sql.Append("point"); return; default: throw new ArgumentException("Unhandled column type:" + column.Type); } }
可能看了这么长的一段源码有点头疼,不知道什么意思,没关系,我们只看需要的部分
case PrimitiveTypeKind.String: if (column.IsFixedLength.HasValue && column.IsFixedLength.Value && column.MaxLength.HasValue) { sql.AppendFormat("char({0})", column.MaxLength.Value); return; } if (column.MaxLength.HasValue) { sql.AppendFormat("varchar({0})", column.MaxLength); return; } sql.Append("text"); return;
很明显这一段的功能是区分char,varchar和text,怎么区分的呢?IsFixedLength,MaxLength是不是很熟悉,对了,这就是EF实体类中字段上的元标记,可惜PDF.NET并不支持元标记,思考了半天,只能用一个折中的办法,代码如下:
if (t == typeof(string)) { int length = entity.GetStringFieldSize(field); if (length == -1) //实体类未定义属性字段的长度 { string fieldType = "text"; if (db is SqlServer) //此处要求SqlServer 2005以上,SqlServer2000 不支持 fieldType = "varchar(max)"; temp = temp + "[" + field + "] "+fieldType; } else { temp = temp + "[" + field + "] varchar" + "(" + length + ")"; } }
PDF.NET虽然不支持元标记,但是它支持给字符串类型的字段设置字段最大长度,所以,这里的解决办法就是如果用户设置了字段长度就用varchar(n)的方式建表,如果没有设置就用text或者varcahr(max)建表。
说到这里,PDF.NET不光可以解决我的一个Model对应多个表的问题,还可以解决表的动态增加问题。
开源就是这样,自己动手,丰衣足食!
相关文章推荐
- 由一个问题引发的思考——关于数据库的外键约束
- Mysql等数据库对于版本号类型字符串的比较问题的思考
- 我在思考一个问题:用纯dhtml技术来实现信息交流平台的应用.
- 一个数据库查询的问题
- 一个棘手的问题,在access数据库 做的一个小站上,数据库链接打开出错
- ado.net连接sql server 2000数据库一定要连网(连一个路由也可以)的问题
- 一个数据库批量处理中的事务问题
- Java Object to Data Model Data Type Mapping, java数据类型与数据库数据类型的对应
- 解决showmodeldialog提交重新打开一个页面的问题
- 一个MS SQL2000的数据库连接池问题
- 一个对于js this关键字的问题
- 一个数据库入门问题
- 请教各位高手一个数据库连接问题
- 练习Eclipse RCP遇到的一个问题及思考
- 一个游戏引发的思考(概率问题)
- 关于数据库备份的一个问题??
- 一个数据库的小问题
- 今天用 hbm2ddl 生成数据库脚本时,不明不白地遇到了一个问题又糊里糊涂解决了
- 一个从来不曾注意的问题,在C#语言中,对于字符串变量的赋初值问题!!
- 请各位大虾们帮帮小弟,谢谢!一个关于产品搜索数据库设计思路的问题