Sitefinity feather multiple content items selectors exception in designer - sitefinity

I am using the sf-list-selector in my designer as shown below. I see my products list and I can select and sort.
<sf-list-selector sf-dynamic-items-selector sf-provider="properties.ProductProviderName.PropertyValue" sf-item-type="properties.ProductType.PropertyValue" sf-multiselect="true" sf-sortable="true" sf-master="true" sf-selected-ids="properties.ProductIds.PropertyValue" />
However I am getting an exception in the log file when I press save in the designer:
Requested URL :
https://localhost/Sitefinity/Services/Pages/ControlPropertyService.svc/batch/fc82280c-3055-6fae-9336-ff0000e88380/?pageId=230b270c-3055-6fae-9336-ff0000e88380&mediaType=0&propertyLocalization=0
Inner Exception --------------- Type : System.Xml.XmlException,
System.Xml, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089 Message : End element 'PropertyValue'
from namespace '' expected. Found element 'item' from namespace ''.
Source : System.Runtime.Serialization Help link : LineNumber : 0
LinePosition : 0 SourceUri : Data :
System.Collections.ListDictionaryInternal TargetSite : Void
ThrowXmlException(System.Xml.XmlDictionaryReader, System.String,
System.String, System.String, System.String) HResult : -2146232000
Stack Trace : at
System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader
reader, String res, String arg1, String arg2, String arg3)
at System.Xml.XmlExceptionHelper.ThrowEndElementExpected(XmlDictionaryReader
reader, String localName, String ns)
at System.Xml.XmlBaseReader.ReadEndElement()
at System.Xml.XmlBaseReader.ReadElementContentAsString()
at ReadWcfControlPropertyFromJson(XmlReaderDelegator , XmlObjectSerializerReadContextComplexJson , XmlDictionaryString ,
XmlDictionaryString[] )
at System.Runtime.Serialization.Json.JsonClassDataContract.ReadJsonValueCore(XmlReaderDelegator
jsonReader, XmlObjectSerializerReadContextComplexJson context)
at System.Runtime.Serialization.Json.XmlObjectSerializerReadContextComplexJson.ReadDataContractValue(DataContract
dataContract, XmlReaderDelegator reader)
at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator
reader, String name, String ns, Type declaredType, DataContract&
dataContract)
at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator
xmlReader, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle,
String name, String ns)
at ReadArrayOfWcfControlPropertyFromJson(XmlReaderDelegator , XmlObjectSerializerReadContextComplexJson , XmlDictionaryString ,
XmlDictionaryString , CollectionDataContract )
at System.Runtime.Serialization.Json.JsonCollectionDataContract.ReadJsonValueCore(XmlReaderDelegator
jsonReader, XmlObjectSerializerReadContextComplexJson context)
at System.Runtime.Serialization.Json.XmlObjectSerializerReadContextComplexJson.ReadDataContractValue(DataContract
dataContract, XmlReaderDelegator reader)
at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator
reader, String name, String ns, Type declaredType, DataContract&
dataContract)
at System.Runtime.Serialization.XmlObjectSerializerReadContextComplex.InternalDeserialize(XmlReaderDelegator
xmlReader, Type declaredType, DataContract dataContract, String name,
String ns)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalReadObject(XmlReaderDelegator
xmlReader, Boolean verifyObjectName)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator
reader, Boolean verifyObjectName, DataContractResolver
dataContractResolver)
I don't have a JSON or JS file for the view. When I use the variation of this for single item select all works good.

