对于多个数据库表对应一个Model问题的思考

最近做项目遇到一个场景,就是客户要求为其下属的每一个分支机构建一个表存储相关数据,而这些表的结构都是一样的,只是分属于不同的机构。这个问题抽象一下就是多个数据库表对应一个Model(或者叫实体类)。有了这个问题,我就开始思考在现有的代码中解决问题,最早数据采集部分是用EF来做数据存储的,我查了一下,资料并不多,问了一下对EF比较熟悉的朋友,得出的结论是EF实现这个功能比较复杂,不易实现。EF不能实现就要去找其他的框架,在PDF.NET的讨论群跟大家讨论这个问题的时候,@深蓝医生说PDF.NET可以支持这个,在医生的指导下,我研究了PDF.NET的源码,确实可以实现这个功能。在PDF.NET的源码中,有一个EntityBase的类,这是所有实体的基础类,该类里面有以下两个方法:

 1          /// <summary>
 2         /// 将实体类的表名称映射到一个新的表名称
 3         /// </summary>
 4         /// <param name="newTableName">新的表名称</param>
 5         /// <returns>是否成功</returns>
 6         public bool MapNewTableName(string newTableName)
 7         {
 8             if (EntityMap == EntityMapType.Table)
 9             {
10                 this.TableName = newTableName;
11                 return true;
12             }
13             return false;
14         }
15
16         /// <summary>
17         /// 获取表名称。如果实体类有分表策略,那么请重写该方法
18         /// </summary>
19         /// <returns></returns>
20         public virtual string GetTableName()
21         {
22             return _tableName; ;
23         }

看到这两个方法,大家应该就基本明白了,有了这两个方法就可以很方便的根据需要将同一个实体也就是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的自增主键一般是借助于序列,在数据库中新建一个序列,然后自增主键取值于这个序列,思路比较清晰,直接动手改源码

 1 /// <summary>
 2         /// 获取创建表的命令脚本
 3         /// </summary>
 4         public string CreateTableCommand
 5         {
 6             get {
 7                 if (_createTableCommand == null)
 8                 {
 9                     string script = @"
10 CREATE TABLE @TABLENAME(
11 @FIELDS
12 )
13                     ";
14
15                     if (this.currDb.CurrentDBMSType == PWMIS.Common.DBMSType.PostgreSQL && !string.IsNullOrEmpty(currEntity.IdentityName))
16                     {
17                         string seq =
18                             "CREATE SEQUENCE " + currEntity.TableName + "_" + currEntity.IdentityName + "_" + "seq INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;";
19
20                         script = seq + script;
21                     }
22
23                     var entityFields = EntityFieldsCache.Item(this.currEntity.GetType());
24                     string fieldsText = "";
25                     foreach (string field in this.currEntity.PropertyNames)
26                     {
27                         string columnScript =entityFields.CreateTableColumnScript(this.currDb as AdoHelper, this.currEntity, field);
28                         fieldsText = fieldsText + "," + columnScript+"\r\n";
29                     }
30                     string tableName =this.currDb.GetPreparedSQL("["+ currTableName+"]");
31                     _createTableCommand = script.Replace("@TABLENAME", tableName).Replace("@FIELDS", fieldsText.Substring(1));
32                 }
33                 return _createTableCommand;
34             }
35         }

