對於多個數據庫表對應一個Model問題的思考

     最近作項目遇到一個場景,就是客戶要求爲其下屬的每個分支機構建一個表存儲相關數據,而這些表的結構都是同樣的,只是分屬於不一樣的機構。這個問題抽象一下就是多個數據庫表對應一個Model(或者叫實體類)。有了這個問題,我就開始思考在現有的代碼中解決問題,最先數據採集部分是用EF來作數據存儲的,我查了一下,資料並很少,問了一下對EF比較熟悉的朋友,得出的結論是EF實現這個功能比較複雜,不易實現。EF不能實現就要去找其餘的框架,在PDF.NET的討論羣跟你們討論這個問題的時候,@深藍醫生說PDF.NET能夠支持這個,在醫生的指導下,我研究了PDF.NET的源碼,確實能夠實現這個功能。在PDF.NET的源碼中,有一個EntityBase的類,這是全部實體的基礎類,該類裏面有如下兩個方法:sql

 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 }

     可能看了這麼長的一段源碼有點頭疼,不知道什麼意思,不要緊,咱們只看須要的部分ui

 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並不支持元標記,思考了半天,只能用一個折中的辦法,代碼以下:this

 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對應多個表的問題,還能夠解決表的動態增長問題。
     開源就是這樣,本身動手,豐衣足食!spa

相關文章
相關標籤/搜索