【问题标题】:How to inherit parameter size from original table definition?如何从原始表定义继承参数大小?
【发布时间】:2017-02-14 17:00:03
【问题描述】:

使用 SqlDataAdapter 和 SqlCommandBuilder 为 sql server 创建的更新语句效率低,如下所述。

这里是复制的示例代码: Sql 服务器:

Create database TestDB;
GO
USE [TestDB]
CREATE TABLE [dbo].[test](
    [i] [int] NOT NULL,
    [v] [varchar](50) NULL,
    [c] [char](10) NULL,
 CONSTRAINT [pk1] PRIMARY KEY CLUSTERED ([i] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
insert into dbo.Test (i,v,c) values (10,'A','B');
GO

c#控制台应用演示代码:

using System;
using System.Data.SqlClient;
using System.Data;

namespace CmdBuildTest
{
    class Program
    {
        static void Main(string[] args)
        {
            SqlConnection cn = new SqlConnection();
            cn.ConnectionString = @"Server=localhost\sql2016;Database=testDB;Trusted_Connection=True;";             
            SqlDataAdapter da = new SqlDataAdapter("Select * From dbo.test", cn);

            //da.FillSchema(ds, SchemaType.Mapped);         
            SqlCommandBuilder cb = new SqlCommandBuilder(da);
            cb.ConflictOption = ConflictOption.OverwriteChanges;
            //cb.RefreshSchema();
            DataSet ds = new DataSet();
            da.Fill(ds);
            ds.Tables[0].Rows[0]["v"] = DateTime.UtcNow.Millisecond.ToString();
            SqlCommand u = cb.GetUpdateCommand(true);
            Console.WriteLine("Update Command: " + u.CommandText);

            foreach (SqlParameter par in u.Parameters)
            {
                Console.WriteLine(" Name=" +  par.ParameterName +  "|Type=" + par.SqlDbType.ToString() + " |Size=" + par.Size.ToString());
            }           
            da.UpdateCommand = u;  //I am not sure if this is required, but I am careful.               
            //Execute Changes / Update Statement :
            da.Update(ds);
            Console.ReadLine();
            //Sample result in Profiler:
            /*
                exec sp_executesql N'UPDATE [dbo].[test] SET [v] = @v WHERE (([i] = @Original_i))',N'@v varchar(3),@Original_i int',@v = '603',@Original_i = 1
            */
        }

    }
}

Console.WriteLine 显示创建的以下 SQL UPDATE 语句:

UPDATE [dbo].[test] SET [i] = @i, [v] = @v, [c] = @c WHERE (([i] = @Original_i))

在 Sql Profiler 中,以下查询正在被咳出:

exec sp_executesql N'UPDATE [dbo].[test] SET [v] = @v 
WHERE (([i] = @Original_i))',N'@v varchar(3),@Original_i int',@v='708',@Original_i=1

现在您可以看到,参数@v 被定义为 VARCHAR(3),而在原始表 dbo.test 中,列 V 被定义为 VARCHAR(50)。

传递了 VARCHAR(3),因为值 708 有 3 位数字。如果您要传递一个长度为 5 个字符的常量字符串,则参数大小将作为 VARCHAR(5) 传递。 该行为是设计解释的:SqlParameter.Size Property 如下:

"如果没有显式设置,则根据实际大小推断大小 指定的参数值。”

我实际上正在寻找一种方法来防止这种情况发生。因此,作为参数传递的可变长度数据类型的每个组合都会强制生成一个执行计划,这会导致 Sql Server 中成千上万个类似的执行计划被编译消耗 CPU 时间并阻塞大量 RAM 被缓存,并且永远不会重新-用过的。

如果不完全重新设计此应用程序的核心代码,如何影响这种行为? 我想在这里做的是不仅使用 CommandBuilder 从原始表中获取 TYPES,而且还获取 SIZE,但似乎无法获取此信息。

【问题讨论】:

  • 使用 SqlDataAdapter 的 FillSchema 方法将为您提供列大小,但在我的测试中这没有帮助。即使将这些MaxLength 值映射回更新命令的参数,查询计划仍然显示varchar 列的长度是推断出来的。
  • 是的,这也是我的发现,同时我也采用了这种方法,但是 dataadapter 的 UPDATE 方法“破坏”了事先准备好的一切......

标签: c# sql-execution-plan sqlcommand sqldataadapter sqlcommandbuilder


【解决方案1】:

我想我找到了答案,所以我会在这里发布(格式化代码的额外空间会有所帮助)。答案似乎是FillSchema 和处理DataAdapter 的RowUpdating 事件的组合。因此,正如我们在上面的 cmets 中简要讨论的那样,使用 da.FillSchema(ds, SchemaType.Source); 来获取列大小。然后,为 DataAdapter 的 RowUpdating 事件添加一个处理程序,并在那里设置更新命令参数的列大小。像这样的:

编辑:包括一个更完整的代码示例供参考:

public static void Main(string[] args)
{
    var cn = new SqlConnection("Data Source=.; Initial Catalog=TestDB; Integrated Security=SSPI");
    var da = new SqlDataAdapter("SELECT * FROM dbo.test", cn);
    var cb = new SqlCommandBuilder(da);
    cb.ConflictOption = System.Data.ConflictOption.OverwriteChanges;

    var ds = new DataSet();
    da.Fill(ds);
    da.FillSchema(ds, SchemaType.Source);

    ds.Tables[0].Rows[0]["v"] = DateTime.UtcNow.Millisecond.ToString();
    da.RowUpdating += new SqlRowUpdatingEventHandler(da_RowUpdating);
    da.Update(ds);
}

static void da_RowUpdating(object sender, SqlRowUpdatingEventArgs e)
{
    foreach (var p in e.Command.Parameters.Cast<SqlParameter>())
    {
        p.Size = e.Row.Table.Columns[p.SourceColumn].MaxLength;
    }
}

这是我在分析器中看到的屏幕截图:

【讨论】:

  • 我会在星期四试试这个并给你反馈。谢谢。
  • 我尝试了你的建议。它基本上与我已经尝试过使用 par.Size = ds.Tables[0].Columns[par.SourceColumn].MaxLength;在主要方法中。这也是您的解决方案成功调整所有参数大小的原因。但不幸的是,dataadapter.Update 方法仍然改变了所有这一切,它以在 SQL Server 中执行的最终 sp_executesql 命令中灵活的、依赖于浓度的数据类型大小的不良结果而告终。所以,很遗憾,它并没有解决问题。
  • 奇怪。它似乎对我有用。我已经更新了上面答案中的代码,以提供对我正在做的事情的更完整的参考。也许您可以发现我们正在做的不同的事情,或者也许其他人可以尝试重现结果。
  • 是的,您和我的主要区别在于我忘记了以下行:da.RowUpdating += new SqlRowUpdatingEventHandler(OnRowUpdating);我没有注意到,因为 thiseventhandler 内部的断点仍然停止/工作,但最终类型大小根据上面的行而有所不同。没有这一行它是 varchar(len of value),它是 varchar(maxlenofcolumn)。奇怪,我不明白为什么,但毕竟你的答案是正确的!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-04
相关资源
最近更新 更多