为什么我的SqlCacheDependency HasChanged返回false,但几乎立即变为true?

12
我无法理解为什么我的SqlCacheDependency对象的HasChanged值最初在命令执行时返回false,但几乎立即在从数据库返回后更改为true。有时候,在项目插入缓存之前就会发生这种情况,导致缓存立即丢弃它;有时候是在插入之后,我可以获取一个枚举器,看到缓存中的键,但在我循环到该项之前,它已被删除。
SPROC:
ALTER PROCEDURE [dbo].[ntz_dal_ER_X_Note_SelectAllWER_ID]
        @ER_ID int
AS
BEGIN
    SELECT
        ER_X_Note_ID,
        ER_ID,
        Note_ID
    FROM dbo.ER_X_Note e
    WHERE
        ER_ID = @ER_ID
END

数据库是MS SQL Server 2008,代理服务已启用,并且有些输出会缓存并保持缓存状态。例如,这个可以正常工作:
ALTER PROC [dbo].[ntz_dal_GetCacheControllerByEntityName] (
    @Name varchar(50)
) AS
BEGIN
    SELECT 
        CacheController_ID,
        EntityName,
        CacheEnabled,
        Expiration
    From dbo.CacheController cc
    WHERE   EntityName = @Name
END

调用失败的存储过程的代码如下:
    DataSet toReturn;
    Hashtable paramHash = new Hashtable();
    paramHash.Add("ER_ID", _eR_ID.IsNull ? null : _eR_ID.Value.ToString());
    string cacheName = BuildCacheString("ntz_dal_ER_X_Note_SelectAllWER_ID", paramHash);
    toReturn = (DataSet)GetFromCache(cacheName);
    if (toReturn == null)
    {

        // Set up parameters (1 input and 0 output)
        SqlParameter[] arParms = {
                new SqlParameter("@ER_ID", _eR_ID),
            };
        SqlCacheDependency scd;

        // Execute query.
        toReturn = _dbTransaction != null 
            ? _dbConnection.ExecuteDataset(_dbTransaction, "dbo.[ntz_dal_ER_X_Note_SelectAllWER_ID]", out scd, arParms) 
            : _dbConnection.ExecuteDataset("dbo.[ntz_dal_ER_X_Note_SelectAllWER_ID]", out scd, arParms);

        AddToCache(cacheName, toReturn, scd);
    }

    return toReturn;

代码能够运行

        const string sprocName = "ntz_dal_GetCacheControllerByEntityName";
        string cacheControlPrefix = "CacheController_" + CachePrefix;
        CacheControl controller = (CacheControl)_cache[cacheControlPrefix];
        if (controller == null)
        {
            try
            {
                SqlParameter[] arParms = {
                                             new SqlParameter("@Name", CachePrefix),
                                         };
                SqlCacheDependency sqlCacheDependency;

                // Execute query.
                DataSet result = _dbTransaction != null
                                     ? _dbConnection.ExecuteDataset(_dbTransaction, sprocName, out sqlCacheDependency, arParms)
                                     : _dbConnection.ExecuteDataset(sprocName, out sqlCacheDependency, arParms);

                controller = result.Tables[0].Rows.Count == 0
                                 ? new CacheControl(false)
                                 : new CacheControl(result.Tables[0].Rows[0]);

                _cache.Insert(cacheControlPrefix, controller, sqlCacheDependency);
            }
            catch (Exception ex)
            {
                // if sproc retreival fails cache the result of false so we don't keep trying
                // this is the only case where it can be added with no expiration date
                controller = new CacheControl(false);

                // direct cache insert, no dependency, no expiration, never try again for this entity
                if (HttpContext.Current != null && UseCaching && _cache != null) _cache.Insert(cacheControlPrefix, controller);
            }
        }
        return controller;
AddToCache方法是重载的,并且其中有更多的测试;在工作方法中直接使用_cache.Insert是为了绕过其他测试。工作代码有助于确定是否应该进行数据库缓存。
您可以看到,当“不工作”的数据最初被检索时,一切都正常: enter image description here 但在那之后的某个随机时间点,在这种情况下只需进入下一个方法即可: enter image description here 然而,数据根本没有改变;我是唯一触摸此数据库实例的人。
3个回答

6

这个问题实际上非常简单,以至于我完全忽略了它。

在这篇文章“为通知创建查询”中,我搜索了多次,里面明确指出:

SET Option Settings

When a SELECT statement is executed under a notification request, the connection that submits the request must have the options for the connection set as follows:

ANSI_NULLS ON
ANSI_PADDING ON
ANSI_WARNINGS ON
CONCAT_NULL_YIELDS_NULL ON
QUOTED_IDENTIFIER ON
NUMERIC_ROUNDABORT OFF
ARITHABORT ON

我仔细阅读了存储过程,反复阅读,但仍然没有看到ANSI_NULLS和QUOTED_IDENTIFIER都是"OFF",而不是"ON"。

现在我的数据集可以正确缓存和保留数据,不会出现虚假的更改指示。


1

我有一种预感,问题出在你的_eR_ID上。我认为你应该尝试在失败的过程中添加一个本地变量,使用一个不可能的值来代替_eR_ID,比如-1。当涉及到空值时,我从来不相信会发生什么,我认为这可能是你问题的根源。

