- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
UDT Problem
Joined: 06-May-2008
Hi there,
Some questions about using a user defined type in MS SQL Server 2008 with LLBLGEN pro (v2.6):
LLBLGEN knows about the UDT (Mapped Entity Fields), it sets the .NET Type of the field to string.
Loading the generated Entity result in an InvalidCastException: my UdtSqlDatatype could not be converted to string. I tried it after implementing a cast operator, IConvertible, TypeConverter ... all with the same result.
Well, seems to be a common problem with typecasting...
-
string test = (string)((UdtSqlDatatype)dataAccess.ExecuteScalar(statement)); label1.Text = test;
-
UdtSqlDatatype udtSqlDatatype = (UdtSqlDatatype)dataAccess.ExecuteScalar(statement); label1.Text = udtSqlDatatype; // implicit cast operator in UdtSqlDatatype
-
string test2 = (string)dataAccess.ExecuteScalar(statement); label1.Text = test2;
where string statement = "select top 1 udtfield from Test";
The first two casts worked fine, but the third always fails...UdtSqlDatatype could not be converted to string.
Because the editor does not allow to change anything but "Is readonly" for this field (even after copying the assembly containing the UDT to the LLBLGEN pro directory) i tried to modify the entity manually.
After modifying FieldInfoProvider.cs in my generated code, i replaced the DataType string with my UDT type in AddElementFieldInfo, modified the entity's property like this
public virtual string UdtField
{ get { return GetValue((int)TestFieldIndex.UdtField, true).ToString(); } set { SetValue((int)TestFieldIndex.UdtField, UdtSqlDatatype.Parse(value)); } }
and the entity can be read and written. But now there was another problem... by changing the type in AddElementFieldInfo, the xml deserialization of the entity fails:
the entity is written with
entity.WriteXml(XmlFormatAspect.Compact25 | XmlFormatAspect.DatesInXmlDataType | XmlFormatAspect.MLTextInCDataBlocks, out xml);
and read with
entity.ReadXml(xmlNode.OuterXml)
now resulting in a null reference exception
Fehler Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
bei SD.LLBLGen.Pro.ORMSupportClasses.XmlHelper.XmlValueToObject(String typeName, String xmlValue)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader,
Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.Xml2EntityCollection(
XmlReader reader, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.SD.LLBLGen.Pro.ORMSupportClasses.
IEntityCollectionAccess2.Xml2EntityCollection(XmlReader reader,
Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader,
Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader,
Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(XmlReader reader, XmlFormatAspect format)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(String xmlData)
Any help is welcome..
best regards, yves
This should work without any manual intervention.
docs wrote:
SqlServer specific: User Defined Types support SqlServer 2005 supports User Defined Types (UDTs) written in a CLR language like C# or VB.NET. The SqlServer driver can read these fields and if you're using UDTs in your tables, the fields which have a UDT as their type will be read by the driver and their UDT type is considered their valid type. Entities mapped onto these tables (or views) have then fields which .NET type is equal to the UDT of the target field in the table/view they're mapped on. The generated entity classes will have properties inside them which refer to the UDT type as the type of the property, as the UDT is a normal CLR type. In such a situation you've to reference the assembly which contains the UDT in your generated code Visual Studio.NET project. (For Adapter, the database generic project). Usage of the field in .NET code is like any other code: you can set the field to an instance of the UDT type and normally save it and load it.
To be able to re-construct the UDT type, the driver has to be able to resolve the type. This is only possible if the assembly with the UDT is known on the system on which you run the LLBLGen Pro designer. So place the assembly which contains the UDT into the GAC and retry. The SQLServer driver will then be able to resolve the UDT and the table with a field of that type will be accepted.
To diagnose UDT related problems, look at the application output window docked at the bottom of hte designer after a refresh/ project creation.
Joined: 06-May-2008
Hi,
[quotenick="Walaa"]This should work without any manual intervention.
Ok..we got the type converter running now (thanks to reflektor CreateInstance is important
The UDT is registered in GAC and SQL-Server.
The problem with the above stack trace (null reference excepotion) still present..
any idea? The UDT is serialized into xml..the xml looks god so far. We will dig into the ORMSupport-sources some deeper...
best regards, yves
Joined: 08-Oct-2008
You can attach a zip file with the code to this thread using the "paperclip" icon. Otherwise you can email it to support AT llblgen DOT com - please put the ThreadId in the subject so we know what it relates to .
Thanks
Matt
DuenowY wrote:
Hi there, Some questions about using a user defined type in MS SQL Server 2008 with LLBLGEN pro (v2.6):
Please also provide the runtime lib build nr. It's been more than a year since 2.6 has been released and there have been a couple of fixes, also in this area.
LLBLGEN knows about the UDT (Mapped Entity Fields), it sets the .NET Type of the field to string.
Loading the generated Entity result in an InvalidCastException: my UdtSqlDatatype could not be converted to string. I tried it after implementing a cast operator, IConvertible, TypeConverter ... all with the same result.
Well, seems to be a common problem with typecasting...
string test = (string)((UdtSqlDatatype)dataAccess.ExecuteScalar(statement)); label1.Text = test;
UdtSqlDatatype udtSqlDatatype = (UdtSqlDatatype)dataAccess.ExecuteScalar(statement); label1.Text = udtSqlDatatype; // implicit cast operator in UdtSqlDatatype
string test2 = (string)dataAccess.ExecuteScalar(statement); label1.Text = test2;
where string statement = "select top 1 udtfield from Test";
The first two casts worked fine, but the third always fails...UdtSqlDatatype could not be converted to string.
That's because the C# compiler has emitted a type cast to string while the other statements will cause an op_implicit call.
Because the editor does not allow to change anything but "Is readonly" for this field (even after copying the assembly containing the UDT to the LLBLGEN pro directory) i tried to modify the entity manually.
UDT typed fields are immutable because you can't save them to the DB. so they're readonly. You can try to set them but that won't work, saving them to the DB will cause a problem. You don't need a setter for xml serialization btw, only for saving the data to the DB.
After modifying FieldInfoProvider.cs in my generated code, i replaced the DataType string with my UDT type in AddElementFieldInfo, modified the entity's property like this
public virtual string UdtField
{ get { return GetValue((int)TestFieldIndex.UdtField, true).ToString(); } set { SetValue((int)TestFieldIndex.UdtField, UdtSqlDatatype.Parse(value)); } }
and the entity can be read and written. But now there was another problem... by changing the type in AddElementFieldInfo, the xml deserialization of the entity fails:
the entity is written with
entity.WriteXml(XmlFormatAspect.Compact25 | XmlFormatAspect.DatesInXmlDataType | XmlFormatAspect.MLTextInCDataBlocks, out xml);
and read with
entity.ReadXml(xmlNode.OuterXml)
now resulting in a null reference exception
Fehler Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei SD.LLBLGen.Pro.ORMSupportClasses.XmlHelper.XmlValueToObject(String typeName, String xmlValue) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.Xml2EntityCollection( XmlReader reader, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.SD.LLBLGen.Pro.ORMSupportClasses. IEntityCollectionAccess2.Xml2EntityCollection(XmlReader reader, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(XmlReader reader, XmlFormatAspect format) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(String xmlData)
This problem seems to come from the fact that you manually changed things.
I have a bit of a problem with the fix you sent: you suggest that toString() is called when the value is of type string. But that won't work, as your UDT field is an UDT typed field, not a string typed field.
If you want it to be a string field, use a type converter which converts it to a string. That still won't make the field writable, as writing to the DB is what's the problem.
Joined: 06-May-2008
Hi Otis,
thanks for the answer. We have a typeconverter and we can read and write the udt without problems.
The problem is: when serializing the entity with the utd to xml and then deserialzing it, the problem occures. The mentioned code seems not to recognize to use the typeconverter for the type but assumes it to be a string.
Typeconverter works UDT works serializing works fine deserializing doesnt work.
we didn't manually change 'things' (aehm, depends what you mean with 'things' though).
Best regards, yves
DuenowY wrote:
Hi Otis, thanks for the answer. We have a typeconverter and we can read and write the udt without problems.
Interesting The problem we had (and have) with saving UDT CLR (it's the CLR part) typed values to sqlserver is that you have to specify the schema/catalog in which the UDT type is located, which is unknown information for the DQE (it has the .NET type but not the udt schema/catalog info)
The problem is: when serializing the entity with the utd to xml and then deserialzing it, the problem occures. The mentioned code seems not to recognize to use the typeconverter for the type but assumes it to be a string.
Typeconverter works UDT works serializing works fine deserializing doesnt work.
we didn't manually change 'things' (aehm, depends what you mean with 'things' though).
Best regards, yves
'things' as in: manually changed generated code, which could lead to problems, as you might overlook a spot
XML as you know works only with strings. So a type has to be convertable to a string and back. The type converter is not used here, as the type converter is for database interaction and isn't known at the entity level (xml serialization is done at the entity level, it has no idea that a type converter is present). The build-in system is a bit limited in that it can only work with types which are known (string, byte, int etc.). The problem is: what to call to convert the string back to the object it really represents? The only way I see it if it checks in XmlValueToObject if the realType instance is checked for an instance of op_Explicit using reflection. As this is always a static/public method it should not give any problems. If the op_Explicit(string) method is present, it then could call the method and get a normal object back instead of the string method.
The downside is that this will slow down the process of deserialization, though as the check is the last resort, it won't be noticable that much.
So in short: your ToString() implementation on the UDT CLR type should give the string which is passable to the implicit operator implementation of the UDT CLR type to get an instance back.
I'll do some tests with this to see if this is usable. We do have a different system in place for properties added manually to serialize to XML but as your field is an entity field, it's always taken into account and you will always run into this problem.
(edit) I saw I had slightly outdated sources when I built the one I attached (a linq related fix wasn't rolled into this built). I'll update the attachment a.s.a.p. Fixed.
ALmost there. I could reproduce it and fixed the original null ref issue (which was caused by a lame type -> string -> type conversion which failed and which was unnecessary anyway), I now have to implement the explicit operator on the test udt and check whether this fixes the problem.
(edit) works!
[Test]
public void UDTXmlDeserializationTest()
{
UdtTestEntity udt = new UdtTestEntity(1);
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
Assert.IsTrue(adapter.FetchEntity(udt));
}
Assert.IsNotNull(udt.Udtfield);
string xml;
udt.WriteXml(XmlFormatAspect.Compact25 | XmlFormatAspect.DatesInXmlDataType | XmlFormatAspect.MLTextInCDataBlocks, out xml);
UdtTestEntity deserializedUdt = new UdtTestEntity();
deserializedUdt.ReadXml(xml);
ComplexNumber c = deserializedUdt.Udtfield;
Assert.AreEqual(udt.Udtfield, c);
}
I'll attach a build to this post for you to test (so remember: ToString() returns a string to be placed into the XML and the op_Explicit(string) implementation on the UDT converts it back to an UDT type.
(edit) attached (and now the right one)
Please post the details here, instead of the email so everything is grouped together, unless it's confidential info. Also post your own code which triggered the problem as we otherwise can't create a repro case.
Joined: 06-May-2008
Dear Otis,
now we get the following error Error The value <AppointmentSeries Type="LF.Components.ScheduleSerial.AppointmentSeries"> <Appointment> <Appointment Type="LF.Components.ScheduleSerial.Appointment"> <Duration>PT24H</Duration> <DateTrigger> <DateTriggerList Type="LF.Components.ScheduleSerial.DateTriggerList"> <Dates> <Date>2001-05-05</Date> </Dates> </DateTriggerList> </DateTrigger> <TimeTrigger> <TimeTriggerList Type="LF.Components.ScheduleSerial.TimeTriggerList"> <Times> <Time>PT0S</Time> </Times> </TimeTriggerList> </TimeTrigger> </Appointment> </Appointment> </AppointmentSeries> is of type 'System.String' while the field is of type 'LF.Components.ScheduleSqlDatatype.Schedule' bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ValidateValue(IEntityField2 fieldToValidate, Object& value, Int32 fieldIndex) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.SetValue(Int32 fieldIndex, Object value, Boolean performDesyncForFKFields) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.SetNewFieldValue(Int32 fieldIndex, Object value) bei LF.Components.Winapo.Personen.Admin.Connect.Transformer.CopyEntityValues[T](T received, T current, Func`3 canCopy)
the error occures while copy udt data from one entity field to another entity (of same entity type) to another.
We tried explicit and implicit cast operator but doesn't help.
This is our Code:
public static void CopyEntityValues<T>(T received, T current, Func<T,EntityField2,bool> canCopy) where T : EntityBase2, IEntity2 { PersonUtilities.MarkEntityAsReadable(received); List<IEntityField2> pks = current.Fields.PrimaryKeyFields;
foreach (EntityField2 f in received.Fields)
{
EntityField2 ownfield = (EntityField2)current.Fields[f.FieldIndex];
if (current.IsNew || !pks.Contains(ownfield))
{
if ((canCopy == null) || canCopy(current, ownfield))
{
// at this line we get the error with udt
current.SetNewFieldValue(ownfield.FieldIndex, f.CurrentValue);
}
}
}
}
The error occures in: SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ValidateValue(IEntityField2 fieldToValidate, Object& value, Int32 fieldIndex):
at this code . Type type = value.GetType(); if (((fieldToValidate.DataType != typeof(object)) && (type != fieldToValidate.DataType)) && !fieldToValidate.DataType.IsInstanceOfType(value)) { string str2 = string.Format("The value {0} is of type '{1}' while the field is of type '{2}'", value, type, fieldToValidate.DataType); this.SetEntityFieldError(fieldToValidate.Name, str2, true); throw new ORMValueTypeMismatchException(str2); }
Maybe there is something wrong with the condition? I will dig deeper in it on monday, maybe you can see whats wrong?
if ((canCopy == null) || canCopy(current, ownfield)) { // at this line we get the error with udt current.SetNewFieldValue(ownfield.FieldIndex, f.CurrentValue); }
Would you please try the following hack.
Inside the above if block, check on the field name being copied and if it is the UDT field, cast the source and destination entities to the their entitytype and copy the field the normal way: destination.UDTField = source.UDTField
I've to verify with our own tests, though I think it is due to an implicit conversion operator, do you have an implicit conversion operator implemented? If so, indeed, it might be Walaa's workaround should work. After all, you can set the UDT field of an entity to a value, correct?
Joined: 06-May-2008
Hi,
we changed the code like this (maybe also useful for you):
private static readonly Dictionary<Type, MethodInfo> _udtParseMethods = new Dictionary<Type,MethodInfo>();
public static void CopyEntityValues<T>(T received, T current, Func<T,EntityField2,bool> canCopy)
where T : EntityBase2, IEntity2
{
PersonUtilities.MarkEntityAsReadable(received);
List<IEntityField2> pks = current.Fields.PrimaryKeyFields;
foreach (EntityField2 f in received.Fields)
{
EntityField2 ownfield = (EntityField2)current.Fields[f.FieldIndex];
if (current.IsNew || !pks.Contains(ownfield))
{
if ((canCopy == null) || canCopy(current, ownfield))
{
current.SetNewFieldValue(ownfield.FieldIndex, ConvertValue(f.CurrentValue, f.DataType));
}
}
}
}
private static object ConvertValue(object value, Type type)
{
if (type.GetCustomAttributes(typeof(Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute), true).Count() > 0)
{
MethodInfo parseMethod = null;
lock (((ICollection)_udtParseMethods).SyncRoot)
{
if (!_udtParseMethods.TryGetValue(type, out parseMethod))
{
parseMethod = type.GetMethod("Parse");
if (parseMethod == null)
{
throw new NotImplementedException(type + ".Parse");
}
_udtParseMethods.Add(type, parseMethod);
}
}
return parseMethod.Invoke(null, new object[] { (SqlString)value.ToString() }); // sure now it exists....
}
return value;
}
Well, this works now
Our tests now run, except one. we get a Null-Ref-Exception at SD.LLBLGen.Pro.ORMSupportClasses.FieldUtilities.DetermineRealType(Type toConvert, Boolean unWrapNullableType), we can bypass this at that point, though (we only need the primary keys of those entities in our case.) Maybe its the same error like the one at the beginning of the thread, but in another method.
Fehler Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei SD.LLBLGen.Pro.ORMSupportClasses.FieldUtilities.DetermineRealType(Type toConvert, Boolean unWrapNullableType)
bei SD.LLBLGen.Pro.ORMSupportClasses.FieldUtilities.DetermineRealFullTypeName(Type toConvert, Boolean unWrapNullableType)
bei SD.LLBLGen.Pro.ORMSupportClasses.XmlHelper.XmlValueToObject(Type realType, String xmlValue)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityFields2.ReadXml(XmlNode fieldsElement)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary2 processedObjectIDs, List
1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.Xml2EntityCollection(XmlNode node, Dictionary
2 processedObjectIDs, List1 nodeEntityReferences)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2
1.ReadXml(XmlNode node, XmlFormatAspect format)
bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.ReadXml(String xmlData)
bei LF.Components.Winapo.Personen.Admin.Connect.TransferPersonSerializer.StringToTransferPersonCollection(String personen)
After all, thanks so far for your support, quite interessting to handle udt with llblgen . We will use the provided assembly until the fix made it to the next Build / Release.
Keep up the good work, we love llblgen with it's linq support and were looking forward to v3.0!
DuenowY wrote:
Hi,
we changed the code like this (maybe also useful for you):
private static readonly Dictionary<Type, MethodInfo> _udtParseMethods = new Dictionary<Type,MethodInfo>();
public static void CopyEntityValues<T>(T received, T current, Func<T,EntityField2,bool> canCopy) where T : EntityBase2, IEntity2 { PersonUtilities.MarkEntityAsReadable(received); List<IEntityField2> pks = current.Fields.PrimaryKeyFields; foreach (EntityField2 f in received.Fields) { EntityField2 ownfield = (EntityField2)current.Fields[f.FieldIndex]; if (current.IsNew || !pks.Contains(ownfield)) { if ((canCopy == null) || canCopy(current, ownfield)) { current.SetNewFieldValue(ownfield.FieldIndex, ConvertValue(f.CurrentValue, f.DataType)); } } } } private static object ConvertValue(object value, Type type) { if (type.GetCustomAttributes(typeof(Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute), true).Count() > 0) { MethodInfo parseMethod = null; lock (((ICollection)_udtParseMethods).SyncRoot) { if (!_udtParseMethods.TryGetValue(type, out parseMethod)) { parseMethod = type.GetMethod("Parse"); if (parseMethod == null) { throw new NotImplementedException(type + ".Parse"); } _udtParseMethods.Add(type, parseMethod); } } return parseMethod.Invoke(null, new object[] { (SqlString)value.ToString() }); // sure now it exists.... } return value; }
Well, this works now
![]()
Hmm. Still strange why this is needed. The property set caused the same problem? The reason I ask is that it seems that the value you pass into SetNewFieldValue is indeed a string and not an object of your CLR UDT type.
(btw, EntityFields2 has a clone method )
Our tests now run, except one. we get a Null-Ref-Exception at SD.LLBLGen.Pro.ORMSupportClasses.FieldUtilities.DetermineRealType(Type toConvert, Boolean unWrapNullableType), we can bypass this at that point, though (we only need the primary keys of those entities in our case.) Maybe its the same error like the one at the beginning of the thread, but in another method.
Fehler Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei SD.LLBLGen.Pro.ORMSupportClasses.FieldUtilities.DetermineRealType(Type toConvert, Boolean unWrapNullableType) bei SD.LLBLGen.Pro.ORMSupportClasses.FieldUtilities.DetermineRealFullTypeName(Type toConvert, Boolean unWrapNullableType) bei SD.LLBLGen.Pro.ORMSupportClasses.XmlHelper.XmlValueToObject(Type realType, String xmlValue) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityFields2.ReadXml(XmlNode fieldsElement) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary
2 processedObjectIDs, List
1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.Xml2EntityCollection(XmlNode node, Dictionary
2 processedObjectIDs, List1 nodeEntityReferences) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2
1.ReadXml(XmlNode node, XmlFormatAspect format) bei SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.ReadXml(String xmlData) bei LF.Components.Winapo.Personen.Admin.Connect.TransferPersonSerializer.StringToTransferPersonCollection(String personen)After all, thanks so far for your support, quite interessting to handle udt with llblgen
. We will use the provided assembly until the fix made it to the next Build / Release.
This can only be caused by the fact that a type specification is null. Could you provide details so we could try to reproduce this?
Also, could you provide the details how you are able to save an UDT CLR typed value into a row without specifying the catalog/schema name of the UDT CLR type?
Keep up the good work, we love llblgen with it's linq support and were looking forward to v3.0!
![]()
(edit) If I could give you a tip: if possible, choose another type than the UDT CLR type, simply because updating UDT CLR types and assemblies is a nightmare. Just a FYI
Joined: 06-May-2008
Dear Otis,
it's all a bit tricky because we were not able to debug the ORMSupportClasses. We have the code though, but with our key we didn't it get to fly. (some wired assembly caching issues..).
You asked: This can only be caused by the fact that a type specification is null. Could you provide details so we could try to reproduce this? We serialized an EntityCollection, and one of the entities containes the entity with the udt. When deserializing the entity collection, the error occures with the stack trace provided.
You ask: Also, could you provide the details how you are able to save an UDT CLR typed value into a row without specifying the catalog/schema name of the UDT CLR type?
at sql server level, we used:
CREATE ASSEMBLY ….
CREATE TYPE [dbo].[Schedule] ….
CreateTable.. [TableName] [dbo].[Schedule] NULL,
When we started with the udt, we had no typeconverter for it. LLBLGen marked it as readonly then. Type String. we couldn't read / save the entities at all.
We implemented a TypeConverter then. First we just implemented the Type converter from a microsoft sample. But llblgen did't recognize it as a valid typeconverter (didn't show errror though, too) until we implemented the CreateInstance-Method to the type-converter. Now llblgen accepted the type for read-Write and we could select our Typeconverter to the field.
without Typeconverter llblgen showed: ".Net type" as "System.String" "UDT Name" as "DBO.Schedule" "TypeConverterToUse" as ""
with TypConverter llblgen showed: ".Net type" as "LF.Components.ScheduleSQLDatatype.Schedule" "UDT Name" as "DBO.Schedule" "TypeConverterToUse" as " LF.Components.ScheduleSqlDatatype.ScheduleConverter "
Now read and write worked fine, but we had the problems we posted with deserializing.
Our type converter (We implemented it in the assembly that also contains the sql datatype) looks like this:
namespace LF.Components.ScheduleSqlDatatype { public class ScheduleConverter : System.ComponentModel.TypeConverter { // Overrides the CanConvertFrom method of TypeConverter. // The ITypeDescriptorContext interface provides the context for the // conversion. Typically, this interface is used at design time to // provide information about the design-time container. public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } if (sourceType == typeof(Schedule)) { return true; } return base.CanConvertFrom(context, sourceType); }
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
if (destinationType == typeof(Schedule))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
// Overrides the ConvertFrom method of TypeConverter.
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string stringValue = value as string;
if (stringValue != null)
{
return Schedule.Parse(stringValue);
}
Schedule scheduleValue = value as Schedule;
if (scheduleValue != null)
{
return scheduleValue;
}
return base.ConvertFrom(context, culture, value);
}
// Overrides the ConvertTo method of TypeConverter.
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return value.ToString();
}
if (destinationType == typeof(Schedule))
{
return value.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
return new Schedule();
}
}
}
Sorry for the textwall. Hope this helps. If you want to take a look at our udt code, i could send it via mail.
Best regards, Yves
I guess saving to the DB goes OK due to the fact that it is by default converted to varchar which is implicitly convertable to your type, there's no other explanation for it.
I also find it very strange that the type without the typeconverter was set to System.String, it should have been the UDT CLR type found in the table.
I can't reproduce your problem:
[Test]
public void UDTXmlDeserializationTest()
{
UdtTestEntity udt = new UdtTestEntity(1);
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
Assert.IsTrue(adapter.FetchEntity(udt));
}
Assert.IsNotNull(udt.Udtfield);
string xml;
udt.WriteXml(XmlFormatAspect.Compact25 | XmlFormatAspect.DatesInXmlDataType | XmlFormatAspect.MLTextInCDataBlocks, out xml);
UdtTestEntity deserializedUdt = new UdtTestEntity();
deserializedUdt.ReadXml(xml);
ComplexNumber c = deserializedUdt.Udtfield;
Assert.AreEqual(udt.Udtfield, c);
UdtTestEntity copy = new UdtTestEntity();
foreach(EntityField2 field in deserializedUdt.Fields)
{
copy.SetNewFieldValue(field.FieldIndex, field.CurrentValue);
Assert.AreEqual(field.CurrentValue, copy.Fields[field.Name].CurrentValue);
}
}
works OK.
Joined: 06-May-2008
Hi,
hmm, maybe there's smt wrong with our UDT? This is how it looks like:
using System; using System.Collections; using System.Collections.Generic; using System.Data.SqlTypes; using System.Linq; using System.Xml.Linq; using Microsoft.SqlServer.Server; using System.ComponentModel;
namespace LF.Components.ScheduleSqlDatatype { [Serializable] [TypeConverter(typeof(ScheduleConverter))] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, IsByteOrdered = true, MaxByteSize = -1)] public class Schedule : INullable, IBinarySerialize, IConvertible { private bool _isNull; private string _seriesDefinition; private static readonly Schedule _nullElement = new Schedule();
public Schedule ()
{
Clear();
}
public bool IsNull
{
get { return _isNull; }
}
public static Schedule Null
{
get
{
return _nullElement;
}
}
public override string ToString ()
{
return IsNull ? "NULL" : _seriesDefinition;
}
public static Schedule Parse (SqlString s)
{
if (s.IsNull)
{
return Null;
}
Schedule sched = new Schedule();
sched.AssignValue(s.ToString());
return sched;
}
[SqlMethod(OnNullCall = false)]
public bool HasTriggerAt (SqlDateTime at)
{
if (!IsNull && !at.IsNull)
{
// code omitted
}
return false;
}
[SqlMethod(OnNullCall = false)]
public bool HasTriggerBetween (SqlDateTime from, SqlDateTime to)
{
if (!IsNull && !from.IsNull && !to.IsNull)
{
// code omitted
}
return false;
}
public void Read (System.IO.BinaryReader r)
{
AssignValue(r.ReadString());
}
public void Write (System.IO.BinaryWriter w)
{
if (IsNull)
{
w.Write(String.Empty);
}
else
{
w.Write(_seriesDefinition);
}
}
private void Clear ()
{
_seriesDefinition = String.Empty;
_isNull = true;
}
private void AssignValue (string s)
{
if (String.IsNullOrEmpty(s))
{
Clear();
}
else
{
_seriesDefinition = s;
_isNull = false;
}
}
private object Load (string source)
{
if (!String.IsNullOrEmpty(source))
{
// code omitted
}
return null;
}
public static implicit operator string(Schedule value)
{
return value == null ? null : value.ToString();
}
string IConvertible.ToString(IFormatProvider provider)
{
return ToString();
}
#region IConvertible Members
TypeCode IConvertible.GetTypeCode()
{
return TypeCode.Object;
}
bool IConvertible.ToBoolean(IFormatProvider provider)
{
return Convert.ToBoolean(ToString());
}
byte IConvertible.ToByte(IFormatProvider provider)
{
return Convert.ToByte(ToString());
}
char IConvertible.ToChar(IFormatProvider provider)
{
return Convert.ToChar(ToString());
}
DateTime IConvertible.ToDateTime(IFormatProvider provider)
{
return Convert.ToDateTime(ToString());
}
decimal IConvertible.ToDecimal(IFormatProvider provider)
{
return Convert.ToDecimal(ToString());
}
double IConvertible.ToDouble(IFormatProvider provider)
{
return Convert.ToDouble(ToString());
}
short IConvertible.ToInt16(IFormatProvider provider)
{
return Convert.ToInt16(ToString());
}
int IConvertible.ToInt32(IFormatProvider provider)
{
return Convert.ToInt32(ToString());
}
long IConvertible.ToInt64(IFormatProvider provider)
{
return Convert.ToInt64(ToString());
}
sbyte IConvertible.ToSByte(IFormatProvider provider)
{
return Convert.ToSByte(ToString());
}
float IConvertible.ToSingle(IFormatProvider provider)
{
return Convert.ToSingle(ToString());
}
object IConvertible.ToType(Type conversionType, IFormatProvider provider)
{
return Convert.ChangeType(ToString(), conversionType);
}
ushort IConvertible.ToUInt16(IFormatProvider provider)
{
return Convert.ToUInt16(ToString());
}
uint IConvertible.ToUInt32(IFormatProvider provider)
{
return Convert.ToUInt32(ToString());
}
ulong IConvertible.ToUInt64(IFormatProvider provider)
{
return Convert.ToUInt64(ToString());
}
#endregion
}
}
Best regards, Yves
I think it's the implicit operator. Could you remove that please and try again? My testcode uses an explicit operator (from string to UDT CLR type) which gives the proper results for xmldeserialization
Joined: 06-May-2008
Tried it with both, implicit and explicit operator, same problem.
Also downloaded the latest Version, now using
SD.LLBLGen.Pro.DQE.SqlServer.NET20.dll 2.6.8.1114 SD.LLBLGen.Pro.LinqSupportClasses.NET35.dll 2.6.9.622 SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll 2.6.9.703, first tried with 2.6.9.616
Maybe you could run your test with our udt?
I also find it very strange that the type without the typeconverter was set to System.String, it should have been the UDT CLR type found in the table.
Do I have to do something special with the editor at this point?
What exactly do you mean with
The problem we had (and have) with saving UDT CLR (it's the CLR part) typed values to sqlserver is that you have to specify the schema/catalog in which the UDT type is located, which is unknown information for the DQE (it has the .NET type but not the udt schema/catalog info)
DuenowY wrote:
Tried it with both, implicit and explicit operator, same problem.
tried as in: you removed the implicit operator and used the explicit operator? What I find really strange is that the runtime lib I attached earlier to this thread looks only for the op_Explicit method and calls that for deserializing the data to a real object. As you don't have that operator, I fail to see how your UDT class could even work, as it has to give the same problem as you started this thread with: the null ref exception due to the data which was kept as 'string' which isn't going to work.
So in other words: you NEED the explicit operator, for the deserialization to work, with the runtime library I attached earlier.
Maybe you could run your test with our udt?
I could, but that wouldn't make your UDT work. The problem is: you need the explicit operator overload from string to your UDT type. That's mandatory, otherwise the string in the XML can't be deserialized to the UDT type.
I also find it very strange that the type without the typeconverter was set to System.String, it should have been the UDT CLR type found in the table.
Do I have to do something special with the editor at this point?
The driver will obtain the type information for the UDT CLR type in the schema. It then will try to instantiate an instance of that type using that info. This means that your UDT CLR type has to be in an assembly which is in the GAC on the machine you use the designer on. This has to be the same version as the one used in SQLServer.
What exactly do you mean with
The problem we had (and have) with saving UDT CLR (it's the CLR part) typed values to sqlserver is that you have to specify the schema/catalog in which the UDT type is located, which is unknown information for the DQE (it has the .NET type but not the udt schema/catalog info)
When the DQE creates a Sqlparameter to save the value to the DB, it has to specify the catalog/schema/T-SQL name of the UDT CLR type but that's unknown information, hence the UDT CLR typed fields are readonly. As you said you could save the fields, I was a little surprised as the SqlDbType of the parameter has to be set to UDT and the required info has to be specified otherwise an exception will occur, but you didn't have that. It might be due to the string value and the IConvertable implementation, but I'm not sure.
As the value is a string anyway (or XML as it seems), why not store it inside a normal nvarchar field in the DB? this gives less headaches for you as well, because when you update the UDT CLR type you don't have to drop the table (and migrate the data!).
Joined: 06-May-2008
You're right, the explicit operator is needed for the deserialization to work .... from and to string ....
public static explicit operator string(Schedule value) { return value == null ? null : value.ToString(); }
public static explicit operator Schedule(string value) { return value == null ? null : Schedule.Parse(value); }
See http://msdn.microsoft.com/en-us/library/ms131082.aspx:
The UDT must contain a public static (or Shared) Parse method that supports parsing from, and a public ToString method for converting to a string representation of the object.
Btw, the compiler checks the existance of the UDT's method Parse.
I still don't get the designer to show me the correct .net type (the assembly is in the GAC, sqlserver is on the same machine) without using the type converter, but it always shows the correct UDT name, is that what you mean with 'the UDT CLR type' ?
When the DQE creates a Sqlparameter to save the value to the DB, it has to specify the catalog/schema/T-SQL name of the UDT CLR type but that's unknown information, hence the UDT CLR typed fields are readonly.
Yeah storing the data in a nvarchar field is an idea, but i really need those two methods HasTriggerAt and HasTriggerBetween (thought it would be better for performance if the content of this field isn't always parsed if not needed...)
DuenowY wrote:
You're right, the explicit operator is needed for the deserialization to work .... from and to string
....
public static explicit operator string(Schedule value) { return value == null ? null : value.ToString(); }
public static explicit operator Schedule(string value) { return value == null ? null : Schedule.Parse(value); }
See http://msdn.microsoft.com/en-us/library/ms131082.aspx:
The UDT must contain a public static (or Shared) Parse method that supports parsing from, and a public ToString method for converting to a string representation of the object.
Btw, the compiler checks the existance of the UDT's method Parse.
Ok, though just the explicit operator FROM string is required for deserialization.
I still don't get the designer to show me the correct .net type (the assembly is in the GAC, sqlserver is on the same machine) without using the type converter, but it always shows the correct UDT name, is that what you mean with 'the UDT CLR type' ?
No, the .NET type should be of 'Schedule' as well. Could you mail me the .dll please? I'll then add it to sqlserver and check whether I can reproduce it.
When the DQE creates a Sqlparameter to save the value to the DB, it has to specify the catalog/schema/T-SQL name of the UDT CLR type but that's unknown information, hence the UDT CLR typed fields are readonly.
Yeah storing the data in a nvarchar field is an idea, but i really need those two methods HasTriggerAt and HasTriggerBetween
(thought it would be better for performance if the content of this field isn't always parsed if not needed...)
If you're not doing any CLR code inside sqlserver, it's of no real use as you can then also do it using a normal .NET type and a type converter. (as the field in the DB is then just a string)