经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
CYQ.Data 支持 PostgreSQL 数据库
来源:cnblogs  作者:路过秋天  时间:2018/12/10 9:50:56  对本文有异议

前言:

很久之前,就有同学问我CYQ.Data能不能支持下PostgreSQL,之后小做了下调查,发现这个数据库用的人少,加上各种因素,就一直没动手。

前两天,不小心看了一下Github上的消息:

看到这个问题又被重新提了出来了,于是,闹吧!

下面分享一下支持该数据库要处理的过程,让大伙明白CYQ.Data要支持一种新的数据库,需要花多少功夫。

1、找到数据库的驱动程序:Npgsql.dll

网上查找了点相关知识,发现.NET 里操作PostgreSQL有两种提供的dll,一种是正规的收费的,另一种是开源的Npgsql.dll,因此这里选择了开源的。

在Nuget上可以搜索Npgsql,不过上面的版本要求依赖的版本很高,于是我找了最早的版本开始支持,毕竟CYQ.Data 是从支持最低2.0及以上的。

这里是找到的下载低版本支持的网址:http://pgfoundry.org/frs/?group_id=1000140&release_id=1889

同时,下载的两个2.0和4.0两个版本,也一并上传到:https://github.com/cyq1162/cyqdata/tree/master/文档

2、创建PostgreDal.cs,实现动态加载DLL

添加动态加载的代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Reflection;
  5. using System.Data.Common;
  6. using CYQ.Data.Cache;
  7. using System.IO;
  8. namespace CYQ.Data
  9. {
  10. internal class PostgreDal : DbBase
  11. {
  12. public PostgreDal(ConnObject co)
  13. : base(co)
  14. {
  15. }
  16. internal static Assembly GetAssembly()
  17. {
  18. object ass = CacheManage.LocalInstance.Get("Postgre_Assembly");
  19. if (ass == null)
  20. {
  21. try
  22. {
  23. string name = string.Empty;
  24. if (File.Exists(AppConst.RunFolderPath + "Npgsql.dll"))
  25. {
  26. name = "Npgsql";
  27. }
  28. else
  29. {
  30. name = "Can't find the Npgsql.dll";
  31. Error.Throw(name);
  32. }
  33. ass = Assembly.Load(name);
  34. CacheManage.LocalInstance.Set("Postgre_Assembly", ass, 10080);
  35. }
  36. catch (Exception err)
  37. {
  38. string errMsg = err.Message;
  39. Error.Throw(errMsg);
  40. }
  41. }
  42. return ass as Assembly;
  43. }
  44. protected override DbProviderFactory GetFactory(string providerName)
  45. {
  46. object factory = CacheManage.LocalInstance.Get("Postgre_Factory");
  47. if (factory == null)
  48. {
  49. Assembly ass = GetAssembly();
  50. factory = ass.GetType("Npgsql.NpgsqlFactory").GetField("Instance").GetValue(null);
  51. // factory = ass.CreateInstance("Npgsql.NpgsqlFactory.Instance");
  52. if (factory == null)
  53. {
  54. throw new System.Exception("Can't Create NpgsqlFactory in Npgsql.dll");
  55. }
  56. else
  57. {
  58. CacheManage.LocalInstance.Set("Postgre_Factory", factory, 10080);
  59. }
  60. }
  61. return factory as DbProviderFactory;
  62. }
  63. protected override bool IsExistsDbName(string dbName)
  64. {
  65. try
  66. {
  67. IsAllowRecordSql = false;
  68. bool result = ExeScalar("select 1 from pg_catalog.pg_database where datname='" + dbName + "'", false) != null;
  69. IsAllowRecordSql = true;
  70. return result;
  71. }
  72. catch
  73. {
  74. return true;
  75. }
  76. }
  77. public override char Pre
  78. {
  79. get
  80. {
  81. return ':';
  82. }
  83. }
  84. public override void AddReturnPara()
  85. {
  86. }
  87. }
  88. }

几点说明:

  1. 1GetFactory方法,其它dll框架提供的都是直接实例化,而Npgsql.dll提供却是单例属性,所以代码有点变化。
  2. 2Npgsql操作参数化的符号是“:”号。

3、DalCreate.cs追加PostgreSql类型及数据库链接解析

 

这里重点发现postgresql和mssql两者的数据库链接格式都一致:

  1. server=...;uid=xxx;pwd=xxx;database=xxx;

因此从单纯的语句上,根本无法判断从属于哪种数据库。

经过小小的思考,解决方案出来了:

  1. else
  2. {
  3. //postgre和mssql的链接语句一样,这里用database=和uid=顺序来决定;database写在后面的,为postgre
  4. int dbIndex = connString.IndexOf("database=", StringComparison.OrdinalIgnoreCase);
  5. int uid = connString.IndexOf("uid=", StringComparison.OrdinalIgnoreCase);
  6. if (uid > 0 && uid < dbIndex && File.Exists(AppConfig.RunPath + "Npgsql.dll"))
  7. {
  8. return PostgreClient;
  9. }
  10. return SqlClient;
  11. }