这是我建议尝试的修改版本:

DataSet toReturn;
Hashtable paramHash = new Hashtable();

int local_er_ID = eR_ID.IsNull ? -1 : _eR_ID.Value;
paramHash.Add("ER_ID", local_eR_ID.ToString());

string cacheName = BuildCacheString("ntz_dal_ER_X_Note_SelectAllWER_ID", paramHash);
toReturn = (DataSet)GetFromCache(cacheName);
if (toReturn == null)
{

    // Set up parameters (1 input and 0 output)
    SqlParameter[] arParms = {
            new SqlParameter("@ER_ID", local_eR_ID),
        };
    SqlCacheDependency scd;

    // Execute query.
    toReturn = _dbTransaction != null 
        ? _dbConnection.ExecuteDataset(_dbTransaction, "dbo.[ntz_dal_ER_X_Note_SelectAllWER_ID]", out scd, arParms) 
        : _dbConnection.ExecuteDataset("dbo.[ntz_dal_ER_X_Note_SelectAllWER_ID]", out scd, arParms);

    AddToCache(cacheName, toReturn, scd);
}

return toReturn;

重要提示

在编写上述代码时,我认为我发现了您的问题源:在设置存储过程参数时,您使用了_eR_ID,但在设置paramHash时,您使用了_eR_ID.Value

代码重写将解决此问题,但我怀疑这是问题的根源。


我会尝试,但我认为这很不可能,因为param哈希仅用于构建创建缓存项的键名。 - The Evil Greebo
1
@TheEvilGreebo:抱歉,我觉得我没有表达清楚。我认为问题出在这行代码 new SqlParameter("@ER_ID", _eR_ID) 上。这将整个 _eR_ID 对象传递给参数,我不确定它如何将其转换为 Sql 的值(我怀疑它调用对象的 ToString() 方法)。在代码的早期,您使用了 _eR_ID.Value,因此我认为您需要进行的最小更改是 new SqlParameter("@ER_ID", _eR_ID.Value) - competent_tech
哦,我明白你的意思了。那么 _eR_ID 是一个 SqlInt32 - 所以人们会认为使用它来初始化一个新的 SqlParm 不会引起问题,但值得一试! - The Evil Greebo
谢谢你的尝试 - 但事实证明它是更简单的问题。我只需要用新鲜的眼光再次审视它。 - The Evil Greebo

0

遇到相同的问题并在网上找到相同的答案却没有任何帮助,我正在研究来自分析器的无效订阅响应的xml。

我在msdn支持网站上找到了一个例子,它的代码顺序略有不同。当我尝试时,我意识到了问题 - 不要在创建命令对象和缓存依赖项对象之后打开连接对象。以下是您必须遵循的顺序,一切都会很好:

  1. 确保启用通知(SqlCahceDependencyAdmin)并首先运行SqlDependency.Start
  2. 创建连接对象
  3. 创建命令对象并分配命令文本、类型和连接对象(使用构造函数、设置属性或使用CreateCommand的任何组合)。
  4. 创建sql缓存依赖项对象
  5. 打开连接对象
  6. 执行查询
  7. 使用依赖项将项目添加到缓存中。

如果您按照此顺序,并遵循选择语句上的所有其他要求,没有任何权限问题,这将起作用!

我认为问题与.NET框架管理连接的方式有关,具体而言是设置了哪些设置。我尝试在我的sql命令测试中覆盖此设置,但从未奏效。这只是一个猜测 - 我知道的是立即更改顺序解决了问题。

我能够从以下两篇 MSDN 文章中拼凑出来。

这篇文章是无效订阅的更常见原因之一,它展示了 .Net 客户端设置属性与通知要求相反的情况。

https://social.msdn.microsoft.com/Forums/en-US/cf3853f3-0ea1-41b9-987e-9922e5766066/changing-default-set-options-forced-by-net?forum=adodotnetdataproviders

那么,这篇文章是来自一个像我一样已经将代码简化到最简格式的用户。我的原始代码模式与他的类似。

https://social.technet.microsoft.com/Forums/windows/en-US/5a29d49b-8c2c-4fe8-b8de-d632a3f60f68/subscriptions-always-invalid-usual-suspects-checked-no-joy?forum=sqlservicebroker

然后我发现了这篇帖子,也是一个非常简单的问题,只需要为表格提供两部分名称。在他的情况下,建议解决了这个问题。在查看他的代码之后,我注意到主要区别是等到命令对象和依赖项对象创建后才打开连接对象。我的唯一假设是,在幕后(我还没有开始反编译检查,所以只是假设),连接对象是以不同的方式打开的,或者事件顺序和命令发生了不同的变化,因此会产生这种关联。

https://social.msdn.microsoft.com/Forums/sqlserver/en-US/bc9ca094-a989-4403-82c6-7f608ed462ce/sql-server-not-creating-subscription-for-simple-select-query-when-using-sqlcachedependency?forum=sqlservicebroker

我希望这能帮助到其他遇到类似问题的人。


我认为这不正确。顺序很重要,但据我所知,打开连接时并不重要。 - The Evil Greebo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接