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

          /// <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());
}
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 ?? );
sql.Append(',');
sql.Append(column.Scale ?? );
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 == -) //实体类未定义属性字段的长度
{
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对应多个表的问题,还可以解决表的动态增加问题。
     开源就是这样,自己动手,丰衣足食!

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

  1. SqlServer数据库表生成C# Model实体类SQL语句——补充

    在sql语句最前边加上  use[数据库名] 原链接:https://www.cnblogs.com/jhli/p/11552105.html   --[SQL骚操作]SqlServer数据库表生成C ...

  2. 【python】用 sqlacodegen 将存在的数据库表 转化成model.py

    Flask的sqlalchemy对数据库表的模型提供了很多易用的方法.为了使用这些内容,需要将数据库表按照Flask识别的格式创建成Model,但是一般我们都是在已经创建好的数据库环境中开发Pytho ...

  3. 【转发】SqlServer数据库表生成C# Model实体类SQL语句

    已知现有表T1 通过运行下面的sql即可,先配置表名. declare @TableName sysname = 'T1' declare @Result varchar(max) = ' /// & ...

  4. 【SQL骚操作】SqlServer数据库表生成C# Model实体类SQL语句

    已知现有表T1 想快速获取cs类结构 /// <summary> /// T1 /// </summary> public class T1 { /// <summary ...

  5. efcore dbfirst 通过数据库表反向生成model

    创建class library并设置为启动项目 使用nuget控制台,设置当前项目为新建的class library Install-Package Microsoft.EntityFramework ...

  6. 为什么要用hibernate 与基于数据库表结构的项目开发

    最近开始学习hibernate,其实并不知道要学习什么,有什么用.后来问了一下同事,他就说快捷方便简单,很多事情不用自己做他会帮你做好,但是我觉得不应该是这样的,于是我就去搜了一下,就搜到了一篇帖子, ...

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

    Django通过model层不可以创建数据库,但可以创建数据库表,以下是创建表的字段以及表字段的参数.一.字段1.models.AutoField 自增列= int(11) 如果没有的话,默认会生成一 ...

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

    django根据已有数据库表生成model类 创建一个Django项目 django-admin startproject 'xxxx' 修改setting文件,在setting里面设置你要连接的数据 ...

  9. EFCore 通过实体Model生成创建SQL Server数据库表脚本

    在我们的项目中经常采用Model First这种方式先来设计数据库Model,然后通过Migration来生成数据库表结构,有些时候我们需要动态通过实体Model来创建数据库的表结构,特别是在创建像临 ...

随机推荐

  1. spider RPC框架的需求来源与特性介绍(一)

    spider RPC 特性介绍 spider RPC 性能测试 spider RPC 入门指南 spider RPC 配置文件参考 spider RPC 开发指南 spider RPC 安全性 spi ...

  2. win10 下visual studio 2015 在调试模式下不能跟踪源文件

    win10 下visual studio 2015 在调试模式下不能跟踪源文件,只要一调试就会关闭(隐藏)打开的文档,非常不方便.经过一番折腾,发现是配置的问题. 如果安装多个版本的VS,请删除对应版 ...

  3. iframe关于滚动条的去除和保留

    iframe嵌入页面后,我们有时需要调整滚动条,例如,去掉全部的滚动条,去掉右边的滚动条且保留底下的滚动条,去掉底下的滚动条且保留右边的滚动条.那么我们应该怎么做呢? 一:去掉全部的滚动条 第一个方法 ...

  4. Three.js制作360度全景图

    这是个基于three.js的插件,预览地址:戳这里 使用方法: 1.这个插件的用法很简单,引入如下2个js <script src="js/three.min.js"> ...

  5. 【前端优化之渲染优化】大屏android手机动画丢帧的背后

    前言 上周我与阿里的宇果有一次技术的交流,然后对天猫H5站点做了一些浅层次的分析,后面点时间基本天天都会有联系,中途聊了一些技术细节.聊了双方团队在干什么,最后聊到了前端优化.因为我本身参与了几次携程 ...

  6. 移动端报表JS开发示例

    最近对移动端的报表开发颇有研究,细磨精算了好久,虽然到现在还是“囊中羞涩”,但决定还是先抛砖引玉,拿点小干货出来和大家分享. 研究的工具是比较有代表性的FineReport. 1.  移动端哪些地方支 ...

  7. SAP CRM 7.0中的BOL(Business Object Layer)

    业务对象层(BOL)和通用交互层(GenIL)属于业务层. 业务对象层:   在CRM WebClient会话运行期间,业务对象层存储业务对象的数据以及它们属性和关系的定义. 通用交互层 通用交互层将 ...

  8. Smart Tag——DevExpress WPF初探

    Smart Tag是一个设计时扩展,所有标准控件均自带这个功能,当然也包括 DevExpress WPF Controls .可以快速设置控件的值或者绑定最重要的属性.它还可以帮助你完成一些重复的工作 ...

  9. React Native 之 Touchable 介绍与使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  10. android 之 启动画面的两种方法

    现在,当我们打开任意的一个app时,其中的大部分都会显示一个启动界面,展示本公司的logo和当前的版本,有的则直接把广告放到了上面.启动画面的可以分为两种设置方式:一种是两个Activity实现,和一 ...