简的说:只有满足引用了npgsql.dll以及database写在uid之后两种条件下,判断为postgresql,其它的都回归到mssql。

4、处理表结构语句:获取数据库表以及表的结构语句:

这一块花的时间比较多,网上也费了点时间查了不少资料,最后自己写了语句:

获取数据库所有表:

  1. internal static string GetPostgreTables(string dbName)
  2. {
  3. return string.Format("select table_name as TableName,cast(obj_description(relfilenode,'pg_class') as varchar) as Description from information_schema.tables t left join pg_class p on t.table_name=p.relname where table_schema='public' and table_catalog='{0}'", dbName);
  4. }

获取某表的结构:

  1. internal static string GetPostgreColumns()
  2. {
  3. return @"select
  4. a.attname AS ColumnName,
  5. case t.typname when 'int4' then 'int' when 'int8' then 'bigint' else t.typname end AS SqlType,
  6. coalesce(character_maximum_length,numeric_precision,-1) as MaxSize,numeric_scale as Scale,
  7. case a.attnotnull when 'true' then 0 else 1 end AS IsNullable,
  8. case when position('nextval' in column_default)>0 then 1 else 0 end as IsAutoIncrement,
  9. case when o.conname is null then 0 else 1 end as IsPrimaryKey,
  10. d.description AS Description,
  11. i.column_default as DefaultValue
  12. from pg_class c
  13. left join pg_attribute a on c.oid=a.attrelid
  14. left join pg_description d on a.attrelid=d.objoid AND a.attnum = d.objsubid
  15. left join pg_type t on a.atttypid = t.oid
  16. left join information_schema.columns i on i.table_schema='public' and i.table_name=c.relname and i.column_name=a.attname
  17. left join pg_constraint o on a.attnum = o.conkey[1] and o.contype='p'
  18. where c.relname =:TableName
  19. and a.attnum > 0 and a.atttypid>0
  20. ORDER BY a.attnum";
  21. }

5、处理关键字符号

由于PostgreSQL的大小写敏感,而且关键字加需要用双引号包含(这点和SQLite一致):

这里在原有的基础上加上case即可。

6、处理差异化的SQL语句:SqlCreate.cs

A、获取插入后的自增值,这里可以借用一下自增列产生的默认值:

这里用默认值,替换一下nextval序列为currval序列即可。

  1. else if (_action.dalHelper.dalType == DalType.PostgreSQL)
  2. {
  3. string key = Convert.ToString(primaryCell.Struct.DefaultValue);
  4. if (!string.IsNullOrEmpty(key))
  5. {
  6. key = key.Replace("nextval", "currval");
  7. sql = sql + "; select " + key + " as OutPutValue";
  8. }
  9. }

 B、需要引用关键字的地方:

略。。。。

7、处理分页语句:SqlCreateForPager.cs

