对于多个数据库表对应一个Model问题的思考
最近做项目遇到一个场景,就是客户要求为其下属的每一个分支机构建一个表存储相关数据,而这些表的结构都是一样的,只是分属于不同的机构。这个问题抽象一下就是多个数据库表对应一个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问题的思考的更多相关文章
- SqlServer数据库表生成C# Model实体类SQL语句——补充
在sql语句最前边加上 use[数据库名] 原链接:https://www.cnblogs.com/jhli/p/11552105.html --[SQL骚操作]SqlServer数据库表生成C ...
- 【python】用 sqlacodegen 将存在的数据库表 转化成model.py
Flask的sqlalchemy对数据库表的模型提供了很多易用的方法.为了使用这些内容,需要将数据库表按照Flask识别的格式创建成Model,但是一般我们都是在已经创建好的数据库环境中开发Pytho ...
- 【转发】SqlServer数据库表生成C# Model实体类SQL语句
已知现有表T1 通过运行下面的sql即可,先配置表名. declare @TableName sysname = 'T1' declare @Result varchar(max) = ' /// & ...
- 【SQL骚操作】SqlServer数据库表生成C# Model实体类SQL语句
已知现有表T1 想快速获取cs类结构 /// <summary> /// T1 /// </summary> public class T1 { /// <summary ...
- efcore dbfirst 通过数据库表反向生成model
创建class library并设置为启动项目 使用nuget控制台,设置当前项目为新建的class library Install-Package Microsoft.EntityFramework ...
- 为什么要用hibernate 与基于数据库表结构的项目开发
最近开始学习hibernate,其实并不知道要学习什么,有什么用.后来问了一下同事,他就说快捷方便简单,很多事情不用自己做他会帮你做好,但是我觉得不应该是这样的,于是我就去搜了一下,就搜到了一篇帖子, ...
- django使用model创建数据库表使用的字段
Django通过model层不可以创建数据库,但可以创建数据库表,以下是创建表的字段以及表字段的参数.一.字段1.models.AutoField 自增列= int(11) 如果没有的话,默认会生成一 ...
- django根据已有数据库表生成model类
django根据已有数据库表生成model类 创建一个Django项目 django-admin startproject 'xxxx' 修改setting文件,在setting里面设置你要连接的数据 ...
- EFCore 通过实体Model生成创建SQL Server数据库表脚本
在我们的项目中经常采用Model First这种方式先来设计数据库Model,然后通过Migration来生成数据库表结构,有些时候我们需要动态通过实体Model来创建数据库的表结构,特别是在创建像临 ...
随机推荐
- Spring下ActiveMQ实战
MessageQueue是分布式的系统里经常要用到的组件,一般来说,当需要把消息跨网段.跨集群的分发出去,就可以用这个.一些典型的示例就是: 1.集群A中的消息需要发送给多个机器共享: 2.集群A中消 ...
- 《连载 | 物联网框架ServerSuperIO教程》- 12.服务接口的开发,以及与云端双向交互
1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...
- activiti工作流的web流程设计器整合视频教程 SSM 和 独立部署
本视频为activiti工作流的web流程设计器整合视频教程 整合Acitiviti在线流程设计器(Activiti-Modeler 5.21.0 官方流程设计器) 本视频共讲了两种整合方式 1. 流 ...
- Java web.xml 配置详解
在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是 ...
- java web学习总结(二十五) -------------------JSP中的九个内置对象
一.JSP运行原理 每个JSP 页面在第一次被访问时,WEB容器都会把请求交给JSP引擎(即一个Java程序)去处理.JSP引擎先将JSP翻译成一个_jspServlet(实质上也是一个servlet ...
- 兼容javascript和C#的RSA加密解密算法,对web提交的数据进行加密传输
Web应用中往往涉及到敏感的数据,由于HTTP协议以明文的形式与服务器进行交互,因此可以通过截获请求的数据包进行分析来盗取有用的信息.虽然https可以对传输的数据进行加密,但是必须要申请证书(一般都 ...
- 浅谈Hybrid技术的设计与实现第二弹
前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...
- @property中的copy.strong.weak总结
1.NSString类型的属性为什么用copy NSString类型的属性可以用strong修饰,但会造成一些问题,请看下面代码 #import "ViewController.h" ...
- iOS:GCD组
组内异步会与组外顺序执行的事件争抢资源 1).创建一个组 dispatch_group_t group = dispatch_group_create(); 2).组内异步ST1,DISPATCH_Q ...
- Linux2.6内核协议栈系列--TCP协议2.接收
1.排队机制 接收输入TCP报文时,有三个队列: ● 待处理队列 ● 预排队队列 ● 接收队列 接收队列包含了处理过的TCP数据段,也就是说,去除了全部的协议头,正准备将数据复制到用户应用程序.接收队 ...