非常规方式处理Oracle+.NET开发全球化的时区显示

咨询了几个大牛有关.NET开发中全球化的时区显示问题,大家的意见有三个:

1、使用UTC Time记录到数据库,展示的时候根据用户所选择的时区进行转换展示
2、使用固定时区DateTime记录到数据库,展示的时候根据用户所选择的时区进行转换展示
3、记录timestamp到数据库,选择DateTime.UTCTime转为秒或毫秒级别的timestamp,展示的时候转为时间类型,并根据用户所选择的时区进行转换展示

大部分人喜欢1,其次是3,最后是2

而我今天要分享的这个Oracle数据库下的开发,有个前提就是我不能修改数据库,也不能修改写入数据库的时间是指定时区的,因为Infor LN ERP中更新此时间字段,幸运的是它本来就是UTC Time。

但是呢,我不能直接用第1条方案,因为我有些筛选条件,根据用户的日期(时间)还需要筛选数据,那么我不想:既修改展示阶段的时间时区,又修改查询时候的输入时间。

于是就有了今天的非常规方案:sessiontimezone

当我们在Oracle数据库中执行以下SQL时,可以知道数据库的时区和我当前连接的时区。

SELECT BTIMEZONE,SESSIONTIMEZONE,TZ_OFFSET(DBTIMEZONE),TZ_OFFSET(SESSIONTIMEZONE) FROM DUAL;

那么我们就可以使用:TO_NUMBER(SUBSTR(TZ_OFFSET(sessiontimezone),1,3))/24,来动态的展示当前连接所在时区的时间了

SELECT TO_CHAR(A.T$CNFT + TO_NUMBER(SUBSTR(TZ_OFFSET('" + GetUserTimeZone() + "'),1,3))/24, 'YYYY-MM-DD HH24:MI:SS') AS ConfirmedTime FROM MYTABLE A

此处的GetUserTimeZone就是根据当前用户的市区设置,读取到一个“-5:00”或”6:00″形式的TimeZone。

至于用户的时区是根据用户所属的国家来还是根据用户的个人设定,这里的逻辑可以灵活设定优先级。

虽然非常规方案可以满足需要,但是不具备普遍性,性能上也会很依赖Oracle数据的配置。

如果您碰巧也有类似的场景,不妨试试这个方案。

Loading

2021开始用DBeaver连接各种数据库:强烈推荐这款免费开源的通用数据库工具

平常用微软的SQL Server(MSSQL)数据库最多,用的是微软的SQL Server Management Studio (SSMS) 客户端,免费的。

Oracle使用频率次之,用的是它自家的Oracle SQL Developer,也是免费的。

MySQL数据库使用的最少,用得是以前介绍过的SQLyog,免费试用版,过期了也不用了。

最近又要连接MySQL数据库了,听说Navicat for MySQL不错,企业版永久授权活动价也就1600多块钱。但用得实在不多,就懒得买了。

在捣鼓Ubuntu Server的虚拟机下Ngnix的过程中,安装了Ubuntu Desktop版本,通过其App Store找到了DBeaver CE(Community Edition社区版),评价很高,并且免费开源。

在Ubuntu下体验了一下,还挺不错,支持很多数据库,使用时自动下载相应的数据库驱动。

于是在Windows下就正式启用了,下载的速度不快,官网是:DBeaver Community | Free Universal Database Tool

这款软件定位是免费开源的通用数据库工具:为开发者、数据库管理员、分析师和所有需要使用数据库的人提供免费的多平台数据库工具。支持所有流行的数据库:MySQL、PostgreSQL、SQLite、Oracle、DB2、SQL Server、Sybase、MS Access、Teradata、Firebird、Apache Hive、Phoenix、Presto等。

软件是基于Java开发的,所以支持跨平台:Linux、Windows、Mac都支持。除了社区版和企业版,它还有云端的基于浏览器的版本。

