Simple Types Generated As IList<String> Instead of String

Dec 16, 2010 at 3:24 PM

I downloaded 2.0.2.56002, and generated a class for RDLC ReportDefinition.xsd, and I noticed that all simple types were generated as IList<string>, when I really feel that they should be simple string types.   Is it possible to change this easily, without having to resort to modifying LinqToXsd source code?

Dec 16, 2010 at 4:18 PM

We run 1.1.0 and don't have that issue, any element set as xs:string which is limited to 1 instance appears as a string.

We only see List<string> if we set the bounds to be >1.

Not much help I know but unless they've changed it in v2 that's not what should happen.

J.

Dec 16, 2010 at 4:31 PM

Thanks J.

Below is the code that was generated for a String element in the .xsd.  It might to find out how it would have been generated in 1.1...

        /// <summary>
        /// <para>
        /// Occurrence: required, choice
        /// </para>
        /// <para>
        /// Regular expression: (DataProvider | ConnectString | IntegratedSecurity? | Prompt? | any)+
        /// </para>
        /// </summary>
        public IList<string> ConnectString
        {
            get
            {
                if ((this.ConnectStringField == null))
                {
                    this.ConnectStringField = new XSimpleList<string>(thisXmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype, XName.Get("ConnectString""http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition"));
                }
                return this.ConnectStringField;
            }
            set
            {
                if ((value == null))
                {
                    this.ConnectStringField = null;
                }
                else
                {
                    if ((this.ConnectStringField == null))
                    {
                        this.ConnectStringField = XSimpleList<string>.Initialize(thisXmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype, valueXName.Get("ConnectString""http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition"));
                    }
                    else
                    {
                        XTypedServices.SetList<System.String>(this.ConnectStringField, value);
                    }
                }
            }
Dec 16, 2010 at 4:45 PM

Is this what you want?

For this Schema:

<?xml version="1.0" encoding="utf-8"?>
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by Mrs Smith (Spur Solutions) -->
<xs:schema xmlns="http://tempuri.org/test.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://tempuri.org/test.xsd">
	<xs:element name="parent">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="AString" type="xs:string"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element>
</xs:schema>

I get this code

    /// <summary>
    /// <para>
    /// Regular expression: (AString)
    /// </para>
    /// </summary>
    public partial class parent : XTypedElement, IXMetaData {
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        static Dictionary<XName, System.Type> localElementDictionary = new Dictionary<XName, System.Type>();
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static ContentModelEntity contentModel;
        
         public static explicit operator parent(XElement xe) { return XTypedServices.ToXTypedElement<parent>(xe,LinqToXsdTypeManager.Instance as ILinqToXsdTypeManager); }
        
        static parent() {
            BuildElementDictionary();
            contentModel = new SequenceContentModelEntity(new NamedContentModelEntity(XName.Get("AString", "")));
        }
        
        /// <summary>
        /// <para>
        /// Regular expression: (AString)
        /// </para>
        /// </summary>
        public parent() {
        }
        
        /// <summary>
        /// <para>
        /// Occurrence: required
        /// </para>
        /// <para>
        /// Regular expression: (AString)
        /// </para>
        /// </summary>
        public string AString {
            get {
                XElement x = this.GetElement(XName.Get("AString", ""));
                return XTypedServices.ParseValue<string>(x, XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype);
            }
            set {
                this.SetElement(XName.Get("AString", ""), value, XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype);
            }
        }
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        Dictionary<XName, System.Type> IXMetaData.LocalElementsDictionary {
            get {
                return localElementDictionary;
            }
        }
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        XName IXMetaData.SchemaName {
            get {
                return XName.Get("parent", "http://tempuri.org/test.xsd");
            }
        }
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        SchemaOrigin IXMetaData.TypeOrigin {
            get {
                return SchemaOrigin.Element;
            }
        }
        
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        ILinqToXsdTypeManager IXMetaData.TypeManager {
            get {
                return LinqToXsdTypeManager.Instance;
            }
        }
        
        public void Save(string xmlFile) {
            XTypedServices.Save(xmlFile, Untyped);
        }
        
        public void Save(System.IO.TextWriter tw) {
            XTypedServices.Save(tw, Untyped);
        }
        
        public void Save(System.Xml.XmlWriter xmlWriter) {
            XTypedServices.Save(xmlWriter, Untyped);
        }
        
        public static parent Load(string xmlFile) {
            return XTypedServices.Load<parent>(xmlFile);
        }
        
        public static parent Load(System.IO.TextReader xmlFile) {
            return XTypedServices.Load<parent>(xmlFile);
        }
        
        public static parent Parse(string xml) {
            return XTypedServices.Parse<parent>(xml);
        }
        
        public override XTypedElement Clone() {
            return XTypedServices.CloneXTypedElement<parent>(this);
        }
        
        private static void BuildElementDictionary() {
            localElementDictionary.Add(XName.Get("AString", ""), typeof(string));
        }
        
        ContentModelEntity IXMetaData.GetContentModel() {
            return contentModel;
        }
    }

Dec 16, 2010 at 4:47 PM

Yours:

/// Occurrence: required, choice

Mine:

/// Occurrence: required

Post your schema?
Dec 16, 2010 at 4:59 PM

J.

I was looking for this:

 public string AString {
            get {
                XElement x = this.GetElement(XName.Get("AString", ""));
                return XTypedServices.ParseValue<string>(x, XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype);
            }
            set {
                this.SetElement(XName.Get("AString", ""), value, XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype);
            }
        }

Let me see if that still works with the version that I have.

Thanks,

Bob

Dec 16, 2010 at 5:01 PM

OK, well, I'm off home.

Let me know how you do. :)

