zoukankan      html  css  js  c++  java
  • 对于多个数据库表对应一个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+"
    ";
    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对应多个表的问题,还可以解决表的动态增加问题。
         开源就是这样,自己动手,丰衣足食!

  • 相关阅读:
    023-Spring Boot 服务的注册和发现
    022-Spring Boot 构建微服务实战
    021-Spring Boot 测试,Junit方式使用,mock方式,Controller测试
    020-Spring Boot 监控和度量
    003-Spring 中的StreamUtils
    004-微信证书问题
    019-Spring Boot 日志
    018-Spring Boot Starter开发
    017-Spring Boot AOP
    016-Spring Boot JDBC
  • 原文地址:https://www.cnblogs.com/znlgis/p/4312074.html
Copyright © 2011-2022 走看看