安装后首次运行会问你要不要创建一个SQLite的示例数据库,用于探索软件。这个引导功能做的挺赞,必须赞一下。

我试了访问MySQL、SQL Server,体验挺不错的,第一时间就把过期的SQLyog删除了。

后续我会深度用一下它的MySQL和Oracle连接数据库,请关注后续更新。

如果你也有类似管理MySQL的需要,不妨试试这款DBeaver:非常强大易用的数据库管理和开发工具。

Loading

Oracle中如何获取当年第几周、第几天,当月第几周、第几天等

最近弄有关标签上的日期码DATECODE,遇到了自动生成的问题,这个呢可以在C#程序中根据日期自动生成,也可以通过数据库生成。今天先介绍下数据库生成,这里用Oracle数据库。

先来说一下需求:日期格式需要取年的后两位+月份转为A-L+当月第几周,如21I1就是今天的日期码。

相应的Oracle SQl语句如下:

SELECT TO_CHAR(SYSDATE, 'YY')||
DECODE(TO_CHAR(SYSDATE,'MM'),'01','A','02','B','03','C','04','D','05','E','06','F','07','G','08','H','09','I','10','J','11','K','12','L','MM')||
TO_CHAR(SYSDATE,'W') AS DATECODE FROM DUAL;

可能你会问,这个周是9月份的第二个周了,为啥显示为第1周?

有必要把一些常用的SQl解释下。

SELECT TO_CHAR(SYSDATE, 'w') FROM DUAL; --1个月中的第几周
(从每月1号开始算,日期+6天为每1周结尾)
SELECT TO_CHAR(SYSDATE, 'ww') FROM DUAL; --1年中的第几周(每年1月1日为第1周开始,日期+6天为每1周结尾)
SELECT TO_CHAR(SYSDATE, 'iw') FROM DUAL; --1年中的第几周(星期一至星期日算1周,且每年的第一个星期一为第1周)
SELECT TO_CHAR(SYSDATE, 'd') FROM DUAL; --1周的第几天
SELECT TO_CHAR(SYSDATE, 'day') FROM DUAL; --当前日期是星期几,可设置显示英文还是中文
SELECT TO_CHAR(SYSDATE, 'ddd') FROM DUAL; --1年中第几天
SELECT TO_CHAR(SYSDATE,'q') FROM DUAL; --1年中第几季度

Loading

手贱玩坑自己填:ExecuteNonQuery返回-1

最近手贱,将Oracle.ManagedDataAccess升级到19.12.0,原来运行正常(其实有问题)的代码忽然出问题了。

后来一条是发现所有BEGIN END包裹的UPDATE语句,现在都是返回-1,而不是想要的影响的行数了。

其实我所使用的DotNet.Util的底层类库中ExecuteNonQuery();默认值就是-1.

var result = -1;

并且呢,本来ExecuteNonQuery()在执行UPDATE、INSERT以及DELETE语句时,返回值为该命令所影响的行数。这是这个方法的建议用法。

如果用这个执行SELECT语句,不会返回影响的行数或者记录,而返回-1,不论查询结果有没有记录都会返回-1。

同理,如果执行任何CREATE、ALTER、TRUNCATE创建/修改/删除表、字段、索引等语句,同样返回-1。

既然明白了原理,但还得使用一次性多个SQL一起执行,没办法的办法,只能硬着头皮,用个馊主意。

针对BEGIN END的调用,明确知道包含BEGIN END的,就返回一个绝对值,代表成功了。

//return result;
//return -1 if BEGIN END
return Math.Abs(result);

我知道这样做不对,不好。

谁有好办法,分享下。

比方说现在EF里面的做法,或者ORM这些怎么处理的?

等我有时间去研究下SqlSugar或者FreeSql这类ORM看看他们怎么处理的。

Loading

.NET程序连接Oracle一次执行多行SQL的注意事项