It turns out the value for the sf-selected-ids attribute needs to be in JSON array format. e.g. [ productId1, productId2, productId3]. Otherwise the backend service throws that exception. However, the selector by itself creates the string as product1, product2, product3. I.e. without the brackets. (You can see this in the advanced view of the designer).
So here are the detailed steps:
Here is the selector in the designer view (DesignerView.Simple.cshtml):
<sf-list-selector sf-dynamic-items-selector sf-provider="properties.ProductProviderName.PropertyValue" sf-item-type="properties.ProductType.PropertyValue" sf-multiselect="true" sf-sortable="true" sf-master="true" sf-selected-ids="productIds" />
You'll need the designer JS file to do the JSON conversion back and forth. So I save this in the MVC/Scripts/[WidgetName]/designer-simple.json: (Simple is the name of the designer view)
(function ($) {
var designerModule = angular.module('designer');
angular.module('designer').requires.push('sfSelectors');
designerModule.controller('SimpleCtrl', ['$scope', 'propertyService', function ($scope, propertyService) {
$scope.feedback.showLoadingIndicator = true;
propertyService.get().then(function (data) {
if (data) {
$scope.properties = propertyService.toAssociativeArray(data.Items);
}
},
function (data) {
$scope.feedback.showError = true;
if (data)
$scope.feedback.errorMessage = data.Detail;
}).finally(function () {
$scope.feedback.showLoadingIndicator = false;
});
$scope.$watch('properties.ProductIds.PropertyValue', function (newValue, oldValue) {
if (newValue) {
$scope.productIds = JSON.parse(newValue);
}
});
$scope.$watch('productIds', function (newValue, oldValue) {
if (newValue) {
$scope.properties.ProductIds.PropertyValue = JSON.stringify(newValue);
}
});
}]);
})(jQuery);
Lastly I added a DesignerView.Simple.json file in the same folder as DesignerView.Simple.cshtml:
{
"priority": 1,
"scripts": [
"client-components/selectors/common/sf-selected-items-view.js"
],
"components" : ["sf-dynamic-items-selector"]
}
The widget controller has a ProductIds property. Its values will be in format [productId1, productId2, etc.]. I used a JSON deserializer to get an array of products for the controller Index action:
public class ProductListController : Controller
{
private string productProviderName = WebConfigurationManager.AppSettings["productProviderName"];
private string productTypeName = WebConfigurationManager.AppSettings["productTypeName"];
public string ProductIds { get; set; }
public string ProductType
{
get { return productTypeName; }
set { productTypeName = value; }
}
public string ProductProviderName
{
get { return productProviderName; }
set { productProviderName = value; }
}
public ActionResult Index()
{
var selectedProducts = string.IsNullOrEmpty(this.ProductIds) ? new Guid[0] : JsonConvert.DeserializeObject<Guid[]>(this.ProductIds);
// ... rest of your controller index action
}
}

Related

How can I bind a comma separated list in a URL to an array of objects?

I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.

No MediaTypeFormatter is available to read an object of type 'FineUpload' from content with media type 'multipart/form-data'

I am using the FineUploader control with ASP.NET MVC 4 with Knockout.js and require.js. I have gotten the control to load properly without any errors, but when I click upload there is error uploading a file. Is there an extra step I am missing?
This is example I am trying to follow:
FineUploader Example
Here is the error captured for fiddler:
{"Message":"An error has occurred.","ExceptionMessage":"No MediaTypeFormatter is available to read an object of type 'FineUpload' from content with media type 'multipart/form-data'.","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger)\r\n at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger)\r\n at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger)\r\n at System.Web.Http.ModelBinding.FormatterParameterBinding.ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)\r\n at System.Web.Http.Controllers.HttpActionBinding.<>c__DisplayClass1.<ExecuteBindingAsync>b__0(HttpParameterBinding parameterBinder)\r\n at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()\r\n at System.Threading.Tasks.TaskHelpers.IterateImpl(IEnumerator`1 enumerator, CancellationToken cancellationToken)"}
Here is the knockout.bindings.js file that loads the control:
knockout.bindinds.js
define('knockout.bindings', ['ko', 'moment', 'fu', 'toastr'], function (ko, moment, fu, toastr) {
ko.utils.contains = function (string, startsWith) {
string = string || "";
if (startsWith.length > string.length) return false;
return string.toLowerCase().indexOf(startsWith.toLowerCase()) >= 0;
};
ko.bindingHandlers.date = {
update: function (element, valueAccessor) {
var value = valueAccessor();
var date = moment(value());
var strDate = date.format('LL');
$(element).text(strDate);
}
};
ko.bindingHandlers.contractuploader = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).fineUploader({
request: { endpoint: '/api/UploadContract/UploadFile' }
})
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// This will be called once when the binding is first applied to an element,
// and again whenever the associated observable changes value.
// Update the DOM element based on the supplied values here.
}
};
});
Here is the controller:
UploadContractController.cs
public class UploadContractController : ApiController
{
[AcceptVerbs("GET", "POST")]
public FineUploaderResult UploadFile(FineUpload upload)
{
try
{
return new FineUploaderResult(true, new { extraInformation = 12345 });
}
catch (Exception ex)
{
throw;
}
}
}
Please let me know if any other information is needed!

