I create a custom dataset that I pass off to a black boxed component. The dataset consists of usually 5-6 tables (with unique names assigned by me). The component takes the dataset and builds a drop down combo box based off the table names. What I am needing to do though is to change the ordering of the tables within the dataset. I need to do this so I first offer up to the user the appropriate selection in the drop down (based off what section in the application they are in). So for instance...if they are in "Section A" then that is the first table name shown in the drop down list...if the user goes to "Section F" then that is what is shown in the list first...so on and so forth.
The more code intensive way is of course to just change the ordering in which I add the tables to the dataset. This would work, but I thought there had to be some way to do this more elegantly and with less code.
I am working in C# with the 3.5 framework.
Remember that DataSets and their contents are stored on the heap, so you can have a DataTable object in more than one place in the DataSet.
Simply create your DataSet with a dummy DataTable in position zero. Then, based on whatever section they'e in, you put the corresponding table in position zero. Your table name will appear twice in your DropDownBox, once as the 'default' and again below in its proper context and order with the other tables.
public class ThisThing
{
private DataSet myDS = new DataSet();
//Populate your DataSet as normal
public DataSet ChangeLocation(int CurrentSectionNumber)
{
myDS.Table[0] = myDS.Table[CurrentSectionNumber]
}
}
I'm not sure trying to force your ordering information into the DataSet's data structure is the most intuitive approach. You might consider passing an ordered list of DataTable instead of (or in addition to) the DataSet.
Related
Constructing a BindingSource.Filter string feels like an ugly, manual, forced way of filtering already retrieved data.
No explicit type checking
No explicit column name checking
No explicit SQL syntax checking
Requires manual ToString() formatting
DataSet design changes not propagated to Filter
Managing a Filter with multiple criteria from multiple controls quickly becomes tedious, error-prone, and unwieldy.
Using a typedTableAdapter.FillBy(typedDataSet.typedTable, #params ...) is a powerful, easy, and straightforward method for "filtering" between the database and the DataSet.
Does .NET provide any strongly-typed filtering between a strongly-typed DataSet and Form controls (perhaps through a BindingSource)?
Initial Bounty:
The initial bounty was awarded for a proof of concept (using a LINQ query as the DataSource). However, it does not demonstrate how to actually access the strongly-typed typedTableRow to perform the filtering.
Additional Bounty:
All of the casting through IList, DataView, DataRowView, DataRow, and typedTableRow has proven to be quite confusing.
Object Generic Watch List Type
------ -------- ------------
myBindingSource.List IList {System.Data.DataView}
myBindingSource.List[0] object {System.Data.DataRowView}
((DataRowView)myBindingSource.List[0]).Row
DataRow typedTableRow
Demonstrate a BindingSource with a DataSource using a strongly-typed LINQ query (ie: with typedTableRow fields accessible in .Where( ... )).
Notes (for shriop):
The form controls are bound to myBindingSource.
myBindingSource.DataSource: typedDataSet
myBindingSource.DataMember: typedTable
Filter Code (applied in FilterBtn_Click()):
myBindingSource.DataSource
= typedDataSet.typedTable.Where( x => x.table_id > 3).ToList();
After filtering, the BindingNavigator shows the appropriate number of records. However, if I navigate to any record which contains a null value, I get a StrongTypingException thrown in typedDataSet.typedTableRow.get_FIELDNAME(). Since this behavior only happens after filtering, I assume the LINQ filtering breaks something in the data binding.
Ok, I think this is what you want. I created a typed DataSet called AdventureWorks and added the Product table to it. I then added a DataGridView and a TextBox to a Form. I added an instance of the typed DataSet to the form. I added a BindingSource to the form. I set the DataSource of the BindingSource to the DataSet instance on the form. I set the DataMember to the Product table, which generated a ProductTableAdapter on the form. I set the DataSource of the DataGridView to the BindingSource. I bound the Text property of the TextBox to the Name property of the BindingSource, which resolves to the Name column of the Product table. In OnLoad, it had already generated a Fill for me using the TableAdapter and the Product DataTable. I then just had to add a single line to set my typed filter:
this.bindingSource.DataSource = this.adventureWorks.Product.Where(p => !p.IsProductSubcategoryIDNull()).ToList();
I then ran the form and was able to see only the filtered set of rows, and as I clicked through them, the text of the TextBox would change to match the name of the product of the selected row.
The ToList is key because the BindingSource does something goofy when either binding or supplying values out to the bound controls where without it you will get an exception that says
The method or operation is not implemented.
at System.Linq.Enumerable.Iterator`1.System.Collections.IEnumerator.Reset()
...
You also have to remember to watch out for the nullable fields when applying your filter criteria and make sure you're using the typed Is*Null() methods.
While this is a fairly straightforward way, it throws exceptions when it displays column values that have nulls, unless you go into the DataSet designer and change the option for handling nulls to return a null instead of throwing an exception. This works for string columns, but not so well for other column types like DateTime.
After ALOT of research into how DataView implements this, which DataTable uses internally, I can't find a simple fully functional implementation and did find this answer which best describes the pain, Data binding dynamic data .
I did find a pretty simple solution if you're ok with binding to a copy of the data, using some logic from Simple way to convert datarow array to datatable . This gets you back to a DataTable, and using it's implementation, but with only your rows after filtering.
DataRow[] rows = this.adventureWorks.Product.Where(p => !p.IsProductSubcategoryIDNull()).ToArray();
if (rows.Length > 0)
{
this.bindingSource.DataSource = rows.CopyToDataTable();
}
else
{
this.bindingSource.DataSource = rows;
}
Now you should still be able to use this copy of the data to send updates back to the database if you get the DataTable back out of the DataSource, making sure that it's a DataTable and not a DataRow[], and send that DataTable into the TableAdapter's Update method. Then, depending on how you're doing things, you could refill your original table, and reapply your filter.
You can use linq on binding source:
this.BindingSource.DataSource = ((IList<T>)this.BindingSource.List).Where( ... );
I have done same thing in vb.net and share it here maybe useful for someone:
I have created an extension for filtering TypedTables and filling my BindingSource.Datasource from a filtered view of exiting filled typedTable keeping original table and keeping schema in returning table (returning typedTable instead of DataTable):
Imports System.Runtime.CompilerServices
Imports System.Windows.Forms
Public Module DB_Extensions
<Extension()> _
Public Function LINQ_Filter(Of RowT As DataRow)(ByRef table As TypedTableBase(Of RowT), predicate As System.Func(Of RowT, Boolean)) As TypedTableBase(Of RowT)
' Create a clone of table structure(without data) to not loose filtered data on original table and return in a new table instead
Dim ret As TypedTableBase(Of RowT) = table.Clone()
' Using .ImportRow() method garantees to not loose original table schema after return
For Each r As RowT In table.Where(predicate)
ret.ImportRow(r)
Next
Return ret
End Function
End Module
and use it simply this way (needs to import DB_Extensions module to work):
myBindingSource.DataSource = someTypedTable.LINQ_Filter(your_filters)
May I tentatively offer an alternative to this approach (of LINQ directly into the Bindingsource filter).
I have wresteld with this problem for filtering data with complex sort criteria especially where data is matched with other sources. I have also need to keep update feature of the tableadapter working which seems to preclude some strastegies. In the past I dynamically created an adhoc filter column and used that but the upkeep of keeping hte datatable straight was rather tedious and it isn;t partuicularly fast or pretty.
Recently I realised that the bindingsource filter has a simliar option to SQL regards the IN command.
An example (sorry its VB) would be bindingsource.filter = "[columnname] IN ('n','n','n','n')" etc where each N is one of a list of values to match.
It is quite a simple matter to create a bindingsource extension to take a list and return the compiled filter string.
By utilising this method you can use Linq (or any other method) to create your inclusion list. For my part I used the unique ID keys of the records I want to include for the contents of my list (of integer usually).
There seems to be scant documentation on the facilites or limits of the bindingsource filter however I have seen reports of people using IN with very large strings. I personally don't deal with massive data so it's not a problem for me but maybe, if you consider this appraoch useful, you woud want to test those limits (and performance of course).
I hope this helps spark someone's imagination - but if you do want to shoot me down - please - be gentle :)
I have a client report (RDLC) with these specifications. and I am using VS 2008 to implement this
I have a DataSet with 4 Different DataTable for each section. and note that all of the report located in a List in order to print a large group of Data with one request.
Description
all of the report located in a List // to print some different data with one request
At top (in the list) we have a rectangle which include some Textbox to represent common Information
At Middle I have 2 different List which include some TextBox To represent List of Professor and Managers ( it also has to show their Picture, with Name and etc)
Then I have another List to represent The Organizations Information. ( it use some TextBox inside of a List to represent All of the Organizations Information)
As Client Report work with One Dataset. I provide four DataTable for my different Report Sections.
Each DataTable Has an ID for Grouping
The Common DataTable ID use to group the entire of the list for another Object (Here University)
How Can I implement Such a report, While I can't use Different DataTable for grouping because If I do such a Thing I can Display only the First Field of the groups
Any Help is Appriciated
Thank you in advance
The simplest way will be to create an unnormalized data-view by joining your four data tables and then bind your report to it. You have group on ID (that is common to all data tables).
Yet another option would be to use sub-reports but IMO, its more complicated/inefficient to use them instead of above option.
I'm using table adapters and datasets in .NET (version 2.0).
I have two tables like this:
Table1
------
...
TypeId
Table2
------
Id
Name
where Table1.TypeId is linked to Table2.Id.
I've strongly generated these types by using the wizard so now I can do things like this:
Table1Adapter adapter = new Table1Adapter();
Table1DataSet data = adapter.GetData();
foreach(Table1Row row in data) { ... }
// Can now iterate through and display the data
This all works fine. But now I also want the data from Table2. I have noticed that in row there is generated field Table2Row which seems ideal but it is set to null. How do I populate this dataset properly?
Each DataTable in Typed-Dataset has its own TableAdapter. So you'll have to the same step for each DataTable in the dataset.
There does not exists any api that lets you do this auto-fill of the entire typed-dataset or no such code is generated within typed-dataset that supports this. It is also difficult to do this because TableAdapters do not have a common base-class that can let you do this.
If you really need to do this, you'll have to maintain a collection of DataTable type-names and TableAdapter type-names and iterate over the collection to perform the dataset fill.
So I recommend to fill dataset for each table in 'hard-code' manner.
Actually there is a Microsoft Data Provider called `MSDataShape', which provides this functionality. It was flagged for depreciation last when I checked and I don't know what the current status and future plans are, or what replaces it.
Here's an example of a single "SQL command", that will return, I believe, a DataSet with two nicely related DataTable-s:
SHAPE {select * from customers}
APPEND ({select * from orders} AS rsOrders
RELATE customerid TO customerid)
Potential replacement to look at is well-crafted FOR XML query, but I have not played with that.
EDIT:
I think this SO answer does exactly that using XML.
In my DataGridView I'am displaying a buch of columns from one table. In this table I have a column which points to item in another table. As you may already guessed, I want to display in the grid in one column some text value from the second table instead of and ItemID.
I could not find a right example on the net how to do this.
Lets assume that I have two tables in databes:
Table Users:
UserID UserName UserWorkplaceID
1 Martin 1
2 John 1
3 Susannah 2
4 Jack 3
Table Workplaces:
WorkplaceID WorkplaceName
1 "Factory"
2 "Grocery"
3 "Airport"
I have one untyped dataset dsUsers, one binding source bsUsers, and two DataAdapters for filling dataset (daUsers, daWorkplaces).
Code which I am performing:
daUsers.Fill(dsUsers);
daWorkplaces.Fill(dsUsers);
bsUsers.DataSource = dsUsers.Tables[0];
dgvUsers.DataSource = bsUsers;
At this point I see in my dgvUsers three columns, UserID, UserName and UserWorkplaceID. However, instead of UserWorkplaceID and values 1,2,3 I would like to see "Factory", "Grocery" and so on...
So I've added another column to dgvUsers called "WorkplaceName" and in my code I am trying to bind it to the newly created relation:
dsUsers.Relations.Add("UsersWorkplaces", dsUsers.Tables[1].Columns["WorkplaceID"], dsUsers.Tables[0].Columns["UserWorkplaceID"]);
WorkplaceName.DataPropertyName = "UsersWorkplaces.WorkplaceName";
Unfortunately that doesn't work. Relation is created without errors but fields in this column are empty after running the program.
What I am doing wrong?
I would like to also ask about an example with LookUp combobox in DataGridView which allow me to change the UserWorkplaceID but instead of numeric value it will show a tex value which is under WorkplaceName.
Thanks for your time.
In my opinion, the best decision would be to use the DataGridViewComboBoxColumn column type. If you do it, you should create a data adapter with lookup data beforehand and then set DataSource, DataPropertyName, DisplayMember, and ValueMember properties of the DataGridViewComboBoxColumn. You could also set the DisplayStyle property to Nothing to make the column look like a common data column. That's it.
I don't know if you can do exactly what you want, which seems to be binding the DataGridView to two different DataTable instances simulataneously. I don't think the DataGridView class supports that -- or if it does it's a ninja-style move I haven't seen.
Per MSDN, your best bet is probably using the CellFormatting event on the DataGridView and check for when the cell being formatted is in the lookup column, then you could substitute your value from the other table. Use an unbound column for the WorkplaceName column, hide the UserWorkplaceID column and then implement the CellFormatting event handle to look up the value in the row, e.g.:
private void dgv_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
if (dgv.Columns[e.ColumnIndex].Name.Equals("WorkplaceName")
{
// Use helper method to get the string from lookup table
e.Value = GetWorkplaceNameLookupValue(
dataGridViewScanDetails.Rows[e.RowIndex].Cells["UserWorkplaceID"].Value);
}
}
If you've got a lot of rows visible, this might impact performance but is probably a decent way to get it working.
If this doesn't appeal to you, maybe use the DataTable.Merge() method to merge your lookup table into your main table. A quick glance at one of my ADO.NET books suggests this should work, although I have not tried it. But I'm not sure if this is too close to the idea suggested previously which you shot down.
As for your second question about the lookup combobox, you should really post it in a separate question so it gets proper attention.
You could make SQL do the job instead. Use a join to return a table with Workplace names instead of IDs, output that table into a dataset and use it instead.
eg.
SELECT A.UserID, A.UserName, B.WorkplaceID
FROM Users A
JOIN Workplaces B ON A.UserWorkplaceID = B.WorkplaceID
Then use its output to fill dsUsers.
I've checked through the history of questions, hit google, and other results and still am completely baffled about the C# reportViewer. It appears to only want to work with "typed" datasets. As many other people have inquired and I saw no real answers, maybe a fresh post will pull something new.
In its simplest form, I have a business object that performs a query from a database. I know what the resulting columns are and want to have it plugged into a specific report that is properly "formatted" as I need, as opposed to a simple columnar dump.
Since the query returns a "DataTable" object, but no known columns are "typed", I'm hosed.
As mentioned in other posts, if I have a system of 200+ tables, 400+ views and 200+ stored procedures, I don't want to type-cast everything. Especially if I am doing a NEW query that is a result of individual tables joined from some NEW stored procedure.
It shouldn't be this difficult to draw a report. If I type the column wrong, or SUM(), COUNT(), or whatever incorrectly, that's my fault, but at least let me get an untyped table into a report.
Help...
The DataTable's columns don't need to be typed, they can all use the default of string.
What I did was I added a DataSet to my project, and designed the DataSet to match my query. I left all columns as strings. In the RDLC, I set up a table using this DataSet as my datasource, strictly for design time purposes.
At runtime, I instead dynamically swapped in a DataTable I generated and made sure it matched the design time DataSet (since they are all strings, I just need to make sure my DataTable has the same number of columns and the column names match).
DataTable dt = new DataTable();
DataColumn dc = dt.Columns.Add();
dc.ColumnName = "DataColumn1";
dc = dt.Columns.Add();
dc.ColumnName = "DataColumn2";
dt.Rows.Add(new object[] { "Frank", 32 });
this.reportViewer1.LocalReport.DataSources.Clear();
this.reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("DataSet1_DataTable1", dt));
this.reportViewer1.RefreshReport();
At runtime the ReportViewer loads this DataTable as its data source and uses it to populate the table.
Is that what you are after?