Datable.Rows.Contains(value) causes Missing Primary Key Exception - c#

I'm trying to see if a row of a DataTable contains a value, but it says "System.Data.MissingPrimaryKeyException: 'Table doesn't have a primary key.'" I'm not sure why a key would be necessary to find a value in a row and need some guidance on how to correctly loo through the rows to find a value.
I labeled the if statement that throws the exception with comments.
DataTable OutputDV = new DataTable();
DataView ScrumTeams = ISBL.GetScrumTeams();
//this dataview returns this: [CountDate, ScrumTeam, DefectCount]
DataView allDefects = ISBL.GetDefects(dtstartdate, dtenddate);
//Adding the columns for the datview
DataColumn ScrumTeamName = new DataColumn("ScrumTeam", typeof(string));
ScrumTeamName.ReadOnly = true;
OutputDV.Columns.Add(ScrumTeamName);
//first create columns of all dates
foreach (DataRow x in allDefects.ToTable().Rows)
{
//if there isnt a column by name of x then add one
if (!OutputDV.Columns.Contains(x["CountDate"].ToString()))
{
OutputDV.Columns.Add(x["CountDate"].ToString());
}
//CODE BELOW CAUSES EXCEPTION:
//System.Data.MissingPrimaryKeyException: 'Table doesn't have a primary key.'
if (!OutputDV.Rows.Contains(x["ScrumTeam"].ToString()) || OutputDV.Rows.Count == 0)
{
OutputDV.Rows.Add(x["ScrumTeam"].ToString());
}
}

Related

Why is DataRow not found in DataTable

New to data manipulation using DataTable, DataColumn, DataRow. Why is employee not found in the DataTable? I was expected indexFound to have the value "2", and when I debug the values all look correct.
// Manually create a new DataTable
DataTable dtEmployees = new DataTable("Employee");
// Add columns
dtEmployees.Columns.Add("ID", typeof(int));
dtEmployees.Columns.Add("Name", typeof(string));
dtEmployees.Columns.Add("Salary", typeof(int));
// Add rows to the table
dtEmployees.Rows.Add(52, "Sally", 29000);
dtEmployees.Rows.Add(63, "Harry", 22000);
dtEmployees.Rows.Add(72, "Alain", 23000);
dtEmployees.Rows.Add(110, "Pete", 24500);
// Look for an employee...
int index = 2;
Console.WriteLine("(index of DataRow({0}) should return {0})", index);
DataRow employee = dtEmployees.NewRow();
employee["ID"] = dtEmployees.Rows[index]["ID"];
employee["Name"] = dtEmployees.Rows[index]["Name"];
employee["Salary"] = dtEmployees.Rows[index]["Salary"];
int indexFound = dtEmployees.Rows.IndexOf(employee);
if (indexFound != -1)
Console.Write(" Employee has index {0}", indexFound); // Was expecting this...
else
Console.Write(" Employee not found in table..."); // ...but actually get this. Why??
The Microsoft documentation didn't provide any meaningful help.
A DataRow needs to be added to the Rows collection of the Datatable if you want to find it in the collection with IndexOf, so you just need to add this line before searching the row
dtEmployees.Rows.Add(employee);
Of course this method (IndexOf) is of some value only if you have a row to search for. Usually if you want to search a Datatable the preferred way is through the Select method that could return a set of one or more rows using a search criteria that resembles a WHERE sql statement.
dtEmployees.Rows.Add(employee);
DataRow[] rows = dtEmployees.Select("ID = 72");
if(rows != null && rows.Length > 0)
{
Console.WriteLine("rows found:" + rows.Length);
foreach(DataRow r in rows)
Console.WriteLine(r.Field<string>("name"));
}

How to fill DataTable rows only if primary key is bigger than last primary key?