How to transport a derived object as a base object when the contract specifies an interface

If a [ServiceContract] [OperationContract] specifies a return value that is an interface (IInterface) then I can return a Base object (Base : IInterface) or a Derived object (Derived : Base) using ServerKnownType attribute on the server interface.
If however I need to transport the Derived object as Base object (because Derived adds server side functionality I dont need client side) then Im stuck.
The [DataContract(Name = "Base")] on the Derived class trick (see here) does NOT work in this case (where the server method returns an interface) because we must declare the Derived object AND the Base object as a KnownTypes - and when the server comes to resolve the types to serialize it doesn't like having 2 different known types with the same data contract name - and will thrown and exception when resolving.
How to tranport Derived as Base in this case?
You can used the following DataContractResolver to extend the use of [DataContract(Name = "Base")] as attribute on Derived to specify it should serialize as Base to work in the case where the server interface method returns an Interface not a concrete base type.
To use it the Base must be declared as a KnownType (ServerKnownType) and the Derived must have the [DataContract(Name = "Base")] attribute but NOT be declared a KnownType.
public class DeserializeAsBaseResolver : DataContractResolver {
public static void Install(ServiceHost serviceHost) {
SetSerializationBehavior(serviceHost, "MethodReturningIInterfac");
}
public static void SetSerializationBehavior(ServiceHost serviceHost, string contractMethodName) {
ContractDescription cd = serviceHost.Description.Endpoints[0].Contract;
OperationDescription myOperationDescription = cd.Operations.Find(contractMethodName);
bool methodFound = (myOperationDescription != null);
if (!methodFound) {
string msg = string.Format("\"{0}\" not a valid method on {1}",
contractMethodName, cd.ConfigurationName);
throw new ArgumentException(msg);
}
DataContractSerializerOperationBehavior serializerBehavior =
myOperationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (serializerBehavior == null) {
serializerBehavior = new DataContractSerializerOperationBehavior(myOperationDescription);
myOperationDescription.Behaviors.Add(serializerBehavior);
}
serializerBehavior.DataContractResolver = new DeserializeAsBaseResolver();
}
public override bool TryResolveType(Type type, Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace) {
// Look for [DataContract(Name = "ClassType")] attribute and serialize as that
// type if one is found:
Type typeToResolveOn = type;
DataContractAttribute dca = null;
// .Net 4.0 and below code:
object[] attributes = type.GetCustomAttributes(typeof(DataContractAttribute), false);
foreach (object attibute in attributes) {
if (attibute is DataContractAttribute) {
dca = attibute as DataContractAttribute;
break;
}
}
// .Net 4.5 and above code:
//dca = type.GetCustomAttribute<DataContractAttribute>();
if (dca != null && !string.IsNullOrEmpty(dca.Name)) {
string modifiedAssemblyQualifiedName = string.Format("{0}.{1}, {2}", type.Namespace, dca.Name, type.Assembly);
Type serializeAsType = Type.GetType(modifiedAssemblyQualifiedName);
if (serializeAsType != null)
typeToResolveOn = serializeAsType;
}
bool ret = knownTypeResolver.TryResolveType(typeToResolveOn, declaredType, null, out typeName, out typeNamespace);
if (!ret && type != typeToResolveOn)
ret = knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
return ret;
}
public override Type ResolveName(string typeName, string typeNamespace,
Type declaredType, DataContractResolver knownTypeResolver) {
return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
}
}
}

JsonSerializationException for type with private constructor

