I have written a SQL Server CLR User-defined type (UDT) in SQL Server 2012. I have been able to access it though SQL test scripts, and have used it as a local variable, defined it in a table, and tested it through Visual Studio and SQL Server Management Studio.
We have an service that uses SQLBulkCopy in a fairly generalized fashion to pick up files that are placed in a directory, then insert their contents to the appropriate table. When I add my UDT as a column in one of those tables, I receive an error from the WriteToServer( DataTable ) invocation.
The UDT column is being passed as a System.String, in the hope that the UDT's Parse() method will be called within SQL Server to convert it to the internal type. I have also tried declaring the UDT class within this client program, and passing the data as the UDT type directly.
In either case I receive this error message (edited to take out my proprietary names)
Could not find method 'Read' for type 'MyNamespace.MyType' in assembly 'MyType'
I have reviewed as many similar questions that I can find about this error message, and they generally refer to the format of the CREATE statement. Also, they generally refer to CLR functions, not CLR types, which are slightly different. This is mine:
CREATE TYPE [dbo].[MyType]
EXTERNAL NAME [MyType].[MyNamespace.MyType]
I suspect this might not be the issue, and that, instead, it has to do with how SQLBulkCopy interacts with a SQLCLR UDT. For this particular combination it's difficult to find any in-depth explanation.
Edit #1 - It is custom serialization.
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType( Format.UserDefined, MaxByteSize = -1 )]
public struct MyType: INullable, IBinarySerialize
Edit #2 - Execute permission is granted
GRANT EXECUTE
ON TYPE :: MyType
TO PUBLIC
Edit #3 - adapted testing code
CREATE TABLE [dbo].[TestMyType]
(
[SourceMachine] [varchar](32) NULL,
[Output] MyType NULL
)
and updated by
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;
}
This gave the same error message that is shown above.
Edit #4 - Eliminate SqlBulkCopy from the issue
I have recreated the issue using a parameterized INSERT. I set it up to pass the UDT object from the client to the server as a parameter that directly uses an instance of the 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();
giving
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'