【问题标题】:ODAC seems to be caching table schema?ODAC 似乎正在缓存表模式?
【发布时间】:2013-05-27 09:50:00
【问题描述】:

我将 Oracle 的 ODAC.NET 用于针对 Oracle 11 Express 数据库的 .NET 3.5 项目,我看到了我无法解释的行为(而且似乎无法解决)。

ODAC应该是最新的,我3天前刚拉的,版本如下:

  • Oracle.DataAccess.dll 版本 2.112.3.0(第 5 版)
  • oci.dll(即时客户端)版本 11.2.0.1

我有一个表格 People,它有 3 列:

  • 身份证
  • 名字
  • 姓氏

在代码中,我使用 OracleCommand.ExecuteNonQuery 运行 ALTER TABLE 命令,将名为“MIDDLE_NAME”的新列添加到表中。该命令成功。如果我使用 Oracle SQL Developer 查看表格,就会显示列。一切顺利。

现在,如果我在执行 alter table 后立即使用 OracleCommand.ExecuteReader 和命令文本 SELECT * FROM People 运行,我得到的数据只有 3 列,而不是 4 列!

这里是重现问题的代码:

public void FieldTest()
{
    var sql1 = "CREATE TABLE People (" +
        "ID NUMBER PRIMARY KEY, " +
        "FirstName NVARCHAR2 (200), " +
        "LastName NVARCHAR2 (200) NOT NULL)";

    var sql2 = "ALTER TABLE People " +
        "ADD Middle_Name NUMBER";

    var sql3 = "SELECT * FROM People";

    var sql4 = "SELECT column_name FROM all_tab_cols WHERE table_name = 'PEOPLE'";

    var cnInfo = new OracleConnectionInfo("192.168.10.246", 1521, "XE", "system", "password");
    var connectionString = BuildConnectionString(cnInfo);

    using (var connection = new OracleConnection(connectionString))
    {
        connection.Open();

        using (var create = new OracleCommand(sql1, connection))
        {
            create.ExecuteNonQuery();
        }

        using (var get = new OracleCommand(sql3, connection))
        {
            using (var reader = get.ExecuteReader())
            {
                Debug.WriteLine("Columns: " + reader.FieldCount);
                // outputs 3, which is right
            }
        }

        using (var alter = new OracleCommand(sql2, connection))
        {
            alter.ExecuteNonQuery();
        }

        using (var get = new OracleCommand(sql3, connection))
        {
            using (var reader = get.ExecuteReader())
            {
                Debug.WriteLine("Columns: " + reader.FieldCount);
                // outputs 3, which is *wrong* <---- Here's the problem
            }
        }

        using (var cols = new OracleCommand(sql4, connection))
        {
            using (var reader = cols.ExecuteReader())
            {
                int count = 0;

                while (reader.Read())
                {
                    count++;
                    Debug.WriteLine("Col: " + reader.GetString(0));
                }
                Debug.WriteLine("Columns: " + count.ToString());
                // outputs 4, which is right
            }
        }
    }
}

我已经尝试了一些方法来防止这种行为,但它们都没有给我返回第 4 列:

  • 我关闭连接并重新打开它
  • 我为SELECT 使用了一个新的OracleConnection,而不是ALTER
  • 我对@9​​87654329@ 和ALTER 使用相同的OracleConnection
  • 我为SELECT 使用了一个新的OracleCommand,而不是ALTER
  • 我对@9​​87654335@ 和ALTER 使用相同的OracleCommand
  • 我在ALTERSELECT 之间的连接上调用PurgeStatementCache
  • 我在ALTERSELECT 之间的连接上调用FlushCache
  • 我明确地将CloseDispose OracleCommandOracleConnection(与使用块相对)用于ALTERSELECT
  • 重新启动调用 PC 和托管 Oracle 数据库的 PC。

如果我通过SELECT * FROM all_tab_cols 查看列列表,则新列就在那里。

似乎唯一可靠的工作是关闭应用程序并重新启动它(它来自单元测试,但它是测试主机的关闭和重新启动)。然后我得到第四列。有时我可以使用断点并重新执行查询,然后会出现第 4 列,但没有任何东西可以通过直接执行代码来特别重复(意味着不设置断点并将执行点向上移动)。

ODAC 内部的某些东西似乎正在缓存该表的模式,但我可以弄清楚是什么、为什么或如何阻止它。任何人都有这方面的经验,或者有什么想法可以防止它吗?

