SQLBulkCopy avec CLR UDT donne "Impossible de find la méthode 'Read' pour le type 'MyNamespace.MyType' dans l'assembly 'MyType'"

J'ai écrit un SQL Server CLR défini par l'user (UDT) dans SQL Server 2012. J'ai été en mesure d'y accéder par des scripts de test SQL, et l'ai utilisé comme variable locale, défini dans une table, et testé à travers Visual Studio et SQL Server Management Studio.

Nous avons un service qui utilise SQLBulkCopy de manière assez générale pour récupérer les files placés dans un directory, puis insert leur contenu dans la table appropriée. Lorsque j'ajoute mon UDT en tant que colonne dans l'une de ces tables, je reçois une erreur de l'appel WriteToServer (DataTable).

La colonne UDT est transmise en tant que System.Ssortingng, dans l'espoir que la méthode Parse () de l'UDT sera appelée dans SQL Server pour la convertir en type interne. J'ai également essayé de déclarer la class UDT dans ce programme client, et de passer datatables directement au type UDT.

Dans les deux cas, je reçois ce message d'erreur (édité pour supprimer mes noms propriétaires)

Impossible de find la méthode 'Lire' pour le type 'MyNamespace.MyType' dans l'assembly 'MyType'

J'ai passé en revue autant de questions similaires que je peux find à propos de ce message d'erreur, et elles se réfèrent généralement au format de l'instruction CREATE. En outre, ils se réfèrent généralement aux fonctions CLR, et non aux types CLR, qui sont légèrement différents. C'est à moi:

CREATE TYPE [dbo]. [MyType]
NOM EXTERNE [MyType]. [MyNamespace.MyType]

Je suspecte que ceci ne soit pas le problème, et que, au contraire, cela a à voir avec la façon dont SQLBulkCopy interagit avec un UDT SQLCLR. Pour cette combinaison particulière, il est difficile de find une explication en profondeur.

Édition # 1 – C'est la serialization personnalisée.

[Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType( Format.UserDefined, MaxByteSize = -1 )] public struct MyType: INullable, IBinarySerialize 

Édition # 2 – L'autorisation d'exécution est accordée

 GRANT EXECUTE ON TYPE :: MyType TO PUBLIC 

Édition # 3 – code de test adapté

 CREATE TABLE [dbo].[TestMyType] ( [SourceMachine] [varchar](32) NULL, [Output] MyType NULL ) 

et mis à jour par

 try { DataTable dataTable = new DataTable( "[TestMyType]" ); dataTable.Columns.Add( "SourceMachine", typeof( System.Ssortingng ) ); dataTable.Columns.Add( "Output", typeof( MyNamespace.MyType ) ); dataTable.Rows.Add( "Ron1", MyNamespace.MyType.Parse( "This is ssortingng 1" ) ); dataTable.Rows.Add( "Ron2", MyNamespace.MyType.Parse( "This is ssortingng 2" ) ); dataTable.Rows.Add( "Ron3", MyNamespace.MyType.Parse( "This is ssortingng 3" ) ); SqlBulkCopy sqlBulkCopy = new SqlBulkCopy( conn ); sqlBulkCopy.DestinationTableName = "[TestMyType]"; sqlBulkCopy.WriteToServer( dataTable ); } catch ( Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); throw; } 

Cela a donné le même message d'erreur que ci-dessus.

Edit # 4 – Éliminer SqlBulkCopy du problème
J'ai recréé le problème en utilisant un INSERT paramétré. Je l'ai configuré pour passer l'object UDT du client au server en tant que paramètre qui utilise directement une instance de l'UDT.

 ssortingng 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(); 

donnant

 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 devrait être capable de gérer les UDT de SQLCLR (Types définis par l'user). J'ai réussi à utiliser les deux methods DbDataReader et DataTable .

Voici ce qui a fonctionné pour moi:

C # code (j'ai fait le "client" une procédure stockée 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, SqlSsortingng TheConnectionSsortingng) { 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(TheConnectionSsortingng.Value); _BulkCopy.DestinationTableName = "dbo.BulkCopyUDT"; try { _BulkCopy.WriteToServer(_DataTable); } finally { _BulkCopy.Close(); } } } 

Code 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, @TheConnectionSsortingng 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 

Le test

 EXEC dbo.SqlBulkCopy_Test 1; SELECT *, SomeData.ToSsortingng() FROM dbo.BulkCopyUDT; EXEC dbo.SqlBulkCopy_Test 3, N'Data Source=(local); User=test; Password=test; Initial Catalog=my_database;'; SELECT *, SomeData.ToSsortingng() FROM dbo.BulkCopyUDT; 

J'ai également obtenu ce travail à partir d'une application console, en utilisant à la fois SqlBulkCopy et une requête ad hoc paramétrée:

 using System; using System.Data; using System.Data.SqlClient; namespace SqlBulkCopyUDT { class Program { static void Main(ssortingng[] args) { int _TheID = Int32.Parse(args[0]); ssortingng _TheConnectionSsortingng = @"Data Source=(local); Integrated Security=true; Initial Catalog=my_database;"; if (args.Length > 1) { _TheConnectionSsortingng = 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(_TheConnectionSsortingng); //_BulkCopy.DestinationTableName = "dbo.BulkCopyUDT"; //try //{ // _BulkCopy.WriteToServer(_DataTable); //} //finally //{ // _BulkCopy.Close(); //} using (SqlConnection _Connection = new SqlConnection(_TheConnectionSsortingng)) { 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 Si vous envoyez datatables directement à une colonne UDT, alors il doit être sous forme binary car c'est le seul moyen que SqlBulkCopy transporte, selon le code source .

J'ai utilisé une notation d'interface explicite sur deux methods dans l'UDT, comme ceci.

 void IBinarySerialize.Read( BinaryReader r ) { } void IBinarySerialize.Write( BinaryWriter w ) { } 

Mais ils devaient être définis comme ceci:

 public void Read( BinaryReader r ) { } public void Write( BinaryWriter w ) { } 

La différence était suffisante pour empêcher SQL Server d'identifier la méthode correcte à utiliser sur l'UDT lors de SqlBulkCopy et paramétrer INSERT lors de la transmission de l'object complet MyType.

Le problème a commencé lorsque j'ai utilisé Visual Studio pour append les routines stub qui implémentaient l'interface IBinarySerialize . J'ai cliqué avec le button droit sur le nom de l'interface en haut de la définition de la struct et j'ai choisi "implémenter l'interface de manière explicite". J'aurais dû sélectionner "Implement Interface", pour générer les stubs de la méthode sans les qualificatifs.