J.

Dec 16, 2010 at 7:31 PM

Thanks, J.

I was able to get the effect that I wanted with this:

        public string ConnectString
        {
            get
            {
                XElement x = this.GetElement(XName.Get("ConnectString""http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition"));
                return XTypedServices.ParseValue<string>(x, 
                    XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype);
            }
            set
            {
                this.SetElement(XName.Get("ConnectString""http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition"), value
                    XmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype);
            }
        }
The RDLC report definition XSD has a namespace, that needed to be specified in the XName.Get call.
Bob
Dec 17, 2010 at 12:20 PM

Ah, so it's a weird XSD that's not yours and you can't edit.

So you're adding that extra property manually, in a partial class to stop it getting wiped in an auto-generation event, yes?

J.

Dec 17, 2010 at 3:05 PM

I was playing around with manipulating an .rdlc file, since it is just XML, so I downloaded the .xsd, and generated the LINQ-to-XSD class with the current version that I downloaded.

The schema definition for that piece of code is like this:

   <xsd:complexType name="ConnectionPropertiesType">
      <xsd:choice minOccurs="1" maxOccurs="unbounded">
         <xsd:element name="DataProvider" type="xsd:string" />
         <xsd:element name="ConnectString" type="xsd:string" />
         <xsd:element name="IntegratedSecurity" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="Prompt" type="StringLocIDType" minOccurs="0" />
         <xsd:any namespace="##other" processContents="skip" />
      </xsd:choice>
      <xsd:anyAttribute namespace="##other" processContents="skip" />
   </xsd:complexType>

As, you can see the ConnectString element is just a string.  I don't understand why it was generated as a list.

Dec 17, 2010 at 6:58 PM

I am trying to track it down in the code, and I came across this in XsdToTypesConverter.SetPropertyFlags:

            if (currentGroupingInfo.IsRepeating)
            {
                propertyInfo.IsList = true;
            }

The GroupInfo.IsRepeating seems to be set here:

internal GroupingInfo(ContentModelType cmType, Occurs occursInSchema) {
            this.contentModelType = cmType;
            this.occursInSchema = occursInSchema;
            this.contentType = ContentType.Grouping;
            if ((int)occursInSchema > (int)Occurs.ZeroOrOne) {
                groupingFlags |= GroupingFlags.Repeating;
            }
        }

The "occursInSchema" appears to come from this line, which calls GetOccurence:

currentGroupingInfo = new GroupingInfo((ContentModelType)((int)particleType), GetOccurence(currentGroupBase));

I traced through the GetOccurence code for the Author element, and it is defined as ZeroOrOne.  That would appear that the occurrence is determined by "currentGroupBase" (which is Choice in this case), and not the element itself.

        private Occurs GetOccurence(XmlSchemaParticle particle)        {
            if (particle.MinOccurs == 1)
            {
                if (particle.MaxOccurs == 1)
                {
                    return Occurs.One;
                }
                else
                { //maxOccurs cannot be 0
                    return Occurs.OneOrMore;
                }

            }
            else if (particle.MinOccurs == 0)
            {
                if (particle.MaxOccurs == 1)
                {
                    return Occurs.ZeroOrOne;
                }
                else
                { //maxOccurs cannot be 0
                    return Occurs.ZeroOrMore;
                }
            }
            else
            {
                Debug.Assert(particle.MinOccurs > 1);
                return Occurs.OneOrMore;
            }
        }
