Ordering a ConcurrentDictionary. Why is this not working? - c#

We have a C# app that populates tables on worksheets within an Excel document.
The tables must be populated in the order the rows are returned from the database.
The object DataFileColData is defined as a List and contains the result set rows. For testing purposes, I'm only using [0] of the List.
Code segment #1 below doesn't work. Row order is not preserved in that the end result has the data displayed out of order although the numbers themselves are listed in order:
if (DataFileColData[0].Count() > 0)
{
ConcurrentDictionary<int, DataRow> theRows = new ConcurrentDictionary<int, DataRow>(9, DataFileColData[0].Count());
Parallel.For(0, DataFileColData[0].Count(), i =>
{
// go through each column
int c = 0;
try
{
foreach (var Col in DataFileColData)
{
var cell = Col[i];
if (cell != null)
{
if (cell.GetType().Name == "JArray") //If Jarray then table compression was used not column compression
{
if (theRows.TryAdd(i, Dt.NewRow()))
theRows[i].ItemArray = JsonConvert.DeserializeObject<object[]>(Col[i].ToString());
}
else
{
if (theRows.TryAdd(i, Dt.NewRow()))
theRows[i][c] = cell;
}
}
c++;
}
} //try
catch (Exception e)
{
throw new Exception("Exception thrown in \"PublicMethods.cs | RenderExcelFile\" while in foreach loop over DataFileColData: " + e.ToString());
}
} //for
); //parallel
//Add the rows to the datatable in their original order
//(might have gotten skewed from the parallel.for loop)
for (int x = 0; x < theRows.Count; x++)
Dt.Rows.Add(theRows[x]);
//Set the name so it appears nicely in the Excel Name Box dropdown instead of "table1", "table2", etc etc.
Dt.TableName = ExcelTableSpec.TableTitle + " " + r.TableID;
}
code segment #2 below does work with the row order and data associated with each row preserved :
if (DataFileColData[0].Count() > 0)
{
DataRow[] theRows = new DataRow[DataFileColData[0].Count()];
Parallel.For(0, DataFileColData[0].Count(), i =>
{
DataRow Rw = Dt.NewRow();
// go through each column
int c = 0;
try
{
foreach (var Col in DataFileColData)
{
var cell = Col[i];
if (cell != null)
{
if (cell.GetType().Name == "JArray") //If Jarray then table compression was used not column compression
{
lock (theRows)
{
theRows[i] = Dt.NewRow();
theRows[i].ItemArray = JsonConvert.DeserializeObject<object[]>(Col[i].ToString());
}
}
else
{
lock (theRows)
{
theRows[i] = Dt.NewRow();
theRows[i][c] = cell;
}
}
}
c++;
}
} //try
catch (Exception e)
{
throw new Exception("Exception thrown in \"PublicMethods.cs | RenderExcelFile\" while in foreach loop over DataFileColData: " + e.ToString());
}
} //for
); //parallel
//Add the rows to the datatable in their original order
//(might have gotten skewed from the parallel.for loop)
Dt = theRows.CopyToDataTable();
//Set the name so it appears nicely in the Excel Name Box dropdown instead of "table1", "table2", etc etc.
Dt.TableName = ExcelTableSpec.TableTitle + " " + r.TableID;
}
I don't understand why. I didn't think the locking mechanism would be needed because each thread gets its own instance of "i" and a ConcurrentDictionary is supposed to be thread safe.
Would someone be able to explain to me please why the code isn't working the way I think it should?
Thank you!
UPDATED CODE as per #Enigmativity's comments below.
The MSDN documentation isn't quite clear (to me anyway), but does appear to update the DataTable even though the MSDN documentation doesn't indicate it does when executing the NewRow() method.
New working code below:
if (DataFileColData[0].Count() > 0)
{
DataRow[] theRows = new DataRow[DataFileColData[0].Count()];
Parallel.For(0, DataFileColData[0].Count(), i =>
//for (int i = 0; i < DataFileColData[0].Count(); i++)
{
lock (Dt)
{
theRows[i] = Dt.NewRow();
}
// go through each column
int c = 0;
try
{
foreach (var Col in DataFileColData)
{
var cell = Col[i];
if (cell != null)
{
if (cell.GetType().Name == "JArray") //If Jarray then table compression was used not column compression
{
theRows[i].ItemArray = JsonConvert.DeserializeObject<object[]>(Col[i].ToString());
}
else
{
theRows[i][c] = cell;
}
}
c += 1;
} //foreach
} //try
catch (Exception e)
{
throw new Exception("Exception thrown in \"PublicMethods.cs | RenderExcelFile\" while in foreach loop over DataFileColData: " + e.ToString());
}
} //for
); //parallel
//Add the rows to the datatable in their original order
//(might have gotten skewed from the parallel.for loop)
Dt = theRows.CopyToDataTable();
//Set the name so it appears nicely in the Excel Name Box dropdown instead of "table1", "table2", etc etc.
Dt.TableName = ExcelTableSpec.TableTitle + " " + r.TableID;
//cleanup
if (theRows != null)
Array.Clear(theRows, 0, theRows.Length);
theRows = null;
} //if (DataFileColData[0].Count() > 0)

