none
Bug? Columns duplicated across all tables in DataSet at Design Time RRS feed

  • Question

  • I am new here and was guided to this forum by a kind MVP.  Hoping to find a workaround to an issue...

    I've recreated this issue in both C# and VB Express editions of Visual Studio.

    Expectation:  A DataSet component can be added to the Data Sources list where it appears as a treeview with dropdown nodes representing the Dataset's Tables as well as the Column names within each Table.  These nodes can be conveniently dragged and dropped onto a form in the Forms Designer.

    Problem:  I have a custom Dataset derived class that dynamically defines the DataSet's tables in the class constructor.  When this component is added to the Data Sources toolbox, all the Tables in this Dataset (at Design Time) display the same list of columns which are the column names from the first Table added to the Dataset.

    The problem is easy to replicate:


    [DesignTimeVisibleAttribute(true)] [DesignerAttribute(typeof(MyDataDesigner))] [DesignerCategoryAttribute("Component")] [SerializableAttribute] [ToolboxItemAttribute(true)]




    public
    class MyDataSet : DataSet { public MyDataSet() : base("MyDataSet") { this.Tables.Add("Table1", "myNamespace"); this.Tables.Add("Table2", "myNamespace"); this.Tables["Table2"].Columns.Add("strField1", typeof(string)); this.Tables["Table1"].Columns.Add("strField2", typeof(string)); this.Tables["Table1"].Columns.Add("strField3", typeof(string)); this.Tables["Table1"].Columns.Add("boolField4", typeof(bool)); } }
    The constructor is executed in Design Time and the object appears in the Data Source tab, correctly, as a treeview:  the tree's child nodes are the two Tables and their associated columns.  However, both tables reflect the same three columns (the 3 added to "Table1").  The column added to "Table2" is not displayed under that Table (nor anywhere in the tree).

    The Tables appear to be correctly defined at Run Time, but I was hoping to get at them in Design Time to take advantage of drag and drop Form design.  I am stumped.

    Does anyone know of a workaround or correction for this issue?  Is it a bug?  Perhaps I should be serializing the object at Design Time in the constructor?

    Thanks.

    Steve
    Thursday, July 9, 2009 2:07 AM

Answers

  • Hi Steve,

    Yes, you are correct, the issue is in the first phase as in your reply: 1) In tool windows (Toolbox AND Data Sources, and we call it Data Sources Window) when Visual Studio is refreshing the objects in these containers. Your workaround is perfect and it match the cause of the issue: The Data Sources Window is assuming the type is static and cache the DataTable Property info. We are investigating the issue at the moment. 

    RE:  Although the problem makes itself known in the Data Sources window, I suspect it also occurs when DataSets are automatically added to the ToolBox, but is not apparent here because the DataSet's Tables list is not accessible within the Toolbox. 

    I do not think there is any issue related to the Toolbox as the Toolbox does not have the feature to show the DataTable list, and it does not have the feature to drag and drop creating data bound controls. The scope of the issue is limited in the Data Sources Window. 

    RE: P.S.  I am curious to see what happens if any data rows are populated into the tables during DataSet construction.
    The Data Sources Window will never try to read the data rows info, as they are really the run time data. Remember we are at design time.  

    Thanks! 

    John  

      

    • Marked as answer by Steven.M Saturday, July 11, 2009 9:22 PM
    Saturday, July 11, 2009 7:15 PM

