Passing Oracle UDT to stored procedure throws error on ExecuteNonQuery - c#

I've been hitting a wall over this for the last two days. In essence I have created UDT using Oracle tools for Visual Studio (ODAC 12c Release 3 and Oracle Developer Tools for Visual Studio (12.1.0.2.1)), added needed attributes.
The object I'm passing is an UDT object, having a collection of UDT which wraps around some properties.
As good as it works when pulling data from database it throws a fit when trying to pass UDT object as a parameter to stored procedure.
I checked all that came to mind:
- types;
- command configuration: type set to OracleType.Object (tried with array - no change);
And here is the error with the call stack:
"The invoked member is not supported in a dynamic assembly."
at System.Reflection.Emit.InternalAssemblyBuilder.get_Location()
at Oracle.DataAccess.Types.OracleUdt.GetAllReferencedAssemblies()
at
Oracle.DataAccess.Client.RegAndConfigRdr.setudtmapping(Hashtable&
s_mapUdtNameToMappingObj)
at Oracle.DataAccess.Types.OracleUdt.SetCustomTypeMappings()
at Oracle.DataAccess.Types.OracleUdt.GetUdtName(String
customTypeName, String dataSource)
at
Oracle.DataAccess.Client.OracleParameter.SetUDTFromCustomObject(OracleConnection
conn, IOracleCustomType customObj, Int32 i)
at
Oracle.DataAccess.Client.OracleParameter.PreBind_OracleObject(OracleConnection
conn)
at
Oracle.DataAccess.Client.OracleParameter.PreBind_Object(OracleConnection
conn)
at
Oracle.DataAccess.Client.OracleParameter.PreBind(OracleConnection
conn, IntPtr errCtx, Int32 arraySize)
at Oracle.DataAccess.Client.OracleCommand.ExecuteNonQuery()
at OracleDatabaseHelper.OracleCommandEx.ExecuteNonQuery()

Related

BinaryFormatter used in Interop assemblies throws UnsupportedException