我在建表之前,先新建一个序列,新建的表的自增主键引用这个序列即可。
     在修改源码的过程中,我发现了一个问题,如果实体中字段的类型为String,它在表中可能对应char,varchar或者text,怎么解决这个问题呢?思考无果后,我想到EF中对这个的支持很好,那EF中是怎么解决这个问题的呢,翻了半天代码,终于找到了相应的源码,贴出来看看:

  1 // Npgsql.NpgsqlMigrationSqlGenerator
  2 private void AppendColumnType(ColumnModel column, StringBuilder sql, bool setSerial)
  3 {
  4     switch (column.Type)
  5     {
  6     case PrimitiveTypeKind.Binary:
  7         sql.Append("bytea");
  8         return;
  9     case PrimitiveTypeKind.Boolean:
 10         sql.Append("boolean");
 11         return;
 12     case PrimitiveTypeKind.Byte:
 13     case PrimitiveTypeKind.SByte:
 14     case PrimitiveTypeKind.Int16:
 15         if (setSerial)
 16         {
 17             sql.Append(column.IsIdentity ? "serial2" : "int2");
 18             return;
 19         }
 20         sql.Append("int2");
 21         return;
 22     case PrimitiveTypeKind.DateTime:
 23     {
 24         byte? precision = column.Precision;
 25         if ((precision.HasValue ? new int?((int)precision.GetValueOrDefault()) : null).HasValue)
 26         {
 27             sql.Append("timestamp(" + column.Precision + ")");
 28             return;
 29         }
 30         sql.Append("timestamp");
 31         return;
 32     }
 33     case PrimitiveTypeKind.Decimal:
 34     {
 35         byte? precision2 = column.Precision;
 36         if (!(precision2.HasValue ? new int?((int)precision2.GetValueOrDefault()) : null).HasValue)
 37         {
 38             byte? scale = column.Scale;
 39             if (!(scale.HasValue ? new int?((int)scale.GetValueOrDefault()) : null).HasValue)
 40             {
 41                 sql.Append("numeric");
 42                 return;
 43             }
 44         }
 45         sql.Append("numeric(");
 46         sql.Append(column.Precision ?? 19);
 47         sql.Append(‘,‘);
 48         sql.Append(column.Scale ?? 4);
 49         sql.Append(‘)‘);
 50         return;
 51     }
 52     case PrimitiveTypeKind.Double:
 53         sql.Append("float8");
 54         return;
 55     case PrimitiveTypeKind.Guid:
 56         sql.Append("uuid");
 57         return;
 58     case PrimitiveTypeKind.Single:
 59         sql.Append("float4");
 60         return;
 61     case PrimitiveTypeKind.Int32:
 62         if (setSerial)
 63         {
 64             sql.Append(column.IsIdentity ? "serial4" : "int4");
 65             return;
 66         }
 67         sql.Append("int4");
 68         return;
 69     case PrimitiveTypeKind.Int64:
 70         if (setSerial)
 71         {
 72             sql.Append(column.IsIdentity ? "serial8" : "int8");
 73             return;
 74         }
 75         sql.Append("int8");
 76         return;
 77     case PrimitiveTypeKind.String:
 78         if (column.IsFixedLength.HasValue && column.IsFixedLength.Value && column.MaxLength.HasValue)
 79         {
 80             sql.AppendFormat("char({0})", column.MaxLength.Value);
 81             return;
 82         }
 83         if (column.MaxLength.HasValue)
 84         {
 85             sql.AppendFormat("varchar({0})", column.MaxLength);
 86             return;
 87         }
 88         sql.Append("text");
 89         return;
 90     case PrimitiveTypeKind.Time:
 91     {
 92         byte? precision3 = column.Precision;
 93         if ((precision3.HasValue ? new int?((int)precision3.GetValueOrDefault()) : null).HasValue)
 94         {
 95             sql.Append("interval(");
 96             sql.Append(column.Precision);
 97             sql.Append(‘)‘);
 98             return;
 99         }
100         sql.Append("interval");
101         return;
102     }
103     case PrimitiveTypeKind.DateTimeOffset:
104     {
105         byte? precision4 = column.Precision;
106         if ((precision4.HasValue ? new int?((int)precision4.GetValueOrDefault()) : null).HasValue)
107         {
108             sql.Append("timestamptz(");
109             sql.Append(column.Precision);
110             sql.Append(‘)‘);
111             return;
112         }
113         sql.Append("timestamptz");
114         return;
115     }
116     case PrimitiveTypeKind.Geometry:
117         sql.Append("point");
118         return;
119     default:
120         throw new ArgumentException("Unhandled column type:" + column.Type);
121     }
122 }

可能看了这么长的一段源码有点头疼,不知道什么意思,没关系,我们只看需要的部分

 1                 case PrimitiveTypeKind.String:
 2         if (column.IsFixedLength.HasValue && column.IsFixedLength.Value && column.MaxLength.HasValue)
 3         {
 4             sql.AppendFormat("char({0})", column.MaxLength.Value);
 5             return;
 6         }
 7         if (column.MaxLength.HasValue)
 8         {
 9             sql.AppendFormat("varchar({0})", column.MaxLength);
10             return;
11         }
12         sql.Append("text");
13         return;

很明显这一段的功能是区分char,varchar和text,怎么区分的呢?IsFixedLength,MaxLength是不是很熟悉,对了,这就是EF实体类中字段上的元标记,可惜PDF.NET并不支持元标记,思考了半天,只能用一个折中的办法,代码如下:

 1             if (t == typeof(string))
 2             {
 3                 int length = entity.GetStringFieldSize(field);
 4                 if (length == -1) //实体类未定义属性字段的长度
 5                 {
 6                     string fieldType = "text";
 7                     if (db is SqlServer) //此处要求SqlServer 2005以上,SqlServer2000 不支持
 8                         fieldType = "varchar(max)";
 9                     temp = temp + "[" + field + "] "+fieldType;
10                 }
11                 else
12                 {
13                     temp = temp + "[" + field + "] varchar" + "(" + length + ")";
14                 }
15             }

PDF.NET虽然不支持元标记,但是它支持给字符串类型的字段设置字段最大长度,所以,这里的解决办法就是如果用户设置了字段长度就用varchar(n)的方式建表,如果没有设置就用text或者varcahr(max)建表。
     说到这里,PDF.NET不光可以解决我的一个Model对应多个表的问题,还可以解决表的动态增加问题。
     开源就是这样,自己动手,丰衣足食!

时间: 2024-10-24 01:18:58

