How to access Parents Child in DataColumn Expression - c#

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.

Related

How do I update a given SQL row in C# when it has no known or guaranteed unique ID?

I need to go through a database finding all text-like fields, checking for a particular set of URLs that might be in these fields and modify it using some regex.
The actual text manipulation part is fine, but since I'm generically going through tables, some of which seem to not have primary keys, I'm wondering how to update these rows once I've read this. I'll give a dummy example below.
foreach(var matchingTable in tables){
foreach(var matchingColumn in columns){
SqlCommand currentCommand = new SqlCommand("select * from #matchingTable;");
currentCommand.Parameters.AddWithValue("#matchingTable", matchingTable);
using (SqlDataReader reader = currentCommand.ExecuteReader())
{
while (reader.Read())
{
if(/* logic to check reader[matchingColumn]*/){
/*edit row to change column matchingColumn, a row which I can't be sure has any uniquely identifying factor*/
}
}
}
}
}
Is it possible to edit this arbitrary row or will I have to change how I'm doing this?
If a table do not have guaranteed unique id then there is no value of a duplicated record. And so if a duplicated record is also updated then no harm will be done.
You can consider all the fields as one composite field and perform the update.
One thing to keep in mind is the duplicate records will also be updated.
This might be easier to solve inside the database. You can write a cursor equivalent to the two for-loops to select the table and column. From there you can write a simple UPDATE/WHERE using your regular expression.

Can't call DataTable.Load() when IDataReader source has a VARCHAR(256) Identity column

I'm running into some trouble trying to load data into a DataTable using an IDataReader. To keep it really simple, I just call ExecuteReader() on the command, create a DataTable, call Load() on it and feed it the object implementing IDataReader:
...
if(dataReader.HasRows)
{
DataTable tempDT = new DataTable();
tempDT.Load(dataReader);
....
}
...
This works in the vast majority of cases. However, in (rare) circumstances, I get the following exception (column name is obviously variable - in this case, it's ID):
Error - MaxLength applies to string data type only. You cannot set Column `ID` property MaxLength to be a non-negative number
I investigated the source table I was trying to load, and I suspect that the problem stems from it having a VARCHAR(256) ID column, that is a Required, Unique, Key (the issue doesn't seem to occur when the PK is a regular old int). This type of situation is really uncommon in the source data, and while it definitely isn't ideal, I can't modify the schema of the source data.
I took a look at the SchemaTable in more detail, and I am at a loss:
ColumName - ID
ColumnSize - 256
ProviderType - NVarChar
DataType - {Name = "String" FullName = "System.String"}
IsIdentity - True
IsKey - True
IsAutoIncrement - True
IsUnique - True
It just doesn't make sense to me. The source table uses unique codes as the ID, and while it isn't the way I would've designed it, it's.. fine. But I don't understand how a String/Varchar can ever be an identity, auto-increment, etc.
Unfortunately, I'm at the mercy of this source data and can't mess with it, so I'm hoping someone here might have more insight into what exactly is going on. Can anyone conceive of a way for me to Load() my DataTable without applying all the constraints from the IDataReader source data? Is there an entirely alternative approach that would avoid this problem?
Thanks for reading, thanks in advance for any assistance. It's my first question so be gentle. If there's any more information that would help, please let me know!
EDIT: Some people asked for the full code for loading the DataTable. Appended here. Should add that the CacheCommand/etc. comes in from this 'InterSystems.Data.CacheClient' assm. Kinda hoping the problem can be approached more generically. In this case, the Query string is just a 'SELECT TOP 10 *' test.
using (CacheConnection cacheConnection = new CacheConnection())
{
cacheConnection.ConnectionString = connectionString;
cacheConnection.Open();
using (CacheCommand cacheCommand = new CacheCommand(Query, cacheConnection))
{
using (CacheDataReader cacheDataReader = cacheCommand.ExecuteReader())
{
if (cacheDataReader.HasRows)
{
DataTable tempDT = new DataTable();
tempDT.Load(cacheDataReader); // Exception thrown here.
cacheConnection.Close();
return tempDT;
}
else
{
cacheConnection.Close();
return null;
}
}
}
}
EDIT 2: In case it's not clear, I'm trying to extract the entirety of a (small) table from the Cache DB into a DataTable. I normally do this by calling dataTable.Load(cacheDataReader), which works fine 99% of the time, but breaks when the source table in the Cache DB has an identity column of type VARCHAR.
Calling Load() on my DataTable object (which is empty) causes it to infer the schema based on the result set from the imported IDataReader (in this case, CacheDataReader). The problem is that the schema in the CacheDataReader specifies the data in the list above^, and DataTable doesn't seem to allow the MaxLength property, even though the type is VARCHAR/String.
SELECT TOP 10 * FROM table
WHERE IsNumeric(ColumName) = 0
This will return only data where the Primary Key is of type Int

Cannot fill temporary table from gridview

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.

C# Compare 2 DataTables fill changes into third DataTable

Well, lets say I've got 2 Datatables from the beginning.
The first one (source) contains the data from da database.
The second one also contains data from a database, nut these values have to be updated into the first database.
Unfortunately they don't have the same structure.
The sourcedatatable has some additional columns which the second has not.
For example:
First DT: ID | Name | Company | Age
Second DT: Name | Company | Age
I want the FIRST DataTable to be updated with the values from the second DataTable IF THERE ARE SOME DIFFERENCES (and only the differences).
Any ideas on how to work that out? Any suggestions about performance, even if using very big databases?
If you are working with a big amount of data, I would suggest doing things as close to the DB as possible(if possible within a stored procedure).
If sticking to .NET is mandatory, these are the options I would consider given the description of your scenario you provided.
First I would choose how to load the data (the order in which I would consider them):
Generate Entities (LINQ to SQL).
Use F# Type providers
Use ADO directly
After this, I would either:
use .Select and .Except on the IQueryable sources, or
do something similar to http://canlu.blogspot.ro/2009/05/how-to-compare-two-datatables-in-adonet.html, if by some chance I was using ADO.NET
It is rather hard to give a specific and exact answer if you do not provide more information on the type of data, amount, hardware, database type.
Note: whichever solution you choose, you should keep in mind that it is hard to compare things of different structure, so an extra step to add empty columns to the one that is missing columns is required.
This code is just for reference, I did not have time to test it. It might reuire a bit of tweaking. Try something like:
var a = new DataTable();
a.Columns.Add("ID");
a.Columns.Add("Name");
a.Columns.Add("Company");
a.Columns.Add("Age");
var b = new DataTable();
b.Columns.Add("Name");
b.Columns.Add("Company");
b.Columns.Add("Age");
var destination = a.AsEnumerable();
var localValues = b.AsEnumerable();
var diff = destination.Join(localValues, dstRow => dstRow["Name"], srcRow => srcRow["Name"],
(dstRow, srcRow) =>
new {Destination = dstRow, Source = srcRow})
.Where(combinedView =>
combinedView.Destination["Age"] != combinedView.Source["Age"] ||
combinedView.Destination["Company"] != combinedView.Source["Company"]);
Also, I would really move to a proper DB, and maybe improve the data model.

How to create LookUp fields in 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.

Categories