以前写的基于MSSQL数据库的.NET程序,不用担心SQL语句中的;或者换行符。但是因为要基于Infor LN的Oracle数据库进行开发,就碰到了;分号和换行的报错,同时一次执行UPDATE的多条更新语句时,也会报错。

Oracle.ManagedDataAccess.Client.OracleException:ORA-00911: invalid character

单行SQL如果有换行时,加了;就报上面的错,多行执行的时候,会报下面这种错误

Oracle.ManagedDataAccess.Client.OracleException:ORA-06550: line 1, column 1:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:

   ; <an identifier> <a double-quoted delimited-identifier>
The symbol ";" was substituted for "end-of-file" to continue.

虽然从网上能搜索到ExecuteNonQuery执行Oracle多条SQL的时候需要用到以下结构

BEGIN
SQL1;
SQL2;
SQL3;
END;

但是并没有人提到一定要确保整个SQL是一行,必须没有换行!

BEGIN
 UPDATE BAANDB.TWHINH225301 A
 SET A.T$WVID = -999
,A.T$ASGN = 1
,A.T$PKID = 'Troy'
,A.T$STLO = 'EMS'
 WHERE A.T$RUNN = 'CN432789'
 AND A.T$PICM = 1
 AND A.T$ASGN = 2
 AND A.T$PCKD = 2;
UPDATE BAANDB.TWHINH225301 A
 SET A.T$WVID = -999
,A.T$ASGN = 1
,A.T$PKID = 'Troy'
,A.T$STLO = 'EMS'
 WHERE A.T$RUNN = 'CN432785'
 AND A.T$PICM = 6
 AND A.T$ASGN = 2
 AND A.T$PCKD = 2;
END;

因为写日志看SQL方便,用了AppendLine,那么下面的代码中,需要手动替换下换行符:Replace(Environment.NewLine, ” “),如果还不放心,可以用Replace(“r\n”, ” “).Replace(‘\n’, ‘ ‘).Replace(‘\r’, ‘ ‘) 批量将各种换行符替换为空格。

            var result = 1;
            var sb = new StringBuilder();
            sb.AppendLine("BEGIN");
            foreach (var entity in list)
            {
                sb.AppendLine("UPDATE BAANDB.TWHINH225" + companyId + " A");
                sb.AppendLine("SET A.T$WVID = -999");
                sb.AppendLine(",A.T$ASGN = 1");
                if (!string.IsNullOrEmpty(entity.Picker))
                {
                    entity.Picker = dbHelper.SqlSafe(entity.Picker);
                    sb.AppendLine(",A.T$PKID = '" + entity.Picker + "'");
                }
                if (!string.IsNullOrEmpty(entity.StagingLocation))
                {
                    entity.StagingLocation = dbHelper.SqlSafe(entity.StagingLocation);
                    sb.AppendLine(",A.T$STLO = '" + entity.StagingLocation + "'");
                }
                // By Line
                sb.AppendLine("WHERE A.T$OORG = " + GetOrderOrigin(entity.OrderOrigin));
                sb.AppendLine("AND A.T$ORNO = '" + entity.OrderNumber + "'");
                sb.AppendLine("AND A.T$OSET = " + entity.OrderSet);
                sb.AppendLine("AND A.T$PONO = " + entity.Line);
                sb.AppendLine("AND A.T$SEQN = " + entity.SequenceNumber);
                sb.AppendLine("AND A.T$SERN = " + entity.Advice);
                // Check Assign Status
                sb.AppendLine("AND A.T$ASGN = 2");
                sb.AppendLine("AND A.T$PCKD = 2;");
            }
            sb.AppendLine("END;");
            try
            {
                ExecuteNonQuery(sb.ToString().Replace(Environment.NewLine, " "));
            }
            catch (Exception e)
            {
                LogUtil.WriteException(e);
                result = 0;
            }

            return result;

因为这个问题,还从大石头的新生命微信群发了红包求助,意外收获了结识了一个挺不错的开发者王振(王Bank)和他写的DbHelper:https://gitee.com/wangbank/BankDbHelper