这里PostgreSQL和分页和sqlite及mysql是一致的,因此只要在相关的地方补上case即可:

  1. public static string GetSql(DalType dalType, string version, int pageIndex, int pageSize, object objWhere, string tableName, int rowCount, string columns, string primaryKey, bool primaryKeyIsIdentity)
  2. {
  3. if (string.IsNullOrEmpty(columns))
  4. {
  5. columns = "*";
  6. }
  7. pageIndex = pageIndex == 0 ? 1 : pageIndex;
  8. string where = SqlFormat.GetIFieldSql(objWhere);
  9. if (string.IsNullOrEmpty(where))
  10. {
  11. where = "1=1";
  12. }
  13. if (pageSize == 0)
  14. {
  15. return string.Format(top1Pager, columns, tableName, where);
  16. }
  17. if (rowCount > 0)//分页查询。
  18. {
  19. where = SqlCreate.AddOrderBy(where, primaryKey);
  20. }
  21. int topN = pageIndex * pageSize;//Top N 最大数
  22. int max = (pageIndex - 1) * pageSize;
  23. int rowStart = (pageIndex - 1) * pageSize + 1;
  24. int rowEnd = rowStart + pageSize - 1;
  25. string orderBy = string.Empty;
  26. if (pageIndex == 1 && dalType != DalType.Oracle)//第一页(oracle时 rownum 在排序条件为非数字时,和row_number()的不一样,会导致结果差异,所以分页统一用row_number()。)
  27. {
  28. switch (dalType)
  29. {
  30. case DalType.Access:
  31. case DalType.MsSql:
  32. case DalType.Sybase:
  33. return string.Format(top1Pager, "top " + pageSize + " " + columns, tableName, where);
  34. //case DalType.Oracle:
  35. // return string.Format(top1Pager, columns, tableName, "rownum<=" + pageSize + " and " + where);
  36. case DalType.SQLite:
  37. case DalType.MySql:
  38. case DalType.PostgreSQL:
  39. return string.Format(top1Pager, columns, tableName, where + " limit " + pageSize);
  40. }
  41. }
  42. else
  43. {
  44. switch (dalType)
  45. {
  46. case DalType.Access:
  47. case DalType.MsSql:
  48. case DalType.Sybase:
  49. int leftNum = rowCount % pageSize;
  50. int pageCount = leftNum == 0 ? rowCount / pageSize : rowCount / pageSize + 1;//页数
  51. if (pageIndex == pageCount && dalType != DalType.Sybase) // 最后一页Sybase 不支持双Top order by
  52. {
  53. return string.Format(top2Pager, pageSize+" "+columns, "top " + (leftNum == 0 ? pageSize : leftNum) + " * ", tableName, ReverseOrderBy(where, primaryKey), GetOrderBy(where, false, primaryKey));//反序
  54. }
  55. if ((pageCount > 1000 || rowCount > 100000) && pageIndex > pageCount / 2) // 页数过后半段,反转查询
  56. {
  57. orderBy = GetOrderBy(where, false, primaryKey);
  58. where = ReverseOrderBy(where, primaryKey);//事先反转一次。
  59. topN = rowCount - max;//取后面的
  60. int rowStartTemp = rowCount - rowEnd;
  61. rowEnd = rowCount - rowStart;
  62. rowStart = rowStartTemp;
  63. }
  64. break;
  65. }
  66. }
  67. switch (dalType)
  68. {
  69. case DalType.MsSql:
  70. case DalType.Oracle:
  71. if (version.StartsWith("08"))
  72. {
  73. goto temtable;
  74. // goto top3;//sql 2000
  75. }
  76. int index = tableName.LastIndexOf(')');
  77. if (index > 0)
  78. {
  79. tableName = tableName.Substring(0, index + 1);
  80. }
  81. string v = dalType == DalType.Oracle ? "" : " v";
  82. string onlyWhere = "where " + SqlCreate.RemoveOrderBy(where);
  83. onlyWhere = SqlFormat.RemoveWhereOneEqualsOne(onlyWhere);
  84. return string.Format(rowNumberPager, GetOrderBy(where, false, primaryKey), (columns == "*" ? "t.*" : columns), tableName, onlyWhere, v, rowStart, rowEnd);
  85. case DalType.Sybase:
  86. temtable:
  87. if (primaryKeyIsIdentity)
  88. {
  89. bool isOk = columns == "*";
  90. if (!isOk)
  91. {
  92. string kv = SqlFormat.NotKeyword(primaryKey);
  93. string[] items = columns.Split(',');
  94. foreach (string item in items)
  95. {
  96. if (string.Compare(SqlFormat.NotKeyword(item), kv, StringComparison.OrdinalIgnoreCase) == 0)
  97. {
  98. isOk = true;
  99. break;
  100. }
  101. }
  102. }
  103. else
  104. {
  105. columns = "t.*";
  106. index = tableName.LastIndexOf(')');
  107. if (index > 0)
  108. {
  109. tableName = tableName.Substring(0, index + 1);
  110. }
  111. tableName += " t ";
  112. }
  113. if (isOk)
  114. {
  115. return string.Format(tempTablePagerWithIdentity, DateTime.Now.Millisecond, topN, primaryKey, tableName, where, pageSize, columns, rowStart, rowEnd, orderBy);
  116. }
  117. }
  118. return string.Format(tempTablePager, DateTime.Now.Millisecond, pageIndex * pageSize + " " + columns, tableName, where, pageSize, rowStart, rowEnd, orderBy);
  119. case DalType.Access:
  120. top3:
  121. if (!string.IsNullOrEmpty(orderBy)) // 反转查询
  122. {
  123. return string.Format(top4Pager,columns, (rowCount - max > pageSize ? pageSize : rowCount - max), topN, tableName, where, GetOrderBy(where, true, primaryKey), GetOrderBy(where, false, primaryKey), orderBy);
  124. }
  125. return string.Format(top3Pager, (rowCount - max > pageSize ? pageSize : rowCount - max),columns, topN, tableName, where, GetOrderBy(where, true, primaryKey), GetOrderBy(where, false, primaryKey));
  126. case DalType.SQLite:
  127. case DalType.MySql:
  128. case DalType.PostgreSQL:
  129. if (max > 500000 && primaryKeyIsIdentity && Convert.ToString(objWhere) == "" && !tableName.Contains(" "))//单表大数量时的优化成主键访问。
  130. {
  131. where = string.Format("{0}>=(select {0} from {1} limit {2}, 1) limit {3}", primaryKey, tableName, max, pageSize);
  132. return string.Format(top1Pager, columns, tableName, where);
  133. }
  134. return string.Format(top1Pager, columns, tableName, where + " limit " + pageSize + " offset " + max);
  135. }
  136. return (string)Error.Throw("Pager::No Be Support:" + dalType.ToString());
  137. }

总结:

一个数据库的基本支持、写到这里就完成了增删改查及分页。

当然,对于CYQ.Data而言,还差一些未处理:

1、多种数据库转换互通处理:DataType.cs。

2、对表的创建修改操作:SqlCreateForSchema.cs。

3、支持多数据库兼容性写法:SqlCompatible.cs。

4、其它细节。

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号