Please see the documentation for (MSDN Data Tables).
The key point is:
Thread Safety
This type is safe for multithreaded read operations. You must
synchronize any write operations.
So it's not i the the ConcurrentDictionary causing your issues.
I've decompiled the NewRow method and there is a call to NewRow(int record). This code clearly shows write operations.
internal DataRow NewRow(int record)
{
if (-1 == record)
record = this.NewRecord(-1);
this.rowBuilder._record = record;
DataRow row = this.NewRowFromBuilder(this.rowBuilder);
this.recordManager[record] = row;
if (this.dataSet != null)
this.DataSet.OnDataRowCreated(row);
return row;
}

Related

C# Find empty cells and write them inside with ClosedXml

I have this problem, I have installed ClosedXml:
I have an Excel file already created and populated, now I should find the blank line below the already populated one and write some data
Example:
[A, 1] = name;
[B, 1] = surname;
the next line will be empty and I will pass some variables to populate the cells going to the right.
OpenFileDialog FileExcel = new OpenFileDialog();
if (FileExcel.ShowDialog() == DialogResult.OK)
{
try
{
var sr = new StreamReader(FileExcel.FileName);
}
catch (SecurityException ex)
{
MessageBox.Show($"Security error.\n\nError message: {ex.Message}\n\n" +
$"Details:\n\n{ex.StackTrace}");
}
}
using (var excelWorkbook = new XLWorkbook(FileExcel.FileName))
{
var nonEmptyDataRows = excelWorkbook.Worksheet(Convert.ToInt32(comboBox1.SelectedItem)).RowsUsed();
foreach (var dataRow in nonEmptyDataRows)
{
//for row number check
if (dataRow.RowNumber() >= 1 && dataRow.RowNumber() <= 100)
{
}
}
}
Use row.Cells(false) instead of row.Cells(). It does not skip over unused cells. Then you can simply check of cell.Value() is empty
you can do something like that
int lastrow = worksheet.LastRowUsed().RowNumber();
var rows = worksheet.Rows(1, lastrow);
foreach (IXLRow row in rows)
{
foreach (IXLCell cell in row.Cells())
{
if (cell.IsEmpty())
{
//do something
}
}
}

Sum data for spesific row with filter from other row

I have grid with data being input.
Item Price Type
A 1000 1
B 1000 2
C 2000 2
D 3000 3
I want sum(price) with type that has value "2".
try
{
foreach (int i in gridView1.GetSelectedRows())
{
DataRow newRow = gridView1.GetDataRow(i);
if (newRow["NOMINAL"] is DBNull) { newRow["NOMINAL"] = 0; }
if (e.Column.FieldName == "IDISJ")
{
if (verifikasiNamaISJ(IDisj, e.RowHandle) == true)
{
TampilkanPesan.Error("Nama Item Sudah Ada!");
newRow["IDISJ"] = 0;
return;
}
newRow["IDISJ"] = IDisj;
gridView1.FocusedColumn = colNominal;
gridView1.FocusedRowHandle = e.RowHandle;
this.BeginInvoke((MethodInvoker)delegate
{
gridView1.ShowEditor();
});
cariDataItem(Convert.ToInt64(Global.PeriksaDBNullAngka(newRow["IDISJ"])));
newRow["NAMAISJ"] = NamaISJ;
newRow["NAMAJENISISJ"] = Jenis;
newRow["NOMINAL"] = 0;
}
DataTable dt = new DataTable()
>>>> txtTotalPotongan.EditValue = Convert.ToString(dt.Compute("SUM(NOMINAL)", "NAMAJENISISJ = 'Pemotongan'"));
>>>> txtTotalDiterima.EditValue = Convert.ToString(dt.Compute("SUM(NOMINAL)", "NAMAJENISISJ = 'Pendapatan'"));
}
}
catch (FbException ex)
{
TampilkanPesan.Error(ex.Message.ToString());
}
the line with >>>> was my work, but I have no idea how to do it. I guess I'm doing it wrong, since I put Datatable there. It's not data yet, still value of row.
But I don't know how to sum with filter, if not using filter I can do it. Please help me the right line of code for it.
I put this on gridview1_cellvaluechanged for every time something changes, it will update the value on textbox as well.
You could convert your datable to a list using System.Data.DataSetExtensions; and use Linq to filter and find the sum you are looking for.
var sum = dt.AsEnumerable().Where(x=> (int)x["Type"] == 2).Sum(x => (int)x["Price"]);
Here you can find a working example using your Items table.