Dec 20, 2010 at 9:44 AM

Ah, that's why:

      <xsd:choice minOccurs="1" maxOccurs="unbounded">
         <xsd:element name="DataProvider" type="xsd:string" />
         <xsd:element name="ConnectString" type="xsd:string" />
         <xsd:element name="IntegratedSecurity" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="Prompt" type="StringLocIDType" minOccurs="0" />
         <xsd:any namespace="##other" processContents="skip" />
      </xsd:choice>

It's a singular string, but it's part of an unbounded choice.
Becasue it's a choice (and not a sequence) it won't infer a LocalType because the order is not fixed.
So it just creates lots of List<T>s
What I can't figure, is how you enumerate all the elements in the order they are in the document?

J.

Dec 20, 2010 at 3:16 PM

I am trying to figure out the details behind the Microsoft Reporting .rdlc files, and I guess that I need to understand that better, in order to understand why LINQ-to-XSD generates the class that it does.  I don't think that the order of the elements is important in my case.  I just need a lot of First/FirstOrDefault to get the only element in the list.

Dec 20, 2010 at 3:24 PM

 

I only mention order as it was one of the things that I needed from a choice I used once. Don't think I ever figured it out.

Anyway, that unbounded choice means that it contains "a bunch of" each of the elemets described in no particular order.

If you only ever set one, it's a pain but that's why it's a List<T>.

If you want tidy code, your earlier solution of adding your own custom wrapper properties in a partial class is a good way and how I'd do it.

J.

Dec 20, 2010 at 5:32 PM

The funny thing is that I didn't create a custom wrapper in a partial class, I modified the generated code class to use those properties.  Creating a wrapper class might work if I could create a different, overloaded method signature, but properties can't be overloaded.  I can't see a way to change the behavior of the generated class, so that it doesn't generate the List<T> for all the properties. 

The problem that I have with modifying the generated class is when you accidentally re-generate the class, and lose all the changes (like I did).

Dec 20, 2010 at 5:34 PM

I did have the thought of modifying the .xsd schema file to set the upper bounds for those properties, but I don't know enough about the Microsoft Reporting Services schema to say whether those properties are really boundless or not.

Dec 20, 2010 at 5:45 PM

I found this web page, that shows the diagram for the data elements in the Report Definition schema:

Report Definition Report Data Overview Diagram
http://msdn.microsoft.com/en-us/library/bb677408.aspx

The diagram shows that the cardinality for ConnectionProperties is 1:1, and not boundless, as the .xsd schema suggests.  I might be able to look more closely at how the schema SHOULD be defined, in order to avoid any customized code in the generated class.

Dec 20, 2010 at 6:47 PM

Changing the .xsd like this solved my ConnectString problem:

   <xsd:complexType name="ConnectionPropertiesType">
      <xsd:choice minOccurs="1" maxOccurs="1">
         <xsd:element name="DataProvider" type="xsd:string" />
         <xsd:element name="ConnectString" type="xsd:string" />
         <xsd:element name="IntegratedSecurity" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="Prompt" type="StringLocIDType" minOccurs="0" />
         <xsd:any namespace="##other" processContents="skip" />
      </xsd:choice>
      <xsd:anyAttribute namespace="##other" processContents="skip" />
   </xsd:complexType>

I came across a type that cannot be fixed that way:

   <xsd:complexType name="ReportParameterType">
      <xsd:choice minOccurs="1" maxOccurs="unbounded">
         <xsd:element name="DataType">
            <xsd:simpleType>
               <xsd:restriction base="xsd:string">
                  <xsd:enumeration value="Boolean" />
                  <xsd:enumeration value="DateTime" />
                  <xsd:enumeration value="Integer" />
                  <xsd:enumeration value="Float" />
                  <xsd:enumeration value="String" />
               </xsd:restriction>
            </xsd:simpleType>
         </xsd:element>
         <xsd:element name="Nullable" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="DefaultValue" type="DefaultValueType" minOccurs="0" maxOccurs="1" />
         <xsd:element name="AllowBlank" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="Prompt" type="StringLocIDType" minOccurs="0" />
         <xsd:element name="ValidValues" type="ValidValuesType" minOccurs="0" maxOccurs="1" />
         <xsd:element name="Hidden" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="MultiValue" type="xsd:boolean" minOccurs="0" />
         <xsd:element name="UsedInQuery" minOccurs="0">
            <xsd:simpleType>
               <xsd:restriction base="xsd:string">
                  <xsd:enumeration value="False" />
                  <xsd:enumeration value="True" />
                  <xsd:enumeration value="Auto" />
               </xsd:restriction>
            </xsd:simpleType>
         </xsd:element>
         <xsd:any namespace="##other" processContents="skip" />
      </xsd:choice>
      <xsd:attribute name="Name" type="xsd:normalizedString" use="required" />
      <xsd:anyAttribute namespace="##other" processContents="skip" />
   </xsd:complexType>