I am persisting NLog logging statements to my RavenDb database. The LogEventInfo class, which represents a log statement, has a property, LogLevel, with a private constructor. Instances of LogLevel (Info, Warn, etc.) are created via static, readonly properties that call the private constructor.
The problem is that I wish to read the messages out of the database and querying for them is throwing a Json.Net serialization error:
Unable to find a constructor to use for type NLog.LogLevel. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'Level.Name'.
How can I get around the error? Could creating some kind of Raven index help here?
I think the easiest way to do this, is to create a custom class to hold all the log information.
It worked with me like this
create a CustomCreationConverter and use it with your object's deserialization
public class EventInfoConverter : CustomCreationConverter<LogEventInfo>
{
private string _level { get; set; }
public EventInfoConverter(string s)
{
JToken eventinfo = JObject.Parse(s);
var childs = eventinfo.Children();
foreach (var item in childs)
{
if (((Newtonsoft.Json.Linq.JProperty)item).Name == "Level")
{
var m = ((Newtonsoft.Json.Linq.JProperty)item).Value.Children();
foreach (var item1 in m)
{
_level = ((Newtonsoft.Json.Linq.JProperty)item1).Value.ToString();
break;
}
break;
}
}
}
public override LogEventInfo Create(Type objectType)
{
LogEventInfo eventInfo = new LogEventInfo();
switch (_level)
{
case "Info":
eventInfo = new LogEventInfo(LogLevel.Info, "", "");
break;
case "Debug":
eventInfo = new LogEventInfo(LogLevel.Debug, "", "");
break;
case "Error":
eventInfo = new LogEventInfo(LogLevel.Error, "", "");
break;
case "Warn":
eventInfo = new LogEventInfo(LogLevel.Warn, "", "");
break;
default:
break;
}
return eventInfo;
}
}
In your deserialization:
NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
_logger.Log(Newtonsoft.Json.JsonConvert.DeserializeObject<LogEventInfo>(eventInfo, new EventInfoConverter(eventInfo)));

Fluent NHibernate Mapping XDocument property to Oracle XMLType

I am looking for the best way to map from an XDocument property type to an Oracle XMLType? I am mapping to a legacy database and have no control over the schema. It is Oracle 9i.
I have read that version 3 of nHibernate provides out the box functionality for this type of mapping. I am using version 3.1 with fluent mappings and I receive the following error when using the default map on a create:
System.ArgumentOutOfRangeException : Specified argument was out of the range of valid values.
at Oracle.DataAccess.Client.OracleParameter.set_DbType(DbType value)
at NHibernate.Driver.DriverBase.SetCommandParameters(IDbCommand cmd, SqlType[] sqlTypes) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Driver\DriverBase.cs: line 180
at NHibernate.Driver.DriverBase.GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Driver\DriverBase.cs: line 136
at NHibernate.AdoNet.AbstractBatcher.Generate(CommandType type, SqlString sqlString, SqlType[] parameterTypes) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\AdoNet\AbstractBatcher.cs: line 78
at NHibernate.AdoNet.AbstractBatcher.PrepareBatchCommand(CommandType type, SqlString sql, SqlType[] parameterTypes) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\AdoNet\AbstractBatcher.cs: line 146
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs: line 2616
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs: line 3050
at NHibernate.Action.EntityInsertAction.Execute() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Action\EntityInsertAction.cs: line 59
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs: line 136
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs: line 125
at NHibernate.Engine.ActionQueue.ExecuteActions() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs: line 170
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs: line 241
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultFlushEventListener.cs: line 20
at NHibernate.Impl.SessionImpl.Flush() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs: line 1470
I got round this my writing my own user type which converts between an XDocument and a string:
public SqlType[] SqlTypes
{
get { return (new SqlType[] { new StringClobSqlType() }); }
}
public Type ReturnedType
{
get { return (typeof(XDocument)); }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
XDocument xDoc = null;
int columnIndex = rs.GetOrdinal(names[0]);
if (!rs.IsDBNull(columnIndex))
{
xDoc = XDocument.Parse((rs[columnIndex].ToString()));
}
return (xDoc);
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
IDbDataParameter parameter = (IDbDataParameter)cmd.Parameters[index];
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
XDocument xDoc = (XDocument)value;
parameter.Value = xDoc.ConvertToString();
}
}
This worked fine until the string was greater than 4000 characters in length. Now I get the error:
NHibernate.Exceptions.GenericADOException : could not insert: [XmlBlob#95586][SQL: INSERT INTO XMLBLOB (CAT_CODE, BLB_BLOB, BLB_ID) VALUES (?, ?, ?)]
----> Oracle.DataAccess.Client.OracleException : ORA-01461: can bind a LONG value only for insert into a LONG column
The only update I needed to be able to save more than 4000 characters to the XMLType column was to set the parameter type as OracleDbType.XmlType in the set method for my user type implementation:
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
IDbDataParameter parameter = (IDbDataParameter)cmd.Parameters[index];
((OracleParameter)parameter).OracleDbTypeEx = OracleDbType.XmlType;
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
XDocument xDoc = (XDocument)value;
parameter.Value = xDoc.ConvertToString();
}
}