Removal of Duplicate Rows from Data table Based on Multiple columns

I have data table which contains many duplicate rows i need to filter those rows from data table based upon multiple columns to get distinct rows in resultant data table....
Barcode Itemid PacktypeId
1 100 1
1 100 2
1 100 3
1 100 1
1 100 3
need only rows which contains packtypeid 1,2,3 remaining 4th and 5th row should be removed
I have tried using two methods but none didn't turns for better result
Data table contains more than 10 columns but unique column's is "Barcode", "ItemID", "PackTypeID"
Method-1:
dt_Barcode = dt_Barcode.DefaultView.ToTable(true, "Barcode", "ItemID", "PackTypeID");
The above method filter's the rows but it returns columns only 3 column values i need entire 10 column values.
Method-2:
List<string> keyColumns = new List<string>();
keyColumns.Add("Barcode");
keyColumns.Add("ItemID");
keyColumns.Add("PackTypeID");
RemoveDuplicates(DataTable table, List<string> keyColumns)
{
var uniqueness = new HashSet<string>();
StringBuilder sb = new StringBuilder();
int rowIndex = 0;
DataRow row;
DataRowCollection rows = table.Rows;
int i = rows.Count;
while (rowIndex < i)
{
row = rows[rowIndex];
sb.Length = 0;
foreach (string colname in keyColumns)
{
sb.Append(row[colname]);
sb.Append("|");
}
if (uniqueness.Contains(sb.ToString()))
{
rows.Remove(row);
}
else
{
uniqueness.Add(sb.ToString());
rowIndex++;
}
}
The Above Method returns exception like there is no rows at position 5
Method 3:
Instead of Trying above 2 methods i found this Linq Method something very useful
dt_Barcode = dt_Barcode.AsEnumerable().GroupBy(r => new { ItemID = r.Field<Int64>("ItemID"), PacktypeId = r.Field<Int32>("PackTypeID") }).Select(g => g.First()).CopyToDataTable();
It happens because you remove rows.
If you want to preserve the same algorithm, instead of using while (rowIndex < i) use this form of loop:
for (var rowIndex = rows.Count - 1; rowIndex >= 0; rowIndex--)
{
...
if (uniqueness.Contains(sb.ToString()))
{
rows.Remove(row);
rowIndex--;
}
...
}
public void RemoveDuplicatesFromDataTable(ref DataTable table, List<string> keyColumns)
{
Dictionary<string, string> uniquenessDict = new Dictionary<string, string>(table.Rows.Count);
StringBuilder stringBuilder = null;
int rowIndex = 0;
DataRow row;
DataRowCollection rows = table.Rows;
string error = string.Empty;
try
{
while (rowIndex < rows.Count)
{
row = rows[rowIndex];
stringBuilder = new StringBuilder();
foreach (string colname in keyColumns)
{
try
{
if (row[colname].ToString() != string.Empty)
{
stringBuilder.Append(((string)row[colname]));
}
else
{
//If it comes here, means one of the keys are blank
error += "One of the key values is blank.";
}
}
catch (Exception ss)
{
error += "Error " + ss.Message + ".";
}
}
if (uniquenessDict.ContainsKey(stringBuilder.ToString()))
{
rows.Remove(row);
}
else
{
uniquenessDict.Add(stringBuilder.ToString().Replace(",", ""), string.Empty);
rowIndex++;
}
}
}
catch (Exception ex)
{
error = "Failed - " + ex.Message;
}
if(error != string.Empty)
Show`enter code here`(error);
}

How to check duplicate rows in dataset?

I have a dataset which has duplicate rows i want my error message to execute when duplicate rows are present.
Below is my code please help
DataSet dsXml = new DataSet();
dsXml.ReadXml(new XmlTextReader(new StringReader(xml)));
Hashtable hTable = new Hashtable();
ArrayList duplicateList = new ArrayList();
foreach (DataRow drow in dsXml.Tables[0].Rows)
{
if (hTable.Contains(drow))
{
duplicateList.Add(drow);
}
else
{
script.Append("alert('Error - There are some Duplicate entries.'); ");
ErrorOcc = true;
if (ErrorOcc)
{
this.ScriptOutput = script + " ValidateBeforeSaving = false;";
this.StayContent = "yes";
return;
}
}
}
Your code is not working, because DataRow instances will be compared by references instead of comparing their fields. You can use custom comparer:
public class CustomDataRowComparer : IEqualityComparer<DataRow>
{
public bool Equals(DataRow x, DataRow y)
{
if (x.ItemArray.Length != y.ItemArray.Length)
return false;
for (int i = 0; i < x.ItemArray.Length; i++)
if (!x[i].Equals(y[i]))
return false;
return true;
}
public int GetHashCode(DataRow obj)
{
int hash = 17;
foreach (object field in obj.ItemArray)
hash = hash * 19 + field.GetHashCode();
return hash;
}
}
or use existing DataRowComparer which compares DataRow objects for equivalence by using value-based comparison:
HashSet<DataRow> set = new HashSet<DataRow>(DataRowComparer.Default);
// or: new HashSet<DataRow>(new CustomDataRowComparer());
foreach (DataRow row in dsXml.Tables[0].Rows)
{
if (!set.Add(row))
// duplicate row
}
You can also check if duplicated rows exist with Linq to DataSet query:
var duplicatedRowsExist = dsXml.Tables[0].AsEnumerable()
.GroupBy(r => r, DataRowComparer.Default)
.Any(g => g.Count() > 1);
You have to compare the content of the rows, not the rows themselves. Something like this should do it:
var hasDupes = dsXml.Tables[0].Rows
.AsEnumerable()
.GroupBy(row => new
{
row.Field<string>("Title"),
row.Field<string>("Address"),
row.Field<string>("State"),
row.Field<string>("City"),
row.Field<int>("Status"),
row.Field<int>("CreatedBy"),
row.Field<int>("UpdatedBy")
})
.Where(g => g.Count() > 1)
.Any();
if(hasDupes)
//Show error message
I think you have to alter you logic a little. You don't add the row to the hTable, so there are never duplicates. And I guess you have to show the message in the end, else the list will not be complete yet.
As stated by others, you do need Sergeys answer to get the comparison to work. If you have that covered, this code will solve the other logic problems.
foreach (DataRow drow in dsXml.Tables[0].Rows)
{
if (!hTable.Contains(drow))
{
hTable.Contains(drow);
hTable.Add(drow);
}
else
{
duplicateList.Add(drow);
}
}
script.Append("alert('Error - There are some Duplicate entries.'); ");
ErrorOcc = true;
if (ErrorOcc)
{
this.ScriptOutput = script + " ValidateBeforeSaving = false;";
this.StayContent = "yes";
return;
}
First, you need to define your comparison between rows. It appears when you create your hTable there is nothing in it, so the hTable.Contains call is always going to return false.
As a side note, you can't just compare a DataRow with another DataRow, it will use the default equality comparison (implemented using IEqualityComparer) and effectively boils down to a reference equality check, which none of the rows will be equal to each other.
Somewhere, you can either implement your own IEqualityCompariosn, or simply write a custom method to check the values of each row.
Here is the answer of my above Question
You can Check duplicate rows in dataset.. it is working fine try it.
DataSet dsXml = new DataSet();
dsXml.ReadXml(new XmlTextReader(new StringReader(xml)));
List<string> duplicateList = new List<string>();
foreach (DataRow drow in dsXml.Tables[0].Rows)
{
string strr = "";
for (int j = 0; j < dsXml.Tables[0].Columns.Count; j++ )
{
strr += drow[j];
}
if (!duplicateList.Contains(strr))
{
duplicateList.Add(strr);
}
else
{
script.Append("alert('Error - There are some Duplicate entries.'); ");
ErrorOcc = true;
if (ErrorOcc)
{
this.ScriptOutput = script + " ValidateBeforeSaving = false;";
this.StayContent = "yes";
return;
}
}
}

How to find rowindex of a datatable into another datatable?

I am using C#. I have two data tables and i want to find the rows of first data table into second data table.
Example.
First data table's data:
1 inam
2 sohan
Second data tables's data:
3 ranjan
1 inam
2 sohan
Now i want to know the index of first two rows of first data table into second data table.
Please help guys.
Any answer or advice
Best Regards
You can use following extension method which returns the first index of a "sub-sequence":
// I've used String.Join to get something that is comparable easily
// from the ItemArray that is the object-array of all fields
IEnumerable<string> first = table1.AsEnumerable()
.Select(r => string.Join(",",r.ItemArray)); //
IEnumerable<string> second = table2.AsEnumerable()
.Select(r => string.Join(",", r.ItemArray));
int index = second.IndexOfSequence(first, null); // 1
Here the extension:
public static int IndexOfSequence<TSource>(this IEnumerable<TSource> input, IEnumerable<TSource> sequence, IEqualityComparer<TSource> comparer)
{
if (input == null) throw new ArgumentNullException("input");
if (sequence == null) throw new ArgumentNullException("sequence");
if (!sequence.Any()) throw new ArgumentException("Sequence must not be empty", "sequence");
if (comparer == null)
{
comparer = EqualityComparer<TSource>.Default;
}
int index = -1;
int firstIndex = -1;
bool found = false;
TSource first = sequence.First();
using (IEnumerator<TSource> enumerator = input.GetEnumerator())
{
using (IEnumerator<TSource> enumerator2 = sequence.GetEnumerator())
{
enumerator2.MoveNext();
while (enumerator.MoveNext())
{
index++;
found = comparer.Equals(enumerator.Current, enumerator2.Current);
if (found && firstIndex == -1) firstIndex = index;
if (found && !enumerator2.MoveNext())
return firstIndex;
}
}
}
return -1;
}
tested with this sample data:
var table1 = new DataTable();
table1.Columns.Add("ID", typeof(int));
table1.Columns.Add("Name");
var table2 = table1.Clone();
table1.Rows.Add(1, "inam");
table1.Rows.Add(2, "Sohan");
table2.Rows.Add(3, "ranjan");
table2.Rows.Add(1, "inam");
table2.Rows.Add(2, "Sohan");
If you don't have much volume this might work....
var tableOneIndex = -1;
var tableTwoIndex = -1;
foreach (var tableOneRow in tableOne.Rows)
{
tableOneIndex++;
foreach (var tableTwoRow in tableTwo.Rows)
{
tableTwoIndex++;
if (tableOneRow["name"].ToString() == tableTwoRow["name"].ToString())
{
// Do whatever you wanted to do with the index values
}
}
}
As a simple solution, this should suffice:
// Create and populate data tables
DataTable dataTable1 = new DataTable();
dataTable1.Columns.Add("Name", typeof(string));
DataRow row1 = dataTable1.NewRow();
row1["Name"] = "Inam";
DataRow row2 = dataTable1.NewRow();
row2["Name"] = "Sohan";
dataTable1.Rows.Add(row1);
dataTable1.Rows.Add(row2);
DataTable dataTable2 = new DataTable();
dataTable2.Columns.Add("Name", typeof(string));
DataRow row3 = dataTable2.NewRow();
row3["Name"] = "Ranjan";
DataRow row4 = dataTable2.NewRow();
row4["Name"] = "Inam";
DataRow row5 = dataTable2.NewRow();
row5["Name"] = "Sohan";
dataTable2.Rows.Add(row3);
dataTable2.Rows.Add(row4);
dataTable2.Rows.Add(row5);
// Loop through rows in first table
foreach (DataRow row in dataTable1.Rows)
{
int rowIndexInSecondTable = 0;
// Loop through rows in second table
for (int i = 0; i < dataTable2.Rows.Count; i++)
{
// Check if the column values are the same
if (row["Name"] == dataTable2.Rows[i]["Name"])
{
// Set the current index and break to stop further processing
rowIndexInSecondTable = i;
break;
}
}
// The index of the row in the second table is now stored in the rowIndexInSecondTable variable, use it as needed, for example, writing to the console
Console.WriteLine("Row with name '" + row["Name"] + "' found at index " + rowIndexInSecondTable.ToString());
}

Categories