Microsoft discourages the use of BinaryFormatter because it poses security problems. See: BinaryFormatter Obsoletion Strategy.
I have a .NET 6.0 WinForms code which uses the Microsoft.Office.Interop.Access.Dao interop assembly. I need it to insert an image into the Data field the Microsoft Access' system table MSysResources. This field has an Attachment Data Type. This is a multi-valued field. Using DAO is the only way of writing to this field. My (somewhat shortened) code goes like this (note: this code did work before I migrated to .NET 6.0):
using Microsoft.Office.Interop.Access.Dao;
namespace CySoft.RibbonPro.Services;
public class AccessImageResourceLoader : IAccessImageResourceLoader
{
public void UpdateImages(string accdbFile, IEnumerable<KeyValuePair<string, Image>> images)
{
var dbe = new DBEngine(); // <====== This line throws the UnsupportedException =====
Database db = dbe.OpenDatabase(accdbFile);
Recordset rs = rs = db.OpenRecordset("SELECT * FROM MSysResources WHERE 0=1", R
ecordsetTypeEnum.dbOpenDynaset, 0, LockTypeEnum.dbOptimistic);
rs.AddNew();
rs.Fields["Type"].Value = "img";
rs.Fields["Name"].Value = name;
rs.Fields["Extension"].Value = ext;
Recordset2 rsAttachment = (Recordset2)rs.Fields["Data"].Value;
rsAttachment.AddNew();
Field2 dataField = (Field2)rsAttachment.Fields["FileData"];
dataField.LoadFromFile(imageInfo.Key);
rsAttachment.Update();
rs.Update();
rs.Close();
db.Close();
}
}
The details are for illustration only. The first code line creating the DBEngine throws the exception:
BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.
The call stack is:
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.DeserializeUsingBinaryFormatter(StreamWrapper wrappedStream, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.Deserialize(Stream o, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.RuntimeLicenseContext.GetSavedLicenseKey(Type type, Assembly resourceAssembly)
at System.ComponentModel.LicenseManager.LicenseInteropHelper.GetCurrentContextInfo(Type type, Boolean& isDesignTime, String& key)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Internal.Runtime.InteropServices.LicenseInteropProxy.GetCurrentContextInfo(RuntimeTypeHandle rth, Boolean& isDesignTime, IntPtr& bstrKey)
at CySoft.RibbonPro.Services.AccessImageResourceLoader.UpdateImages(String accdbFile, IEnumerable`1 images) in C:\Users\Oli\Documents\Proj\CySoft\CySoft.RibbonPro\CySoft.RibbonPro\Services\AccessImageResourceLoader.cs:line 21
Where AccessImageResourceLoader.cs:line 21 is var dbe = new DBEngine();
Microsoft wants people to use another type of serialization like JSON or XML. This is not an option in this case, because I am not using it directly. It is Microsoft's own COM library which uses it.
Question:
How can I insert or update a record using Access' Attachment data type in .NET 6+?
My Attempts
I have tried to do it with System.Data.OleDb. I can read the Attachment with OleDb. But any attempt to write to this field using OleDb throws an exception.
Setting the <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization> tag in the project file does not help.
Settings the same configuration property in runtimeConfig.template.json does not help either.
I know that I could solve the problem by using Access automtation via an interop assembly. But it has the disadvantage to open the Microsoft Access application. Inserting the image through a database connection is much more elegant and did work before I migrated to .NET 6.0.
You can see here there is a switch to allow the binary serializer for the licenses file
https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContextSerializer.cs#L20
which is being read by the GetSavedLicenseKey method here
https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContext.cs#L84-L89
You can set this switch earlier on before initializing the DBEngine object by calling this:
AppContext.SetSwitch("System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization", true);
I haven't tried it myself but it should work.
This runtime switch might also be settable in the csproj file as described here
https://github.com/dotnet/runtime/blob/main/docs/workflow/trimming/feature-switches.md
Any feature-switch which defines property can be set in csproj file or on the command line as any other MSBuild property. Those without predefined property name the value can be set with following XML tag in csproj file.
<RuntimeHostConfigurationOption Include="<AppContext-Setting>"
Value="false"
Trim="true" />
Final words: There is even more detail on upgrading to .NET 6.0 at this blog which has another method for this flag explained.
https://www.textcontrol.com/blog/2021/12/21/migrate-a-windows-forms-desktop-application-to-dotnet-6/?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral

.NET MySql ArgumentNullException on Open in GetCustomAttributes

I'm trying to swap database providers out in .NET code to MariaDB. As part of that, I'm trying to open a MySqlConnection. This in turn results in an argument null execption with a message of: "Value cannot be null. Parameter name: element" and the following call stack:
mscorlib.dll!System.Attribute.GetCustomAttributes(System.Reflection.Assembly element, System.Type attributeType, bool inherit) Line 756 C#
mscorlib.dll!System.Attribute.GetCustomAttribute(System.Reflection.Assembly element, System.Type attributeType, bool inherit) Line 814 C#
mscorlib.dll!System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Runtime.Versioning.TargetFrameworkAttribute>(System.Reflection.Assembly element) Line 27 C#
> MySql.Data.dll!MySql.Data.MySqlClient.MySqlConnectAttrs.InitFramework() Unknown
MySql.Data.dll!MySql.Data.MySqlClient.MySqlConnectAttrs.MySqlConnectAttrs() Unknown
[Native to Managed Transition]
[Managed to Native Transition]
MySql.Data.dll!MySql.Data.MySqlClient.NativeDriver.SetConnectAttrs() Unknown
MySql.Data.dll!MySql.Data.MySqlClient.Authentication.MySqlAuthenticationPlugin.Authenticate(bool reset) Unknown
MySql.Data.dll!MySql.Data.MySqlClient.NativeDriver.Authenticate(string authMethod, bool reset) Unknown
MySql.Data.dll!MySql.Data.MySqlClient.NativeDriver.Open() Unknown
MySql.Data.dll!MySql.Data.MySqlClient.Driver.Open() Unknown
MySql.Data.dll!MySql.Data.MySqlClient.Driver.Create(MySql.Data.MySqlClient.MySqlConnectionStringBuilder settings) Unknown
MySql.Data.dll!MySql.Data.Failover.FailoverManager.AttemptConnection(MySql.Data.MySqlClient.MySqlConnection connection, string originalConnectionString, out string connectionString, bool mySqlPoolManager) Unknown
MySql.Data.dll!MySql.Data.MySqlClient.MySqlConnection.Open() Unknown
(My code below this point)
I'm using a connection string similar to:
<add name="MyMariaDatabase" connectionString="server=myservername.rds.amazonaws.com;port=3306; database=mydb;uid=myuser; pwd=mypassword" providerName="MySql.Data.MySqlClient" />
Sample connection code:
using (var client = new MySql.Data.MySqlClient.MySqlConnection(connStr))
{
client.Open();
var result = client.ExecuteScalar(someSqlStatement);
}
This is all running in .NET Framework 4.7.2 and MySQL.Data 8.0.19.
Any idea what's going wrong? The error seems deep inside the framework and it's not giving me helpful information.
You're encountering bug 95242, a known problem in Oracle's MySQL Connector/NET (aka MySql.Data).
I would recommend switching to MySqlConnector, an OSS MySQL and MariaDB client library. As well as fixing many bugs in Connector/NET and adding true async I/O support, it is independent of Oracle so it has support for MariaDB-specific features such as the GSSAPI authentication plugin and batch support.
Your Port must be seperated by semicolon.
Instead of
"server=myservername.rds.amazonaws.com,3306; database=mydb;uid=myuser; pwd=mypassword"
use this Connenction string
"Server=myservername.rds.amazonaws.com;Port=3306;Database=mydb;Uid=myuser;Pwd=mypassword;"

Integration of a c# class library in SQL Server

I am trying to test the functionality of CLR Functions in SQL Server 2012. I found a tutorial online on how to basically do this and got it to work on my server.(https://www.skylinetechnologies.com/Blog/Skyline-Blog/March-2013/CLR-Functions-in-SQL-Server-A-Tutorial)
Now, i wanted to create a function that doesnt return a table but a string instead. In my understanding, the SQL Server needs some kind of object to work with, so i tried it with the following test method:
public static class TestSingleValue
{
[SqlFunction(DataAccess = DataAccessKind.None, FillRowMethodName = "MyFillRowMethod", IsDeterministic = true)]
public static SqlChars Test123()
{
SqlChars test = new SqlChars("teststring");
return test;
}
}
On the SQL server, i did the following:
ALTER ASSEMBLY ClassLibrary2 from 'D:\SQL\TestCLR\ClassLibrary2.dll' with Permission_set = SAFE
CREATE FUNCTION TestCLR()
returns nvarchar(max)
AS
EXTERNAL name ClassLibrary2.[CLRTest.TestSingleValue].Test123
GO
Execute TestCLR
The SQL Server throws an error when executing the test method, saying that an "Object reference not set to an instance of an object" and further:
System.NullReferenceException:
System.Data.SqlServer.Internal.ClrLevelContext.GetCurrentContextForLobAccess(>>CClrLobContext* pLobContext)
System.Data.SqlServer.Internal.ClrLevelContext.GetXvarWlobStream(CXVariantBasepxvarSource, XvarLOBStreamInitCode eCode, Int64 lcid, SqlCompareOptions compareOpts, CClrLobContext pLobContext).
Can anyone tell me where i got the concept wrong or maybe provide a link to a good tutorial? I couldnt find one until now. Thanks in advance.
Ok i found the answer myself, the problem is with "nvarchar(max)" as return type. You got to define a length to the nvarchar or use a workaround, then it works just fine.
Related: How to create CLR stored procedure with Nvarchar(max) parameter?

Method not found SetDataTypeProperties in custom SSIS component

I'm trying to build a simple custom SSIS component which looks at a single input column and validates it, creating an output column of type bool depending on the value of each row.
I've successfully built an even simpler component that takes a value and transforms it: that doesn't require fiddling with the output columns. In this instance I need to take in a string and output a boolean and the component needs to know that it outputs a boolean so I can feed the value into a conditional split.
I'm struggling to add the output columns. Based on code samples from Microsoft, I have done this:
public override DTSValidationStatus Validate()
{
IDTSOutput100 output = ComponentMetaData.OutputCollection[0];
IDTSOutputColumn100 outputcol = output.OutputColumnCollection.New();
outputcol.Name = "IsValid";
outputcol.SetDataTypeProperties(DataType.DT_BOOL, 0, 0, 0, 0);
return DTSValidationStatus.VS_ISVALID;
}
And then I attempt to populate it during the ProcessInput step:
public override void ProcessInput(int inputID, PipelineBuffer buffer)
{
while (buffer.NextRow())
{
string str = buffer.GetString(0);
buffer.SetBoolean(0, IsValid(str)); // validation code not relevant
}
}
When I try to use this component in the package, I get this error:
The component has detected potential metadata corruption during validation.
Error at Data Flow Task [Uppercase [24]]: System.MissingMethodException: Method not found: 'Void Microsoft.SqlServer.Dts.Pipeline.Wrapper.IDTSOutputColumn100.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType, Int32, Int32, Int32, Int32)'.
at EmailValidation.Uppercase.Validate()
at Microsoft.SqlServer.Dts.Pipeline.ManagedComponentHost.HostValidate(IDTSManagedComponentWrapper100 wrapper)
Searching on this error message has yielded nothing of value.
In the original sample - and some other tutorials online - adding output columns is done by looping through the input column and adding an additional output for each. I have tried this and get the same error.
I have also tried moving the output column code from Validate to OnInputPathAttached which still yields the same error.
What am I doing wrong?
On investigation this appears to be a bug in SQL Server Data Tools for Visual Studio 2015. I have built, deployed and used a custom component with customised output columns in an Integration Services package in Visual Studio 2013. However, the same tool deployed in a package in 2015 causes the error described.
In case it's still relevant, I encountered a similar issue (With the ComponentMetaData property rather than SetDataTypeProperties), and the solution that worked for me was setting the Embed Interop Types property of the Microsoft.SqlServer.DTSPipelineWrap and Microsoft.SQLServer.DTSRuntimeWrap references to false.
I found this solution here. It's listed as a solution for getting an InvalidCastException, but it seems relevant for whenever you're referencing the DTSPipelineWrap or the DTSRuntimeWrap assemblies in custom components.

SQL CLR User Defined Function that returns Nullable<TimeSpan>

I'm trying to return a Nullable from a SQL CLR User Defined Function, but when i compile i get the following error:
CREATE FUNCTION [ReadCell_Time] (#rowData [xml], #columnName [nvarchar](4000))
RETURNS /* Error: Unsupported type. */
AS EXTERNAL NAME [UserDefinedFunctions].[ReadCell_Time];
GO
The code of my function is:
public static TimeSpan? ReadCell_Time(SqlXml rowData, SqlString columnName)
{
var data = rowData.GetValue(columnName);
return data.IsNull ? null : (TimeSpan?)TimeSpan.Parse(data.Value);
}
I'm using SQL Server 2008, Visual Studio 2013 and my project uses C# target .NET Framework 3.5 with UNSAFE permission.

Categories