All replies

  • Hi Steve, I have interest with your question! :)
    What is the Data Sources list/Data Sources tab? Are you refering to the Visual Studio's DataSource Window (http://msdn.microsoft.com/en-us/library/6ckyxa83(VS.80).aspx) or something your own or non-Microsoft app?
    Is your "data sources tab" looks like this: http://cid-d42c0e973d01efdf.skydrive.live.com/self.aspx/Public/datasource.JPG ?

    You see, I can "repro" your issue at least, will take a closer look tomorrow.

    John





    Thursday, July 9, 2009 7:22 AM
  • John, yes, it is the VS DataSources window I am referring to.  DataSet objects (sources) added to the DataSources window appear as your links show -- as treeview controls where the tables and their columns can be expanded and listed (and dragged and dropped onto forms in the Visual Forms Designer window).

    DataSet tables defined during constructor execution do not seem to "finish" in the Design Time environment.  Instead, only the "first" table added to the DataSet seems to be defined properly and all additional tables just mimic the columns in that first table.  Interestingly, this anomaly occurs whether the tables (and columns) are added via the "DataSet.Tables.Add()" and "DataSet.Table().Columns.Add()" methods or by the "DataSet.ReadXmlSchema()" method. 

    I should add, that although I have not explored this in depth, the data binding related Properties of form controls (e.g. TextBox) that have been created via dragging and dropping a "bogus" column name, seem to be equally "confused" by the erroneous information when working in the forms designer window.  

    Thanks for your interest in this.

    Steve
    • Edited by Steven.M Thursday, July 9, 2009 12:53 PM
    Thursday, July 9, 2009 12:41 PM
  • Hello Steve,

    I understand the situation now and I will follow up with the team in Microsoft to see this can be fixed or it is not supported. On the other hand, I would strongly recommend you to use typed dataset to define the MyDataSet class. TypedDataSet works very well with the DataSource window.

    Here is the step:

    1 Start Visual Studio, create a project
    2. Add a new project item, choose DataSet and name it as MyDataSet
    3. In the dataset designer, right click to add Table1,
    4. Right click on the title bar of table1 and add column1, etc.
    Repeat 3-4 to add more tables.

    5. Without doing anything (not even need a build), open the datasource window.
    You will see the MyDataSet and Tables should show up correctly.

    For more information on how to create typed dataset, see this: http://msdn.microsoft.com/en-us/library/04y282hb.aspx
     
    Hope this help!

    John
    Thursday, July 9, 2009 9:42 PM
  • Hey Steve,

    Additionally you could follow John's advise and then review what code is generated by the TypedDataSet code generator. They have already figured this problem out with their code generation so you might be able to copy code from what is generated into your class to fix this issues. However I would just follow John's advise and used TypedDataSet, its a simplier pattern that will definitely work.

    Thanks
    Chris Robinson
    Program manager - DataSet


    This posting is provided "AS IS" with no warranties, and confers no rights.
    Thursday, July 9, 2009 10:02 PM
  • Thanks guys.  I appreciate the input.

    I have a few pre-defined typed DataSets in the application and they indeed work excellently.  I have also used the XSD.EXE tool successfully to convert our source schemas into a typed DataSet as well.  I have also directly used the classes that XSD is based on for code generation.

    This new addition is for the programmers who will be supporting the app (which itself is a generator) and it allows for one of several external schema files (XSD) to be specified depending on the locale (or to designate an alternate version of the schema).  I am trying to do something a little fancy here and I want it to be in the design environment of Visual Studio.  While not shown in the example, the application permits the programmer to selectively choose which Tables from the source schema are to be generated within the DataSet.  This, in turn, loads the "filtered" schema into this dynamic DataSet that we are talking about.  The programmer can then customize some of the data forms within the design environment before compiling.

    There are multiple ways to skin this cat and I know it's ambitious, but it's where I've taken myself.  I was hoping someone had spotted this issue and had a workaround or a "force" method.  Either way, if we've uncovered a bug, that's a good thing.

    Thanks again.  I am pleased with the responses and am interested to see the conclusions.

    Regards,
    Steve

    Friday, July 10, 2009 12:06 AM
  • Hey Steve,

    So I took your example and attempted to compile it and use it at design time. I did the following steps

    1)Created a new Winforms project
    2)Added a new class with the exact code that you provided above.
    3)Commented out following line because I don't have a MyDataDesigner class implemented on my side and the code was not provided
    [DesignerAttribute(typeof(MyDataDesigner))]
    4)Compiled and then viewed the tool box, the new DataSet was in fact there
    5)Dragged it onto the form successfully
    6)Dragged a grid onto the form and set its datasource to be the dataset
    7)Changed the DataMember from Table1 to Table2 and back again.

    The columns that changed seem consistent with the tables and they do change for me when I change the DataMember.

    When I reviewed the TypedDataSet designer code I did not see a DesignerAttribute on there. It doesn't appear to be needed and it seems that whatever you have implemented here is not working correctly. I would change the code generation to remove this line, or you need to fix your MyDataDesigner to work properly. On how to do that I am not sure since I think that is more of a Winforms designer question not a DataSet one.

    Thanks
    Chris Robinson
    Program Manager - DataSet

    This posting is provided "AS IS" with no warranties, and confers no rights.
    Friday, July 10, 2009 4:13 PM
  • Chris,

    Thanks for spending time on this.  I confirm all your findings.

    However, the issue I am describing relates to the Data Sources window, not the Toolbox.  If you add the MyDataSet to your project's Data Sources, this creates a design tool with very different behaviors than the MyDataSet tool that is automatically added to the Toolbox. 

    I may well end up trying to go back to a Toolbox approach, but there are reasons for my choice of adding this baby to the Data Sources window (albeit, my choice may be based on an erroneous or incomplete understanding of the full purpose of the Data Sources window).  Now, I am probably going to open a can of worms that nobody wants to deal with, but here goes.  Bear with me as I explain behavior that you are no doubt familiar with.

    Dragging and dropping MyDataSet from the Toolbox brings the DataSet into the form with all the Design Time behaviors you describe.  Dragging and dropping from the Data Source window is a different animal: In fact, you cannot drag the entire DataSet.  Instead, you drag and drop individual Tables -- Tables that are exposed in a dropdown treeview list beneath the DataSet.  

    Note: Certain Design Time features of the DataSet component in the Data Sources window are operational BEFORE the object is ever added to a form.  The tool can be expanded to list its Tables and each Table can be expanded to list its Columns.  I guess this is sort of a "pre-Design Time" executional environment, similar to Toolbox-related events.  (Someday I would really like to augment the DataSet's popup menu in the Data Sources window, but let's not go there right now.) 

    Anyway, when dragging and dropping a DataSet Table from the Data Sources window, three components are created:  1) A control (typically a DataGridView or a ComboBox, which Visual Studio allows you to pre-select in a combobox associated with each Table name); 2) a BindingSource component, and; 3) a BindingNavigator component.  The automatic creation of these three components, which I desire, is what led me to try and use the Data Sources window as opposed to the Toolbox. 

    Dragging and dropping a typed DataSet Table from the Data Sources window is eloquent:  the three components are nicely integrated through their various binding properties which all point to the data objects and members you would expect.  The DataGridView is even fully constructed with all of the Table's columns in place.

    Not so when I drag a Table from my "dynamic" MyDataSet:  here the data binding properties are defaulted to empty values or generic objects -- not at all what you would expect.  For example, the "DataSource" property of the BindingSource is set to a generic DataTable object instead of MyDataSet.  (Why?)  Nonetheless, I can then manually change this property and select MyDataSet from among the "Project Data Sources" that are listed a few levels down in the property's dropdown editor.  And after changing that property I can then edit the "DataMember" property of the BindingSource where I now find a list of all the Tables in MyDataSet.  (I may as well be using Toolbox components at this point, right?)

    I need to drill down into the documentation further, but I have presumed the missing links in components and properties relative to dragging my dynamic DataSet merely meant that I needed to augment MyDataSet class (or its associated Designer) with additional bells and whistles that would correctly set these properties in the BindingSource at the time of its creation (in Design Time).  Perhaps I am actually seeing more ramifications of an underlying bug.

    I do believe the fundamental behavior of a DataSet in the Data Sources window has a bug when that DataSet defines or reads its schema via its own constructor.  Thus, my original inquiry:  Why don't the column names appear correctly under each Table (in the Data Sources window) and is there anything I can do to force a "fully reflected" construction of this object so that the columns WILL appear correctly?   

    As we have all acknowledged, a "pre-defined" typed DataSet behaves correctly (and richly) when added to the DataSources window.  Perhaps that is because of the auto-generated code embedded in a typed DataSet.  But having examined that code, I suspect the observed anomaly has more to do with the "Design Time engine" that performs the DataSet object's constructor emulation (which may indeed follow a different path when executing the auto-generated constructor code within a typed DataSet).

    I am open to suggestions (including "Steve, you need to go back to school!").  Am I headed down a dark alley with the combination of my "dynamic" DataSet and the Data Sources window (and the Design Time features that attracted me to the Data Sources window in the first place)?

    Thanks for your patience.

    Steve
    Friday, July 10, 2009 6:36 PM
  • Hey Steve,

    This is the way I look at it, it is very likely (99%) that dataset does not behave as you would expect in a designer, it was never designed for this scenario. TypedDataSet however was.

    At this point I think you need to look at two options
    1) Not using the DataSource wizard approach
       I think the toolbox scenario is actually pretty decent, not alot of code but still drag drop support.

    2) Figuring out how to make the datasoure wizard work
    There  are proven models on how to get this to work, TypedDataSet does this. You could selectively copy how it works and add this into your base class, then the designers will succeed. Also another option is to create a T4 template to create the TypedDataSet you would like.
    Here is a link to what this technology is.
    http://www.olegsych.com/2007/12/how-to-create-a-simple-t4-template/
    Essentially it is about creating a text template, this might be what you need to generate the code required

    Maybe there is another option here that I don't see. But I think what I'm trying to point out is you have all the knowledge for the possible solutions at your finger tips. Its up to you to figure out what makes sense here.

    If it were me, personally I think this advanced designer scenario is a waste of time to figure out. There are equally simple ways of getting this work done. Instead of one drag and drop its 4 drag and drops. I don't see the huge gain in productivity that this complicated component allows. Perhaps there is a reason in your case.

    Thanks
    Chris Robinson
    Program manager - DataSet


    This posting is provided "AS IS" with no warranties, and confers no rights.
    Friday, July 10, 2009 11:31 PM
  • Chris,

    I am not sure I understand your point about DataSet not intended to work in a designer.  Most references to "designer" imply customization of components for the Forms Designer environment.  DataSets work just fine here as far as I have seen.  A custom designer can be associated and you can augment the DataSet's command menu without issue.  Also, I don't view the Data Sources window as "advanced" or complicated.  It's a nicely integrated part of Visual Studio, especially for those of us working with 3-tier architectures.

    I think it's important to reiterate that there seem to be TWO logical "Design Time phases" where component constructors are emulated:  1) In tool windows (Toolbox AND Data Sources) when Visual Studio is refreshing the objects in these containers, and;  2) in Forms Designer when the object is dragged to a form and/or the form is being refreshed.  I hope I am being clear about this distinction because I believe the bug is in the first phase only.  (Which would explain why we don't see a problem when working in the Forms Designer.)
     
    Anyway, I took your advice and looked over the typed DataSet code once more.  The only thing I could see that might have an impact is the fact that the typed DataSet code generator creates a derived DataTable class for each of its data tables and instantiates these (instead of constructing directly from the framework DataTable class).  So I thought I'd give it a shot.  Eureka!  It worked.

    So I have a workaround, but it requires that a separate class be generated as the basis for each Table to be created.  This would seem to underscore that there is a bug in the Design Time constructor emulation, related to either the DataSet class, the DataTable class, or both.   Although the problem makes itself known in the Data Sources window, I suspect it also occurs when DataSets are automatically added to the ToolBox, but is not apparent here because the DataSet's Tables list is not accessible within the Toolbox. 

    BTW, no attributes are needed for this test, so I've stripped them out.  Here's the code:

    using System;
    using System.ComponentModel;
    using System.Data;
    
    namespace DataSetTest
    {
    	public class MyDataSet : DataSet
    	{
    		private DataTable[] tblarray = new DataTable[4];
    
    		// public constructor makes this a real data source
    		// (causes Tables to appear in Data Sources window instead of object properties)
    		public MyDataSet()
    			: base("MyDataSet")
    		{
    			// add first table using framework DataTable
    			this.Tables.Add("Tbl1");
    			this.Tables["Tbl1"].Columns.Add("Tbl1-C1");
    			this.Tables["Tbl1"].Columns.Add("Tbl1-C2");
    			this.Tables["Tbl1"].Columns.Add("Tbl1-C3");
    
    			// add second table using derived DataTable
    			this.Tables.Add(new tableObj("Tbl2"));
    
    			// add third table using framework DataTable
    			this.Tables.Add("Tbl3");
    			this.Tables["Tbl3"].Columns.Add("Tbl3-C1");
    			this.Tables["Tbl3"].Columns.Add("Tbl3-C2");
    			this.Tables["Tbl3"].Columns.Add("Tbl3-C3");
    
    			// add fourth table using derived DataTable
    			this.Tables.Add(new tableObj("Tbl4"));
    		}
    	}
    
    	public class tableObj : DataTable
    	{
    		public tableObj(string tableName) : base(tableName)
    		{
    			this.Columns.Add(new DataColumn(this.TableName + "-" + "C1", typeof(string)));
    			this.Columns.Add(new DataColumn(this.TableName + "-" + "C2", typeof(string)));
    			this.Columns.Add(new DataColumn(this.TableName + "-" + "C3", typeof(string)));
    		}
    	}
    
    	public class tableObj2 : DataTable
    	{
    		public tableObj2(string tableName) : base(tableName)
    		{
    			this.Columns.Add(new DataColumn(this.TableName + "-" + "C1", typeof(string)));
    			this.Columns.Add(new DataColumn(this.TableName + "-" + "C2", typeof(string)));
    			this.Columns.Add(new DataColumn(this.TableName + "-" + "C3", typeof(string)));
    		}
    	}
    }
    
    1. Create a Class Library project
    2. Add the above classes and compile (I have tested this under both .NET Framework 2.0 and 3.5)
    3. Use "Data->Add New Data Source..." menu to add MyDataSet to the project's Data Sources
    4. Also, select the "Data->Show Data Sources" menu which will make a Data Sources tab appear next to the Toolbox tab
    5. Optionally, add a form to the project if you want to play around with dragging and dropping this data source (but no need for this)

    Now, in the Data Sources window you can expand the Tables under MyDataSet.  You will see that the columns listed for "Tbl3" are replicates of "Tbl1".  Columns listed for "Tbl4" are replicates of "Tbl2".

    Now, switch the code line that creates the fourth table ("Tbl4") to use "tableObj2" instead of "tableObj" as its basis.  Recompile and you will see that the columns for "Tbl4" are now correct.  It would appear that a DataTable class, derived or base, gets constructed once and once only in this Design Time phase. 

    I tried adding a DataTable array to MyDataSet to hold "fully constructed" DataTable objects prior to adding them to the DataSet.  My hope was that this might force construction of distinct objects.  No such luck -- you still need to have a unique class defined for each table. 

    I would summarize the issue this way:  At Design Time, when a derived DataSet (having a public constructor) is added to the project's Data Sources, any DataTable object added to the DataSet during its construction (i.e., at the time it is being constructed in the Data Sources window) will have a schema that is a clone of the DataTable that was first added using the same underlying DataTable class.  To work around the problem, you must construct each of your DataTables from a unique DataTable-derived class.  In other words, the system is not creating separate instances of a DataTable class each time its constructor is called.  An object is created on the first call to a DataTable class's constructor and that same object (or clone thereof) is then returned for all subsequent constructions that are based on that same class.

    I searched through the Design Time guidance and can see no caveats or warnings of such "single-instance" behavior. 


    Steve

    P.S.  I am curious to see what happens if any data rows are populated into the tables during DataSet construction.

    Saturday, July 11, 2009 6:22 PM
  • Hi Steve,

    Yes, you are correct, the issue is in the first phase as in your reply: 1) In tool windows (Toolbox AND Data Sources, and we call it Data Sources Window) when Visual Studio is refreshing the objects in these containers. Your workaround is perfect and it match the cause of the issue: The Data Sources Window is assuming the type is static and cache the DataTable Property info. We are investigating the issue at the moment. 

    RE:  Although the problem makes itself known in the Data Sources window, I suspect it also occurs when DataSets are automatically added to the ToolBox, but is not apparent here because the DataSet's Tables list is not accessible within the Toolbox. 

    I do not think there is any issue related to the Toolbox as the Toolbox does not have the feature to show the DataTable list, and it does not have the feature to drag and drop creating data bound controls. The scope of the issue is limited in the Data Sources Window. 

    RE: P.S.  I am curious to see what happens if any data rows are populated into the tables during DataSet construction.
    The Data Sources Window will never try to read the data rows info, as they are really the run time data. Remember we are at design time.  

    Thanks! 

    John  

      

    • Marked as answer by Steven.M Saturday, July 11, 2009 9:22 PM
    Saturday, July 11, 2009 7:15 PM
  • Thank you both once again.  I'm off and running once I read up a little more on the CodeDOM namespace.

    Please let me know if there's ever a patch.  Thanks.

    Steve
    Saturday, July 11, 2009 9:22 PM
  • Hi Steve, we have a fix for the issue, which will be in the release of VS 2010.
    Let me know if you have any more questions.

    Thanks!


    John
    Tuesday, July 14, 2009 11:00 PM