During a gridview => database update function, I use a column-by-column conversion to string in order to pass data back to my database as a whole command string. I'm aware that this sounds convoluted, so here is an example:
Classes in use:
InvoiceHandler.cs
Default.aspx.cs
Since this is a rather large snippet, here is a pastebin:
Default.aspx.cs: http://pastebin.com/Y3fJZ36Z
InvoiceHandler.cs: http://pastebin.com/ZsdAnDxr
At the first point of conversion (invoiceTableEdited.Columns["Column1"].ColumnName = "#K_INV";) I get a NullReferenceException error, assumedly because the method call in Default.aspx.cs
handler.invoiceTableEdited = ViewState["invoiceTable"] as DataTable;
handler.invoiceTableEdited.Rows[row.RowIndex]["K_INVOICE"] = sK_INVOICE;
appears to be having trouble.
What must I do to resolve this?
I'll be honest, it's not very clear how you're trying to create the column collection for the table 'InvoiceTableEdited'. What it looks like you're doing in OnUpdate is you are assigning a string variable to a row in the table with a given index and a column that doesn't currently exist. Your basically saying put this string into a cell with a row number of 'x' and a column name of "column1". At this point "column1" doesn't exist.
I'd create the columns you need first in InvoiceHandler.cs like this (assumes they're string):
invoiceTableEdited.Columns.Add("MyColumn1", typeof(string));
invoiceTableEdited.Columns.Add("MyColumn2", typeof(string));
Obviously if you have loads of columns and you don't care about their names then just create a loop and add them that way. This will give them the naming convention "ColumnN", where 'N' is the number. You can then assign a name to them by referencing there name ("Column1" for example) or using their index.
Related
I have a datatable and I am masking one column by adding new column name as MASKEDSSN.
It has been masked. But i need to Remove Old Column name SSN. While Removing it throws an error as
Cannot remove this column, because it is part of an expression:
MaskedSSN = 'XXX-XX-'+SUBSTRING(CONVERT(SSN, System.String),6,4).
Code:
DataTable employeeTable = new DataTable();
employeeTable.Rows.Add("123455789");
employeeTable.Rows.Add("123447789");
employeeTable.Rows.Add("823456719");
employeeTable.Columns.Add("SSN");
int index = employeeTable.Columns["SSN"].Ordinal;
employeeTable.Columns.Add("MaskedSSN", typeof(string));
employeeTable.Columns["MaskedSSN"].Expression = "'XXX-XX-
'+SUBSTRING(CONVERT(SSN, System.String),6,4)";
DataTable newsss = new DataTable();
newsss = employeeTable.Copy();
newsss.AcceptChanges();
newsss.Columns.RemoveAt(index);
newsss.Columns.Remove("SSN");
You are using Expression syntax to build MaskedSSN
employeeTable.Columns["MaskedSSN"].Expression = "'XXX-XX-
'+SUBSTRING(CONVERT(SSN, System.String),6,4)";
If you make masking manually with AddRow, you can delete old SSN Column.
Edit
Func<string, string> MaskingFnc = (string ssnParam) => string.Format("XXX-XX-{0}", ssnParam.Substring(4,6));
DataTable employeeTable = new DataTable();
employeeTable.Columns.Add("SSN")
employeeTable.Columns.Add("SSNMasked")
employeeTable.Rows.Add("123455789", MaskingFnc("123455789"));
employeeTable.Rows.Add("123447789", MaskingFnc("123447789"));
employeeTable.Rows.Add("823456719", MaskingFnc("823456719"));
Expressions, or computed columns, relies on the source column to have the data in, because it is referencing the data, it doesn't COPY the data and then mask it.
So if your original column data changes, the new column will automatically have the same value.
When you understand this dependency, you will realize that you can't just remove the source column, because if you do, you will have a null reference exception happening. Think of it as excel spreadsheet. Column A has a value, Column B uses Column A's value to determine a percentage (or show it in a currency format). If you delete Column A, Columb B will immediately get an error (#REF! or something)
Your question on CodeProject has more detail, which would be appreciated on StackOverflow too: https://www.codeproject.com/Questions/1272445/Cannot-remove-this-column-because-it-is-part-of-an
In there, you show you try to copy the table, and then remove the column from it, while still having the expression, which results in an error.
Hopefully my explanation highlights the dependency between a column and a computed column/expression.
I would suggest formatting the data in the front-end (that's why we have front-end), or, if it's part of an API, format it in the SQL output.
Any input can also be masked so that data is inserted correctly BEFORE having to read it out anywhere.
You can mask manually using string.Format (note, below might not compile, I leave it to you to figure out)
string result = string.Format("XXX-XX-{0}", SSNColumnValue.Substring(4,6));
Hope this helps guide you in the right direction
i have a strong typed DataTable named Account wich i sorted on Account.FullName:
DataView dvAccount = new DataView(dtAccount)
dvAccount.Sort = "FullName desc";
The fullname is a generated field from my DataSet after my query, based on first name, middle, last etc. This means that sorting by SQL query is not an option unfortunately.
First i tried to get the table like this:
dtAccount = dvAccount.Table()
But this gave me the original Table the Dataview was based on. So after reading online i found out that i should have used the DataView.ToTable() function instead of the DataView.Table() function:
dtAccount = dvAccount.ToTable() as dsAccount.AccountDataTable; // returns null
dtAccount = ((dsAccount.AccountDataTable) dvAccount.ToTable()); // gives Convertion to Typed Datatable Error
Now i get the problem that my Account Table is Strong typed. so searching online tells me that i could go with the DataTable.Merge() Function or DataTable.ImportRow() for each row but these are told to be a very heavy procedures because every row gets checked on the Type. what's the best practice solution to this situation?
I just had the same issue. I used this kind of solution.
dtAccount = New dsAccount.AccountDataTable;
dtAccount.Merge(dvAccount.ToTable());
This works fine for me.
Tell me if you have a better one.
I have a form with a DataGridView widget and I need to get the index of the column with the selected name.
For example, let's say that I have a table with 2 columns: Name, Surname. I need a way to get index of the column name. The problem is that it changes all the time depending on the DataSource but that column always has the same name "Name".
Does anyone know how to solve the problem?
To retrieve a DataGridView column by name you simply reference it through the columns collection indexer:
datagridview1.Columns["columnName"]
Then you can get the column index from that column:
datagridview1.Columns["columnName"].Index;
Do note that if you use an invalid column name then this reference will return null, so you may want to check that the column reference is not null before using it, or use the columns collection .Contains() method first.
If I am right, e.ColumnIndex will also work for this. you can check the MSDN Documentation here
You can get the index by using the Index property of the DataGridViewColumn widget, as such:
ColumnName.Index
This avoids the need for checking whether the column name is valid at runtime as it will generate a compilation error if the column does not exist. This also makes refactoring easier.
I recommend you give the columns a sensible name (for example DCOL_SomeName) so that you can easily distinguish them. Including the name of the DataGridView widget would help if you have multiple DataGridView widgets on the same form.
create a static class below the code
public static class MyTools
{
public static int IndexByName(this DataGridView dgv, string name)
{
foreach(DataGridViewColumn col in dgv.Columns)
{
if(col.HeaderText.ToUpper().Trim() == name.ToUpper().Trim())
{
return col.Index;
}
}
return -1;
}
}
and then call it with your dataGridView
int index = datagridview1.IndexByName("columnName");
I have found it safer to use the column object's Name property, instead of using the column name as a string, because this allows for more consistent code refactoring in the future.
datagridview1.Columns[column1.Name].Index;
Also, it is important to first make sure the column is not null and, as others have said, that it is contained within the datagridview.
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 a DataSet with 3 DataTables:
dtPerson
dtSalary
dtFriend
Every person has salaries, and every person has one friend.
I've added a column dcHisFriend into dtSalary and would like to display friend name of a person owning specified salary.
So dtPerson has a column NAME, dtSalary has column VALUE and dtFriend has a column NAME.
I've added column dcHisFriend and set Expression to this:
dtSalary.Add(dcHisFriend);
dcHisFriend.Expression =
"Max(Parent.Child(Persons_Friend).NAME)";
But this obviously does not work.
Could you please tell me how to
put into column dcHisFriend name of a friend of a person with a salary into salary table?
I think, there is no way how to access any other row in "Expression" in DataColumn.
The only way, how to achieve similar behaviour is hook to DataColumnChanged event on DataTables where are source data and then set the computed value to the regular column (=column without expression).
There is actually a way to do this, provided the relationships between your tables are 1 to 1 (though missing rows aren't a huge problem): Create two relations rather than one, i.e.
var joinColT1 = table1.Columns["ID"];
var joinColT2 = table2.Columns["FK_IDT1"];
var rel1 = new DataRelation("R1To2", joinColT1, joinColT2, false);
var rel2 = new DataRelation("R2To1", joinColT2, joinColT1, false);
theDataSet.Relations.Add(rel1);
theDataSet.Relations.Add(rel2);
// Add the column you're after
var hisFriend = new DataColumn("HisFriend", typeof(string), "Parent([R2To1]).[HisFriend]");
table1.Columns.Add(hisFriend);
// Add a back-reference to the other table against the friend if you want, too
var hisFriendsSalary = new DataColumn("HisFriendsSalary", typeof(decimal) "Parent([R1To2]).[Salary]");
table2.Columns.Add(hisFriendsSalary);
A couple of notes, though: first, when I was first experimenting with this, I got syntax errors without the square brackets around the relation names in the expression. That might just have been to do with the names I'd used for the relations though.
Secondly, I believe the result of Expressions are stored against the rows (they aren't computed "just in time" on access, they're computed when values change, and the results are kept). That means you are storing the data twice by using this approach. Sometimes that's fine, and sometimes it isn't.
Third, you'll note that I'm not using constraints. That's because in my typical use-cases, I'm not expecting every row to have an analogue in the other table (that's why there are two tables in the first place, quite often!). That may (I haven't checked dotnetframework.org) have an impact on performance.