The ReportParameterType needs to be 1..n, but DefaultValue needs to be 0..1.  Setting maxOccurs on the DefaultValue seems to be ignored if the higher "choice" element is unbounded.  I would think that if you set maxOccurs on the element that it should override the maxOccurs on the choice group.

Dec 21, 2010 at 11:11 AM

Hi, min and max have default values of 1, if you don't specify them they take the default.
I haven't run this through Linq2Xsd but Altova XMLSpy (our XML editor of choice) likes it.
I'm working from this file:
http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition/
Assuming by "ignores" you mean that you can, in C#, add more than one item to the List<T>, I don't think Linq2Xsd programatically enforces bounds?

Looking at your original setter:

            set
            {
                if ((value == null))
                {
                    this.ConnectStringField = null;
                }
                else
                {
                    if ((this.ConnectStringField == null))
                    {
                        this.ConnectStringField = XSimpleList<string>.Initialize(thisXmlSchemaType.GetBuiltInSimpleType(XmlTypeCode.String).Datatype, valueXName.Get("ConnectString""http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition"));
                    }
                    else
                    {
                        XTypedServices.SetList<System.String>(this.ConnectStringField, value);
                    }
                }
            }

No boundry checks there.
However, if you validate the resulting XML against the XSD using the .net validation stuff it would fail if you put two elements in there.
Then again, maybe you mean something else entirely when you said ignores!

Anyway, here's the xsd:

<
xsd:complexType name="ReportParameterType"> <xsd:choice> <xsd:element name="DataType"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:enumeration value="Boolean"/> <xsd:enumeration value="DateTime"/> <xsd:enumeration value="Integer"/> <xsd:enumeration value="Float"/> <xsd:enumeration value="String"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="Nullable" type="xsd:boolean" minOccurs="0"/> <xsd:element name="DefaultValue" type="DefaultValueType" minOccurs="0"/> <xsd:element name="AllowBlank" type="xsd:boolean" minOccurs="0"/> <xsd:element name="Prompt" type="StringLocIDType" minOccurs="0"/> <xsd:element name="ValidValues" type="ValidValuesType" minOccurs="0"/> <xsd:element name="Hidden" type="xsd:boolean" minOccurs="0"/> <xsd:element name="MultiValue" type="xsd:boolean" minOccurs="0"/> <xsd:element name="UsedInQuery" minOccurs="0"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:enumeration value="False"/> <xsd:enumeration value="True"/> <xsd:enumeration value="Auto"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:any namespace="##other" processContents="skip"/> </xsd:choice> <xsd:attribute name="Name" type="xsd:normalizedString" use="required"/> <xsd:anyAttribute namespace="##other" processContents="skip"/> </xsd:complexType>

 

J.

Dec 21, 2010 at 2:47 PM

Aah, yes, words, words, words, and their meaning (d'oh)

By "ignores", I mean doesn't even look at.  It looks like in the code that once the maxOccurs is determined for the group, that it doesn't even care what the element maxOccurs is.  I wouldn't say that I was an .xsd schema expert (yet), but I would think that if the element has a more restrictive maxOccurs, that it should be respected.

Dec 21, 2010 at 3:55 PM

Right, I see what you're saying.

It's a weird one, the schema basically says you're allowed any number of the following choices, and for each choice, if you choose DefaultValue, you're only allowed one in each choice.

Logically this is the equivalent to saying you can have DefaultValue as many times as you like. It produces identical XML.

Basically the maxOccurs is meaningless in that context.

Interestingly it's NOT in the schema I downloaded form microsoft!

Maybe I have a newer version, with that corrected?

J.

Dec 21, 2010 at 6:13 PM

maxOccurs wasn't in the schema that I downloaded either.  I added that in my attempt to get the .cs class to generate the way that I would like.  I just realized, with that test, that LINQ-to-XSD wasn't respecting that the element maxOccurs, since it was getting it from the choice group.