Loading

使用Oracle SqlDeveloper遇到的2个常见Java错误及解决办法

用了好长时间的Oracle官方SQL Developer近期在我两位同事的电脑上运行不正常了,起初怀疑是xp系统升级了赛门铁克的杀毒软件造成的,因为我的Win7的系统就没问题,后来帮其中一位同事重新安装了操作系统到Win7,可问题依旧。当然了在重新安装操作系统之前,也尝试搜索Google和Baidu相关的解决办法,尝试过手工修改系统的环境变量,还有在sqldeveloper.config文件增加SetJavaHome参数,都不奏效(也许是我哪里操作有误)。在Win7系统上,我也同时进行了多种测试:

1、通过网页方式在线安装Java6和Java7的客户端,分别测试
2、下载32位的2种Sql Developer版本,一种自带JRE,一种没有
3、下载64位的Sql Developer

都还是不是,分别出现如下错误信息(附截图)

<br/>Unable to launch the Java Virtual Machine Located at path:..\..\jdk\jre\bin\client\jvm.dll<br/>

<br/>Cannot find a J2SE SDK installed at path:....<br/>

期间也出现过如下错误:

[/CODE_LITE]
无法启动此程序,因为计算机丢失MSVCR71.dll。尝试重新安装该程序以解决此问题。
[CODE_LITE]

最终经过不停测试,发现如下解决办法:

1、下载32位的no JRE的SQL Developer版本
2、安装Java6或7的客户端
3、打开网址[URL=http://www.oracle.com/technetwork/java/javase/downloads/index.html]http://www.oracle.com/technetwork/java/javase/downloads/index.html[/URL],手动下载Java Platform (JDK) 6u45 或Java Platform (JDK) 7u21,并安装(默认路径和步骤2的客户端在同一个目录)
4、运行步骤1下载的程序,在弹出的对话框中选择正确的路径(如下图)

选择安装路径:C:\Program Files\Java\jdk1.6.0_43\bin\java.exe,这里的操作系统是XP和Win7 32位,64位的路径请按实际情况选择

这个问题是跟2位同事一起研究才搞定,也感谢他们让我觉得经过几天的思考一下子放松了。

Loading

我的ODP.NET开发之路3-ORA-14551: cannot perform a DML operation inside a query

昨天下午写了2个Function,一个是从ERP LN中读出来Item-Purchase BP中的一个指定的Item和BP的BlockingReason,另一个是按照指定的ID从应用系统中读出来Item和BP,从而调用第一个Function返回真正的BlockingReason,在每一个Function中,都有一段如果报错就调用写错误日志的存储过程。在实际的使用中使用:Select Function2(ID) from Dual;,但是老报以下错误:

<br/>ORA-06502: PL/SQL: numeric or value error: character string buffer too small<br/>ORA-06512: at "TROY.ONTRAC_QRS_PKG", line 760<br/>ORA-14551: cannot perform a DML operation inside a query <br/>ORA-06512: at "TROY.COMMON_SYSTEMLOG_PKG", line 43<br/>ORA-06512: at "TROY.COMMON_BAANBRIDGE_PKG", line 339<br/>ORA-01403: no data found <br/>

经过查询,原因是“对数据库有写操作(INSERT、UPDATE、DELETE、CREATE、ALTER、COMMIT)的函数,是无法简单的用SQL来调用的”。按照这篇文章:[URL=http://www.cnblogs.com/pengyq/archive/2008/11/26/1341656.html]ORA-14551: cannot perform a DML operation inside a query [/URL],加上了“自主事务”:PRAGMA AUTONOMOUS_TRANSACTION; ,不过需要注意在Delcare的部分加入“PRAGMA AUTONOMOUS_TRANSACTION;”之后,必须在SQL语句后面加入“COMMIT;”

看如下例子:

<br/>    /*/* RFD #50 20111214 new start Troy Cui */<br/>    PRAGMA AUTONOMOUS_TRANSACTION;<br/>    /*/* RFD #50 20111214 new end Troy Cui */<br/>    BEGIN<br/>    <br/>        select trim(nvl(FirstName, '') || ' ' || nvl(LastName, ''))<br/>          into sDisplayName<br/>          from COMMON_Users<br/>         where UserID = nLoggedByID;<br/><br/>        if (sDisplayName = '') then <br/>            sDisplayName := '- unassigned -';<br/>        end if;<br/>    <br/>        insert into COMMON_SystemLog(LoggedByID, LogDate, LogCategory, LogEntry, AccessLevel, <br/>                                     Source, DataDump, DisplayName)<br/>                              values(nLoggedByID, SYSTIMESTAMP, sLogCategory, sLogEntry, nAccessLevel,<br/>                                     sSource, sDataDump, sDisplayName);<br/>        /*/* RFD #50 20111214 new start Troy Cui */<br/>        COMMIT;<br/>        /*/* RFD #50 20111214 new end Troy Cui */<br/><br/>    END WriteLog;<br/>

Loading

我的ODP.NET开发之路3-Oracle Package/Procedure/Function

上周的项目进展比较大,完成了几个重大的功能。这其中涉及到在Oracle中创建新表、序列、索引、触发器、包、存储过程、函数,当然了也在实战中学习了几个.Net的Dataset\Datatable的用法。一直想写,不过项目紧,真没空。这不今天有点时间,但是很多东西又没那么强烈的书写欲望了。

说说今天工作遇到的几个东东吧,第一个是Oracle中的包。不晓得为什么Oracle设计包,并且把存储过程和函数都写在不同的包里,而MSSQL虽然也都有存储过程和函数,但是基本上所有的C#程序调用都是对存储过程的操作。Oracle中的存储过程如果放在包内,C#程序使用“包名.存储过程(变量)”或者“包名.函数(变量)”的形式来访问。

对于创建或者修改包的语法我就不重复了,你可以搜索一下。不过有一点我需要分享给大家,在Function中,如果对变量赋值,写法可不一样。

MSSQL中一般写成

<br/>@BlockingReason nvarchar(50)<br/><br/>Select @BlockingReason = value from table where ...<br/>

而在Oracle中是Select value into variable from table where …,看下面的例子

<br/>BlockingReason   varchar2(100);<br/>select trim(nvl(ipbp.t$brea,'')) into BlockingReason<br/>               from baan.ttdipu010301 ipbp  -- item - purchase BP data<br/>              where upper(trim(ipbp.t$item)) = upper(sWAIPartNumber)<br/>                and ipbp.t$otbp =  upper(sBusinessPartnerCode)<br/>                and rownum <=1 order by rownum;<br/>

第二个要分享的是3个很有用的Oracle函数:SUBSTR、DECODE、NVL
SUBSTR(Value,1,5) 取出从Value的第1位开始取5个字符长度
NVL(Value,’a’) 如果Value为Null返回a
DECODE比较复杂,如果你需要针对一个值做很多判断的时候,不用写case了。这个详细的介绍和用法,你搜索一下吧,我解释不清楚。

第三个要分享的,其实是C# .Net的一个Datatable绑定到Dropdownlist的代码,包括了选择值的默认选项

<br/>WAIonTracFailureModes oFailureModes = new WAIonTracFailureModes(_oWorkItem.ApplicationID);<br/><br/>        DataTable dt = oFailureModes.List().Tables[0].Copy();<br/>        DataRow[] drs = dt.Select("ParentID=0");<br/>        foreach (DataRow r in drs)<br/>        {<br/>            //text,value<br/>            WAIonTracFailureModeList.Items.Add(new ListItem(r["Name"].ToString(), r["FailureModeID"].ToString()));<br/>        }<br/><br/>        //WAIonTracFailureModeList.DataSource = oFailureModes.List();<br/>        //WAIonTracFailureModeList.DataTextField = "Name";<br/>        //WAIonTracFailureModeList.DataValueField = "FailureModeID";<br/>        //WAIonTracFailureModeList.DataBind();<br/><br/>        //DataRow[] drs2 = dt.Select("ParentID="+_oWorkItem.FailureModeID);<br/>        DataTable dt2 = oFailureModes.List().Tables[0].Copy();<br/>        DataRow[] drs2 = dt2.Select();<br/>        //<br/>        if (_oWorkItem.FailureModeID != null)<br/>        {<br/>            drs2 = dt2.Select("ParentID=" + _oWorkItem.FailureModeID);<br/>        }<br/><br/>        foreach (DataRow r2 in drs2)<br/>        {<br/>            //text,value<br/>            WAIonTracDetailsOfFailureList.Items.Add(new ListItem(r2["Name"].ToString(), r2["FailureModeID"].ToString()));<br/>        }<br/>

绑定默认/选择值的

<br/>WAIonTracFailureModeList.SelectedIndex = WAIonTracFailureModeList.Items.IndexOf(WAIonTracFailureModeList.Items.FindByValue(_oWorkItem.FailureModeID.ToString()));<br/>        WAIonTracDetailsOfFailureList.SelectedIndex = WAIonTracDetailsOfFailureList.Items.IndexOf(WAIonTracDetailsOfFailureList.Items.FindByValue(_oWorkItem.DetailsOfFailureID.ToString()));<br/>

先写这么多吧,比较乱。另外,昨天买的《涂抹Oracle-三思笔记之一步一步学Oracle》已经到手,目前只知道如何用了,但是“为什么”还不知道,希望这本评价颇高的书能告诉我。

Loading

我的ODP.NET开发之路1

从Oracle下载了ODTwithODAC112021.zip的安装文件,是Oracle11g的,因为本来电脑上安装了Oracle 10g的客户端,所以在安装的时候走了一些弯路,后来把Oracle 10g的客户端删掉,接着把ODP.NET也删掉,重启电脑重新安装ODP.NET,然后在默认的C:\app\你的登陆账号\product\11.2.0\client_1\Network\Admin下放置正确的tnsnames.ora文件。打开VS2010后,就能顺利连接Oracle了。

Loading

配置64位SQL Server 2008的Linked Server连接64位的Oracle注意事项

到目前为止Windows 2008 64位下面还没有提供Microsoft OLEDB Provider for Oracle,如果要建立Linked Server似乎只有ODBC和Oracle Provider for OLEDB。在我目前的实际操作中ODBC连接方式,使用Open Query查询出来的结果老是少,搜索各大搜索引擎,也没查出来什么原因。只能安装Oracle Provider for OLEDB,我参考了这篇文章《[URL=http://www.mssqltips.com/tipprint.asp?tip=1433]How to setup linked servers for SQL Server and Oracle 64 bit client[/URL]》,但是这篇文章中有几个地方写的并不具体,有几个很重要的地方大家一定要注意。

1、Oracle 10g Release 2 64-bit ODAC software的下载地址:[URL=http://www.oracle.com/technetwork/database/windows/downloads/index-090165.html]http://www.oracle.com/technetwork/database/windows/downloads/index-090165.html[/URL]
要选择64-bit Oracle10g Release 2 ODAC 10.2.0.3 for Windows Itanium ODAC10203IA64.zip – 273 MB (286,676,590 bytes)
2、安装ODAC10203IA64的方法,其实不能从解压缩的stage文件家中直接运行,而是从原始的Oracle Universal Installer进行,选择安装文件位置那里选择ODAC解压缩后的Stage文件夹下的Products.xml配置文件。
3、在添加Linked Server的时候,其实没必要用那个SQL语句,界面化的操作只要选择“Oracle ”然后填写Product Name和Data Source,然后在Security选择指定的账号和密码并输入就行了。

Loading