【问题讨论】:

  • 您是否尝试过在 SQL Developer 中发出“提交”语句?如果您没有明确发出提交,更新、插入和 ddl 更改将不会持续存在,它们只是处于一个打开的事务中,因为 SQL Developer 默认将它们包装在一个事务中。这是 SQL Server 的相反行为,您必须在提交之前定义事务。
  • 刚试过。在 ALTER 和 SELECT 之间设置一个断点,运行到那个断点,在 SQL Developer 中执行 COMMIT,然后运行 ​​SELECT,我仍然只能返回 3 列。
  • @DavidC 你不应该在做一个改变表(它是隐含的)之后发出提交,但我想已经发生了奇怪的事情。而且我不知道为什么这个问题被否决了,似乎比我在 Oracle 选项卡中看到的许多问题要好。 +1
  • odp.net 什么版本? (11.2 rel 5 是最新的,你在哪个?)
  • 我用版本更新了问题。不知道为什么它也被否决了,这是一个真正的问题,在询问之前我已经做了一大堆的跑腿工作试图自己解决它。不过不要过分担心 - 我很确定我可以识别好问题和坏问题。

标签: c# .net oracle odac


【解决方案1】:

我知道这个答案会在几年后出现,但如果新读者遇到缓存尝试设置问题:

元数据池 = 假,自我调整 = 假,语句缓存大小 = 0

...在连接字符串中。请记住,这样做会影响性能。

https://docs.oracle.com/database/122/ODPNT/featConnecting.htm#GUID-0CFEB161-68EF-4BC2-8943-3BDFFB878602

【讨论】:

    【解决方案2】:

    也许发布一些你的 C# 代码。以下是按预期运行的测试,这意味着我可以在添加新列后立即看到它。这是使用 odp 11.2 rel 5 达到 11g db,使用 4.0 框架:

    测试表是:

    CREATE TABLE T1
    (
      DTE  DATE default sysdate
    );
    

    在每次运行以下 C# 代码后删除并重新创建它(有点脏但无论如何):

    string connStr = "User Id=xxx;Password=yyy;Data Source=my11gDb;";
    using (OracleConnection con = new OracleConnection(connStr))
    {
        string s = "ALTER TABLE T1 ADD (added_col VARCHAR2(10))";
        using (OracleCommand cmd = new OracleCommand(s, con))
        {
            con.Open();
            cmd.ExecuteNonQuery();
    
            string s2 = "select column_name from all_tab_columns where table_name = 'T1'";
            //con.FlushCache(); // doesn't seem to matter, works with or without
    
            using (OracleCommand cmd2 = new OracleCommand(s2, con))
            {
                OracleDataReader rdr = cmd2.ExecuteReader();
    
                for (int i = 0; rdr.Read(); i++)
                {
                    Console.WriteLine("Column {0} => {1}",i+1,rdr.GetString(0));
                }
                rdr.Close();
            }
        }
    }
    

    输出:

    Column 1 => DTE
    Column 2 => ADDED_COL
    

    编辑: 啊,好的,我明白你在说什么,它看起来像语句缓存。我尝试将缓存大小更改为 0(在 conn 字符串中,使用“Statement Cache Size=0”),还尝试了 cmd.AddToStatementCache = false,但这些都不起作用。

    一个可行的方法是使用稍微不同的字符串,比如添加一个空格。我知道这是一个 hack,但无论如何我只能为我工作。

    试试你的例子:

    var sql3 = "SELECT * FROM People";
    var sql5 = "SELECT * FROM People "; // note extra space
    

    并且在添加列之前使用sql3,在添加列之后使用sql5。

    希望有帮助

    【讨论】:

    • 是的,你在这里的东西会起作用。但是,从 T1 执行 SELECT * 不会为您提供新字段,这是一个问题。我已经在问题中添加了我的测试代码。
    • 不幸的是,声明本身是我生成的,我真的不知道它之前是否已经完成(我想我可以添加一些hackery来计算它)。这是我拥有的支持多个数据库和操作系统的更大 ORM 的一部分,因此我试图避免太多特定的黑客攻击。
    • @ctacke 是的,少黑客 = 好 :-) 我试着搞乱缓存,详细 here
    • 有人会认为调用 PurgeStatementCache 会处理这个问题。闻起来像 ODAC 中的错误,但至少我知道我不会发疯。
    猜你喜欢
    • 2016-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多