对于多个数据库表对应一个Model问题的思考的相关文章

数据库表的命名规范

数据库表的命名规范 数据库 命名规范 数据文件 数据库表 只有一个系统 有多个系统 关联表名 数据库字段命名规范 其它 视图名 存储过程名 SQL语句 原文链接 数据文件 数据文件命名采用系统名_文件类型,比如系统名为kupage,则数据库文件命名为kupage_database.mdf,有的数据库文件有多个,比如SQL Server就有2个,一个是数据库文件,另一个是日志文件,那么他们的文件命名分别为kupage_database.mdf,kupage_log.log.文件名全部采用小写. 总

关系型数据库表结构的两个设计技巧

By良少http://blog.csdn.net/shendl 关系型数据库表结构的设计,有下面两个设计技巧: 物理主键作为关联的外键 关系型数据库,由多个数据表构成.每一个数据表的结构是相同的,不同表之间可能存在关联关系.表之间的关联关系,正是关系型数据库得名的原因. 一个表由多个字段构成.其中可能有多个字段适合作为主键.主键字段,就是表中每一行都不会有重复数据的字段. 主键,可以分为两种:物理主键和逻辑主键. 每一张数据库的表,都使用自增长的id字段作为物理主键. 多表之间的外键关联,都关联

关于数据库表前缀的认识

zblogPHP默认安装时,使用的是mysql数据库,负载能力更强! 那么 1.配置的时候,如何设置数据库表前缀呢? 2.两个.多个zblogPHP如何安装到同一个数据库? 下方回答下这个疑问: 在安装mysql时候会显示配置页面,如下图: 四个剪头分别为: 数据库用户名.数据库密码.数据库名.数据库表前缀! 最后一个大红箭头的就是安装zblogPHP默认时的前缀了,默认一律为:zbp_ 一般情况下如果一个mysql数据库只安装了一个zblogPHP,此时数据库表前缀使用默认即可,但如果如上图所

django使用model创建数据库表使用的字段

Django通过model层不可以创建数据库,但可以创建数据库表,以下是创建表的字段以及表字段的参数.一.字段1.models.AutoField 自增列= int(11) 如果没有的话,默认会生成一个名称为 id 的列,如果要显示的自定义一个自增列,必须将给列设置为主键 primary_key=True.2.models.CharField 字符串字段 必须 max_length 参数3.models.BooleanField 布尔类型=tinyint(1) 不能为空,Blank=True4.

model一定是和数据库表对应的么?

Model既不绝对对应数据表,也不需要对应前端视图,Model对应的是“数据模型”这个抽象的概念 —— 状态和操作. 数据表是Model的存储实现,存储实现不一定只有数据表,可能用不同存储系统存放 数据分片,Model的作用就是掩盖这个底层细节,给上层代码一个统一稳定的接口. 前端视图需要Model的状态,但并不直接操作Model,这是Controller或者View做的事,所以本身并不直接决定Model的 设计. Model的设计表面上同时对应数据表和前端视图,严格地说两者都不是Model设计

django根据已有数据库表生成model类

django根据已有数据库表生成model类 创建一个Django项目 django-admin startproject 'xxxx' 修改setting文件,在setting里面设置你要连接的数据库类型和连接名称,地址之类,和创建新项目的时候一致 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'sqlexam', 'USER': 'root', 'PASSWORD': 'root123456'

Django-根据数据库表生成 model 类

根据数据库表生成 model 类 创建一个Django项目 django-admin startproject 'xxxx' 修改setting文件,在setting里面设置你要连接的数据库类型和连接名称,地址之类,和创建新项目的时候一致 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'sqlexam', 'USER': 'root', 'PASSWORD': 'root123456', 'HOS

定时从一个数据库表中的数据存储到另外一个数据库中的表,而且怎么处理重复的数据?

原文:http://www.iteye.com/problems/77856 定时从一个数据库表中的数据存储到另外一个数据库中的表,而且怎么处理重复的数据? 表结构肯定是不能破坏,但是临时表如果是自己的数据库还行,问题是这个Oracle数据库是客户的数据库呢,你不能在他的数据库做任何多余的操作吧?还有别的更好的方法吗? 这个真的是比较困难. 首先,你要从客户机oracle取数据,因为这1分钟间隔之内不知道用户机新增加了哪些数据(大部分情况下是用户使用别的系统插入数据,而你又没有这个系统的程序接口

sql server 复制表从一个数据库到另一个数据库

sql server 复制表从一个数据库到另一个数据库 /*不同服务器数据库之间的数据操作*/ --创建链接服务器 exec sp_addlinkedserver 'ITSV ', ' ', 'SQLOLEDB ', '远程服务器名或ip地址 ' exec sp_addlinkedsrvlogin 'ITSV ', 'false ',null, '用户名 ', '密码 ' --查询示例 select * from ITSV.数据库名.dbo.表名 --导入示例 select * into 表 f