The problem: inserting rows only if primary key is bigger than existing one when merging source DataTable to actual DataTable (ActualDT.Merge(SourceDT)).
Details of my problem below:
I fill an Actual DataTable with an Int64 primary key by the API from external server after deserializing JSON to Source DataTable. Then I write rows from DataTable to my database and cleanup all rows in DataTable except the biggest primary key. Later I request new data from the API and often the response contains the same rows I already wrote to database and cleanup from my DataTable.
If I won't cleanup the DataTable rows, performance decrease and it's memory pig. So, I leave one row with the biggest primary key after cleaning.
I don't want to compare every PrimaryKey from Source DataTable before merge, comparing can take a lot of time.
What should I do to prevent merging rows that I already wrote to database and removed from Actual DataTable? Maybe I can exclude them even at deserialisation process (I use NewtonSoft JSON.net)? Or any zippy way to prevent merging rows if they primary key < primary key in Actual DataTable?
Thanks for your answers!
UPDATE: merging code
public class MyData
{
DataTable BlackPairs = new DataTable();
DataTable WhiteTable = new DataTable();
public string _Json {
set
{
DataSet TempDS = JsonConvert.DeserializeObject<DataSet>(value);
try
{
foreach (DataTable table in TempDS.Tables)
{
BlackPairs = table.Copy();
WhiteTable.Merge(BlackPairs);
}
}catch{}
}
}
public MyData()
{ //columns initialization
WhiteTable.Columns.AddRange(new DataColumn[]{columns);
WhiteTable.PrimaryKey = new DataColumn[]{tid};
}
I have created custom Merge function based on what we have talked through comments. This function is only if primary column is typeof(int) but it can be easily improved to get all types or just change it to what type you need (string, int, bool...)
public Test()
{
InitializeComponent();
DataTable smallerDatatable = new DataTable();
smallerDatatable.Columns.Add("Col1", typeof(int));
smallerDatatable.Columns.Add("Col2", typeof(string));
DataTable biggerDatatable = new DataTable();
biggerDatatable.Columns.Add("Col1", typeof(int));
biggerDatatable.Columns.Add("Col2", typeof(string));
smallerDatatable.Rows.Add(1, "Row1");
smallerDatatable.Rows.Add(2, "Row2");
smallerDatatable.Rows.Add(3, "Row3");
biggerDatatable.Rows.Add(1, "Row1");
biggerDatatable.Rows.Add(2, "Row2");
biggerDatatable.Rows.Add(3, "Row3");
biggerDatatable.Rows.Add(4, "Row4");
biggerDatatable.Rows.Add(5, "Row5");
DataTable mergedTable = MergeOnUniqueColumn(smallerDatatable, biggerDatatable, "Col1");
dataGridView1.DataSource = mergedTable;
}
private DataTable MergeOnUniqueColumn(DataTable smallTable, DataTable bigTable, string uniqueColumn)
{
DataTable m = smallTable;
for(int i = 0; i < bigTable.Rows.Count; i++)
{
if(!(smallTable.AsEnumerable().Any(row => bigTable.Rows[i][uniqueColumn].Equals(row.Field<object>(uniqueColumn)))))
{
smallTable.Rows.Add(bigTable.Rows[i].ItemArray);
}
}
return m;
}
Function above will fill every missing unique value inside smallTable from bigTable.
If you want to fill smallTable with values from bigTable only after last smallTable row then use this function.
private DataTable MergeOnUniqueColumnAfterLastID(DataTable smallTable, DataTable bigTable, string uniqueColumn)
{
DataTable m = smallTable;
int maxUnique = Convert.ToInt32(m.Compute("max([" + uniqueColumn + "])", string.Empty));
for (int i = 0; i < bigTable.Rows.Count; i++)
{
if (!(smallTable.AsEnumerable().Any(row => (int)bigTable.Rows[i][uniqueColumn] <= maxUnique)))
{
smallTable.Rows.Add(bigTable.Rows[i].ItemArray);
}
}
return m;
}

Adding DataColumn to DataTable

I want to move the data from a dataColumn to a specific column in my dataTable. I am not sure how to specify what column within my Datatable I want to add the datacolumn.
foreach (DataColumn col in dt.Columns)
{
dt1.Columns.Add(col);
}
I receive an exception Column 'X' already belongs to another DataTable.
You need to copy the properties like ColumnName and create new DataColumns:
foreach (DataColumn col in dt.Columns)
{
dt1.Columns.Add(col.ColumnName, col.DataType);
}
There's a reason for the ArgumentException when you add a DataColumn which already belongs to another DataTable. It would be very dangerous to allow that since a DataTable holds a reference to their columns and every column holds a reference to it's DataTable. If you would add a column to another table your code would blow sooner or later.
If you also want to copy the DataRows into the new table:
foreach (DataRow row in t1.Rows)
{
var r = t2.Rows.Add();
foreach (DataColumn col in t2.Columns)
{
r[col.ColumnName] = row[col.ColumnName];
}
}
You cannot add a DataColumn from another table, because it already has an association with it's original table and a DataColumn is passed by reference to the Add method because it's an Object. You'll have to copy it. Here's one way you can do that:
public static class DataColumnExtensions
{
public static DataColumn CopyTo(this DataColumn column, DataTable table)
{
DataColumn newColumn = new DataColumn(column.ColumnName, column.DataType, column.Expression, column.ColumnMapping);
newColumn.AllowDBNull = column.AllowDBNull;
newColumn.AutoIncrement = column.AutoIncrement;
newColumn.AutoIncrementSeed = column.AutoIncrementSeed;
newColumn.AutoIncrementStep = column.AutoIncrementStep;
newColumn.Caption = column.Caption;
newColumn.DateTimeMode = column.DateTimeMode;
newColumn.DefaultValue = column.DefaultValue;
newColumn.MaxLength = column.MaxLength;
newColumn.ReadOnly = column.ReadOnly;
newColumn.Unique = column.Unique;
table.Columns.Add(newColumn);
return newColumn;
}
public static DataColumn CopyColumnTo(this DataTable sourceTable, string columnName, DataTable destinationTable)
{
if (sourceTable.Columns.Contains(columnName))
{
return sourceTable.Columns[columnName].CopyTo(destinationTable);
}
else
{
throw new ArgumentException("The specified column does not exist", "columnName");
}
}
}
public class MyClass
{
public static void Main()
{
DataTable tableA = new DataTable("TableA");
tableA.Columns.Add("Column1", typeof(int));
tableA.Columns.Add("Column2", typeof(string));
DataTable tableB = new DataTable("TableB");
foreach (DataColumn column in tableA.Columns)
{
column.CopyTo(tableB);
}
}
}
Note that there is also an extension method that can be used to Copy individual columns by name, i.e. tableA.CopyColumnTo("Column1", tableB);.
Then you can copy the data like this if the new table is an exact copy of the original:
foreach (DataRow row in tableA.Rows)
{
tableB.Rows.Add(row.ItemArray);
}
Or in a way similar to the second piece of code in Tim Schmelter's answer if it is not an exact copy. I would, however, recommend some error checking if you aren't copying all the columns into the new table:
foreach (DataRow souceRow in sourceTable.Rows)
{
DataRow destinationRow = destinationTable.Rows.Add();
foreach (DataColumn destinationColumn in destinationTable.Columns)
{
string columnName = destinationColumn.ColumnName;
if (sourceTable.Columns.Contains(columnName))
{
destinationRow[columnName] = sourceRow[columnName];
}
}
}
this is a normal behavior, if you're adding the same column to more than one DataTable ArgumentException will be thrown.
see documentation here:
http://msdn.microsoft.com/en-us/library/55b10992.aspx
you can create a new column same as the one you already added to the original table and add it to the new table.
Table data is organized by row, then by column. You can't (that I know of) add a column of data in one shot. You will need to add the column definition to the second table and add the data to it separately.
Since your original code looped through all columns, you may be better off copying the original datatable using DataTable.Copy() and deleting what you don't want.

C#: Retrieving value from DataTable using PrimaryKey

I have a issue with my code in C#. I have set up a couple of DataTables with a primary key assigned to each of them. what I want to do is to retrieve a single row from a single column.
Lets say I have this code:
DataColumn Pcolumn = new DataColumn();
DataColumn[] key = new DataColumn[1];
Pcolumn.DataType = System.Type.GetType("System.Double");
Pcolumn.ColumnName = "length";
key[0] = Pcolumn;
table6F.Columns.Add(Pcolumn);
table6F.Columns.Add("Area", typeof(double));
table6F.Columns.Add("load", typeof(double));
table6F.Columns.Add("weigth", typeof(double));
table6F.PrimaryKey = key;
table.Rows.Add(6.0, 14.0, 17.8 , 11.0 );
table.Rows.Add(7.0, 16.2 , 20.7 , 16.0 );
And I want to retrieve the the "load" for the second row (20.7), I would like to search for 7.0, primary key column, in the Table. I dummy tested to do like this, just to test:
Object oV;
double load;
//Get an Table object given the specific row number, this dummy i always set to 0.
// Given Column
oV = table.Rows[0]["load"];
load = Convert.ToDouble(oV.ToString());
Is there a similar way to extract using the Primary key?
You can retrieve a row from a DataTable based on its primary key using the DataRowCollection.Find method. In your case it would be:
DataRow matchingRow = table.Rows.Find(7.0D);
double value = (double)matchingRow["load"];
You can use Find method to find a row using primary Key
DataRow row = dataTable.Rows.Find("your key");
if(null != row)
{
string value = row["ColumnName"];
}

DataSet Merge and AcceptChanges to get DiffGram

I have two XML data sets, ds1 and ds2. I read these data sets with .ReadXML(name, XmlReadMode.ReadSchema). I am trying to get a DiffGram with the differences between the two by using a merged data set as shown below.
DataSet ds3 = new DataSet();
ds3.Merge(ds1);
ds3.AcceptChanges();
ds3.Merge(ds2);
DataSet ds4 = ds3.GetChanges();
ds4.WriteXml("ds4.xml", XmlWriteMode.DiffGram);
ds1 and ds2 each contains multiple elements. I created ds2 by copying the ds1 file and modifying one of the records.
However, when I look at ds4.xml after execution, it shows all of the record sets in ds1 and all of the records in ds2 (so it shows duplicate entries), and the ds2 updates are listed as ...diffgr:hasChanges="inserted">. It seems that this is only inserting, not updating existing records.
How can I get ds4 to only show the change that was made in ds2?
This behavior of inserting versus updating typically occurs due to the lack of defined primary keys. Have you set primary keys on the tables? That's how the columns are matched up during a merge. Per MSDN (emphasis mine):
When merging a new source DataSet into
the target, any source rows with a
DataRowState value of Unchanged,
Modified, or Deleted are matched to
target rows with the same primary key
values. Source rows with a
DataRowState value of Added are
matched to new target rows with the
same primary key values as the new
source rows.
Therefore, for each DataTable you should set the PrimaryKey property. I wrote a detailed example of this on the MSDN DataTable.Merge page a few years ago. You can take a look at that write up here: Merge using Primary Keys for Expected Results.
A brief example of this approach:
DataTable dt1 = new DataTable();
dt1.Columns.Add("ID", typeof(int));
dt1.Columns.Add("Customer", typeof(string));
dt1.PrimaryKey = new[] { dt1.Columns["ID"] };
dt1.Rows.Add(new object[] { 1, "Ahmad" });
DataTable dt2 = new DataTable();
dt2.Columns.Add("ID", typeof(int));
dt2.Columns.Add("Customer", typeof(string));
dt2.PrimaryKey = new[] { dt2.Columns["ID"] };
dt2.Rows.Add(new object[] { 1, "Mageed" });
// try without primary keys and it'll add a new record
dt1.Merge(dt2);
EDIT: regarding your comment, you could reject the changes on merged rows that were not really changed by passing the table through the code below. A method that accepts a DataTable would be neater. It's important that this code is used prior to calling the DataTable.AcceptChanges() method, otherwise the row states will be discarded.
With LINQ:
foreach (DataRow row in dt1.Rows)
{
if (row.RowState == DataRowState.Modified)
{
var original = dt1.Columns.Cast<DataColumn>()
.Select(c => row[c, DataRowVersion.Original]);
bool isUnchanged = row.ItemArray.SequenceEqual(original);
if (isUnchanged)
{
row.RejectChanges();
}
}
}
If LINQ isn't an option:
foreach (DataRow row in dt1.Rows)
{
if (row.RowState == DataRowState.Modified)
{
bool isUnchanged = true;
foreach (DataColumn col in dt1.Columns)
{
if (!row[col.ColumnName].Equals(row[col.ColumnName, DataRowVersion.Original]))
{
isUnchanged = false;
break;
}
}
if (isUnchanged)
{
row.RejectChanges();
}
}
}
You can call dt1.AcceptChanges() after this is done.

Categories