我在SQL Server 2012中编写了一个SQL Server CLR用户定义类型(UDT)。我已经能够通过SQL测试脚本访问它,并将其用作局部变量,在表中定义它,并通过它测试它Visual Studio和SQL Server Management Studio。
我们有一个服务,它以相当通用的方式使用SQLBulkCopy来获取放在目录中的文件,然后将它们的内容插入到适当的表中。当我在其中一个表中添加UDT作为列时,我收到来自WriteToServer(DataTable)调用的错误。
UDT列作为System.String传递,希望在SQL Server中调用UDT的Parse()方法将其转换为内部类型。我也试过在这个客户端程序中声明UDT类,并直接将数据作为UDT类型传递。
在任何一种情况下,我收到此错误消息(编辑以取出我的专有名称)
无法在程序集“MyType”中找到类型“MyNamespace.MyType”的方法“Read”
我已经查看了有关此错误消息的许多类似问题,它们通常是指CREATE语句的格式。此外,它们通常是指CLR函数,而不是CLR类型,它们略有不同。这是我的:
CREATE TYPE [dbo]。[MyType]
外部名称[MyType]。[MyNamespace.MyType]
我怀疑这可能不是问题,相反,它与SQLBulkCopy如何与SQLCLR UDT交互有关。对于这种特殊的组合,很难找到任何深入的解释。
编辑#1 - 这是自定义序列化。
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType( Format.UserDefined, MaxByteSize = -1 )]
public struct MyType: INullable, IBinarySerialize
编辑#2 - 授予执行权限
GRANT EXECUTE
ON TYPE :: MyType
TO PUBLIC
编辑#3 - 改编的测试代码
CREATE TABLE [dbo].[TestMyType]
(
[SourceMachine] [varchar](32) NULL,
[Output] MyType NULL
)
并更新
try
{
DataTable dataTable = new DataTable( "[TestMyType]" );
dataTable.Columns.Add( "SourceMachine", typeof( System.String ) );
dataTable.Columns.Add( "Output", typeof( MyNamespace.MyType ) );
dataTable.Rows.Add( "Ron1", MyNamespace.MyType.Parse( "This is string 1" ) );
dataTable.Rows.Add( "Ron2", MyNamespace.MyType.Parse( "This is string 2" ) );
dataTable.Rows.Add( "Ron3", MyNamespace.MyType.Parse( "This is string 3" ) );
SqlBulkCopy sqlBulkCopy = new SqlBulkCopy( conn );
sqlBulkCopy.DestinationTableName = "[TestMyType]";
sqlBulkCopy.WriteToServer( dataTable );
}
catch ( Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
throw;
}
这给出了上面显示的相同错误消息。
编辑#4 - 从问题中消除SqlBulkCopy
我使用参数化INSERT重新创建了该问题。我将其设置为将UDT对象从客户端传递到服务器,作为直接使用UDT实例的参数。
string sInsert = "INSERT INTO TestMyType VALUES (?, ?)";
SqlCommand command = new SqlCommand(sInsert, conn);
SqlParameter parm1 = new SqlParameter("SourceMachine", "This is Machine 01");
SqlParameter parm2 = new SqlParameter("Output", MyNamespace.MyType.Parse( "This is INSERT 01" ) );
parm2.UdtTypeName = "MyType";
command.Parameters.Add(parm1);
command.Parameters.Add(parm2);
int nResult = command.ExecuteNonQuery();
给
A first chance exception of type 'System.Data.SqlClient.SqlException'
occurred in System.Data.dll
Additional information: Could not find method 'Read' for
type 'MyNamespace.MyType' in assembly 'MyType'
SqlBulkCopy
应该能够正常处理SQLCLR UDT(用户定义类型)。我成功使用了DbDataReader
和DataTable
方法。
这对我有用:
C#代码 (我使“客户端”成为SQLCLR存储过程)
using System;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
public class xtra
{
[SqlProcedure]
public static void BcpTest(SqlInt32 TheID, SqlString TheConnectionString)
{
System.Data.DataTable _DataTable = new System.Data.DataTable();
_DataTable.Columns.Add("ID", typeof(Int32));
_DataTable.Columns.Add("SomeDate", typeof(DateTime));
_DataTable.Columns.Add("SomeData", typeof(Type_HashTable));
Type_HashTable _Bob = Type_HashTable.Parse(@"testKey=testVal");
_DataTable.Rows.Add(TheID.Value, DateTime.Now, _Bob);
_DataTable.Rows.Add(TheID.Value + 1, DateTime.Now,
Type_HashTable.Parse(@"testKey2=testVal2"));
SqlBulkCopy _BulkCopy = new SqlBulkCopy(TheConnectionString.Value);
_BulkCopy.DestinationTableName = "dbo.BulkCopyUDT";
try
{
_BulkCopy.WriteToServer(_DataTable);
}
finally
{
_BulkCopy.Close();
}
}
}
T-SQL代码
-- DROP TABLE dbo.BulkCopyUDT;
CREATE TABLE dbo.BulkCopyUDT
(
ID INT NOT NULL CONSTRAINT [PK_BulkCopyUDT] PRIMARY KEY,
SomeDate DATETIME,
SomeData [SQL#].[Type_HashTable]
);
GO
GRANT INSERT, SELECT ON dbo.BulkCopyUDT TO [Public];
GRANT EXECUTE ON TYPE::SQL#.Type_HashTable TO [Public];
GO
CREATE PROCEDURE dbo.SqlBulkCopy_Test
(
@TheID INT,
@TheConnectionString NVARCHAR(4000) =
N'Data Source=(local); Integrated Security=true; Initial Catalog=my_database;'
)
AS EXTERNAL NAME [my_assembly].[xtra].[BcpTest];
GO
ALTER ASSEMBLY [my_assembly] WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO
考试
EXEC dbo.SqlBulkCopy_Test 1;
SELECT *, SomeData.ToString() FROM dbo.BulkCopyUDT;
EXEC dbo.SqlBulkCopy_Test 3,
N'Data Source=(local); User=test; Password=test; Initial Catalog=my_database;';
SELECT *, SomeData.ToString() FROM dbo.BulkCopyUDT;
我也使用SqlBulkCopy
和参数化的即席查询从Console App开始工作:
using System;
using System.Data;
using System.Data.SqlClient;
namespace SqlBulkCopyUDT
{
class Program
{
static void Main(string[] args)
{
int _TheID = Int32.Parse(args[0]);
string _TheConnectionString =
@"Data Source=(local); Integrated Security=true; Initial Catalog=my_database;";
if (args.Length > 1)
{
_TheConnectionString = args[1];
}
//DataTable _DataTable = new DataTable();
//_DataTable.Columns.Add("ID", typeof(Int32));
//_DataTable.Columns.Add("SomeDate", typeof(DateTime));
//_DataTable.Columns.Add("SomeData", typeof(Type_HashTable));
//Type_HashTable _Bob = Type_HashTable.Parse(@"testKey=testVal");
//_DataTable.Rows.Add(_TheID, DateTime.Now, _Bob);
//_DataTable.Rows.Add(_TheID + 1, DateTime.Now,
// Type_HashTable.Parse(@"testKey2=testVal2"));
//SqlBulkCopy _BulkCopy = new SqlBulkCopy(_TheConnectionString);
//_BulkCopy.DestinationTableName = "dbo.BulkCopyUDT";
//try
//{
// _BulkCopy.WriteToServer(_DataTable);
//}
//finally
//{
// _BulkCopy.Close();
//}
using (SqlConnection _Connection = new SqlConnection(_TheConnectionString))
{
using (SqlCommand _Command = _Connection.CreateCommand())
{
_Command.CommandType = CommandType.Text;
_Command.CommandText =
@"INSERT INTO dbo.BulkCopyUDT (ID, SomeDate, SomeData)
VALUES (@MyID, GETDATE(), @MyData);";
SqlParameter _ParamMyID = new SqlParameter("@MyID", SqlDbType.Int);
_ParamMyID.Value = _TheID;
_Command.Parameters.Add(_ParamMyID);
SqlParameter _ParamMyData = new SqlParameter("@MyData", SqlDbType.Udt);
_ParamMyData.UdtTypeName = "SQL#.Type_HashTable";
_ParamMyData.Value = Type_HashTable.Parse(@"testKey3=testVal3");
_Command.Parameters.Add(_ParamMyData);
_Connection.Open();
_Command.ExecuteNonQuery();
}
}
}
}
}
PS如果直接将数据发送到UDT列,则需要采用二进制形式,因为这是SqlBulkCopy
根据源代码传输数据的唯一方式。
我在UDT中的两个方法上使用了显式接口表示法,就像这样。
void IBinarySerialize.Read( BinaryReader r )
{
}
void IBinarySerialize.Write( BinaryWriter w )
{
}
但它们必须像这样定义:
public void Read( BinaryReader r )
{
}
public void Write( BinaryWriter w )
{
}
差异足以阻止SQL Server在SqlBulkCopy期间识别在UDT上使用的正确方法,并在传递完整的MyType对象时使用paramaterized INSERT。
当我使用Visual Studio添加实现IBinarySerialize
接口的存根例程时,问题就出现了。我右键单击struct
定义顶部的接口名称,然后选择“显式实现接口”。我应该选择“实现接口”,以生成没有限定符的方法存根。