How to add values in a validation list at once? - c#

I'm trying to add values to a IExcelDataValidationList but the way I'm doing it right now is not very efficient.
I need to create many rows in my worksheet and populate a list for a certain cell for each of these rows. When I have many values to add to the list, this is taking forever.
for (var x = headerRowIndex + 1; x < 1000; x++)
{
var address = ExcelCellBase.TranslateFromR1C1($"R{x}C{colIndex}", 0, 0);
IExcelDataValidationList list = mainWorksheet.DataValidations.AddListValidation(address);
foreach (var e in values)
{
list.Formula.Values.Add(e);
}
}
You see how this can take a long time if values contain a lot of options.
Here's what I tried:
List<string> validationValues = new List<string>();
validationValues = values.ToList();
for (var x = headerRowIndex + 1; x < 1000; x++)
{
var address = ExcelCellBase.TranslateFromR1C1($"R{x}C{colIndex}", 0, 0);
var list = mainWorksheet.DataValidations.AddListValidation(address);
((List<string>)list.Formula.Values).AddRange(validationValues);
}
So I'm trying to add all values to the list at once. This compiles fine, but I'm getting this exception:
System.InvalidCastException: 'Unable to cast object of type
'DataValidationList' to type
'System.Collections.Generic.List`1[System.String]'.'
I've tried casting directly to DataValidationList but it's defined at private and only accessible by EPPlus itself.
Any ideas ?

Instead of creating IExcelDataValidationList per cell, create one for the whole column (address can include range of cells):
var address = ExcelCellBase.GetAddress(headerRowIndex + 1, colIndex, headerRowIndex + 1000, colIndex, true); // whole column
IExcelDataValidationList list = mainWorksheet.DataValidations.AddListValidation(address);
foreach (var e in values)
{
list.Formula.Values.Add(e);
}

Related

Adding new objects to a dictionary while iterating through a loop?

I've been trying to add objects with specific properties to a Dictionary<int, DwgObject> dictionary in a while loop by reading through values in anexcel column.
It seems even though I'm declaring a new object in my loop, the properties of my respective objects end up being the same throughout the dictionary.
Here's the while loop I'm using:
Dictionary<int, DrawingObj> lst = new Dictionary<int, DrawingObj>();
int numRows = 0;
//Loop through Drawing Number column -> Add new obj to list with drawing number prop
//Iterate to next row down -> check for empty string to break loop
string current = wks.Cells[row, column]?.Value2?.ToString() ?? "";
while ((current != ""))
{
DrawingObj newDWG = new DrawingObj();
newDWG.dwgNumber = current;
lst.Add(numRows, newDWG);
numRows++;
current = wks.Cells[(row + numRows), column].Text;
}
The resulting dictionary gives me 13 entries of objects with the property dwgNumber equal to the string of my last excel value. How can I store the unique property values for key-value pair?
Update: I tried changing the loop to pull out the properties of each object as a single string and storing to an array, then later on, using a loop to create an object instance with all of the properties at once. But this results in the same error. See below:
//store string of all properties with delimiter '|'
while ((current != ""))
{
StringBuilder properties = new StringBuilder();
for(int i = 0; i < 7; i++)
{
properties.Append(wks.Cells[(row + numRows), propColIndex[i]].Text);
if(i != propColIndex.Length - 1) { properties.Append("|"); }
}
propStrings.Add(properties.ToString());
numRows++;
current = wks.Cells[(row + numRows), column].Text;
}
//for each property string create new object w/ props -> add object to dictionary foreach (var props in propStrings) // t i = 1; i <= numRows-1; i++)
{
percent = (j / numRows) * 60;
int k = 0;
string[] values = new string[8];
foreach (string prop in props.Split(delimiter))
{
values[k] = prop;
k++;
}
DrawingObj newDWG = new DrawingObj()
{
dwgNumber = values[0],
sheetNo = values[1],
revNo = values[2],
keyWord = values[3],
dwgType = values[4],
descSublocation = values[5],
revType = values[6],
fileName=values[7],
facilityLoc = constFacilFields[0],
facilityType = constFacilFields[1],
jobOrderNum = constFacilFields[2]
};
lst.Add(j-1, newDWG);
j++;

Inserting an item into an observable collection at index zero within a loop only works on the first iteration

Having filled an ObservableCollection with some data I would like to pad it out if the data set isn't large enough to fill my graph.
To do this I am inserting data at index zero within a loop so the padding is always at the beginning of the collection.
The first iteration works well but the next iteration fails with an
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
What am I missing?
private void PadDataSet<T>(ObservableCollection<T> dataSet, DateTime minDataTimePeriod, int minuteIntervals)
{
var paddedList = new List<DataItem>();
var neededIntervals = _timescaleHours * 60 / minuteIntervals + 1;
var actualIntervals = dataSet.Count();
var padCount = neededIntervals - actualIntervals;
for (var i = 0; i < padCount; i++)
{
var g = Activator.CreateInstance<T>();
var item = g as DataItem;
if (item != null)
{
item.TimePeriod = minDataTimePeriod.AddMinutes(-minuteIntervals * (i + 1));
dataSet.Insert(0, g);
}
}
}

C# - Specified cast is not valid using DataTable and Field<int>

I have a csv file with 8 columns, and I am trying to populate an object with 8 variables, each being a list to hold the columns in the csv file. Firstly, I am populating a DataTable with my csv data.
I am now trying to populate my object with the data from the DataTable
DataTable d = GetDataTableFromCSVFile(file);
CoolObject l = new CoolObject();
for (int i = 0; i < d.Rows.Count; i++)
{
l.column1[i] = d.Rows[i].Field<int>("column1"); <-- error here
}
And here is my CoolObject
public class CoolObject
{
public List<int> column1 { set; get; }
protected CoolObject()
{
column1 = new List<int>();
}
}
Unfortunately I am receiving an error on the highlighted line:
System.InvalidCastException: Specified cast is not valid
Why is this not allowed? How do I work around it?
Obviously you DataTable contains columns of type string, so do integer validation in GetDataTableFromCSVFile method, so consumers of this method don't need to worry about it.
Obviously you DataTable contains columns of type string, so do integer validation in GetDataTableFromCSVFile method, so consumers of this method don't need to worry about it.
private DataTable GetDataTableFromCSVFile()
{
var data = new DataTable();
data.Columns.Add("Column1", typeof(int));
// Read lines of file
// line is imaginery object which contains values of one row of csv data
foreach(var line in lines)
{
var row = data.NewRow();
int.TryParse(line.Column1Value, out int column1Value)
row.SetField("Column1", column1Value) // will set 0 if value is invalid
// other columns
}
return data;
}
Then another problem with your code, that you assugn new values to List<int> through index, where list is empty
l.column1[i] = d.Rows[i].Field<int>("column1");
Above line will throw exception because empty list doesn't have item on index i.
So you in the end your method will look
DataTable d = GetDataTableFromCSVFile(file);
CoolObject l = new CoolObject();
foreach (var row in d.Rows)
{
l.column1.Add(row.Field<int>("column1"));
}
In case you are using some third-party library for retrieving data from csv to DataTable - you can check if that library provide possibility to validate/convert string values to expected types in DataTable.
Sounds like someone didn't enter a number in one of the cells. You'll have to perform a validation check before reading the value.
for (int i = 0; i < d.Rows.Count; i++)
{
object o = d.rows[i]["column1"];
if (!o is int) continue;
l.column1[i] = (int)o;
}
Or perhaps it is a number but for some reason is coming through as a string. You could try it this way:
for (int i = 0; i < d.Rows.Count; i++)
{
int n;
bool ok = int.TryParse(d.rows[i]["column1"].ToString(), out n);
if (!ok) continue;
l.column1[i] = n;
}

How to Merge items within a List<> collection C#

I have a implememtation where i need to loop through a collection of documents and based on certain condition merge the documents .
The merge condition is very simple, if present document's doctype is same as later document's doctype, then copy all the pages from the later doctype and append it to the pages of present document's and remove the later document from the collection.
Note : Both response.documents and response.documents[].pages are List<> collections.
I was trying this but was getting following exception Once I remove the document.
collection was modified enumeration may not execute
Here is the code:
int docindex = 0;
foreach( var document in response.documents)
{
string presentDoctype = string.Empty;
string laterDoctype = string.Empty;
presentDoctype = response.documents[docindex].doctype;
laterDoctype = response.documents[docindex + 1].doctype;
if (laterDoctype == presentDoctype)
{
response.documents[docindex].pages.AddRange(response.documents[docindex + 1].pages);
response.documents.RemoveAt(docindex + 1);
}
docindex = docindex + 1;
}
Ex:
reponse.documents[0].doctype = "BankStatement" //page count = 1
reponse.documents[1].doctype = "BankStatement" //page count = 2
reponse.documents[2].doctype = "BankStatement" //page count = 2
reponse.documents[3].doctype = "BankStatement" //page count = 1
reponse.documents[4].doctype = "BankStatement" //page count = 4
Expected result:
response.documents[0].doctype = "BankStatement" //page count = 10
Please suggest.Appreciate your help.
I would recommend you to look at LINQ GroupBy and Distinct to process your response.documents
Example (as I cannot use your class, I give example using my own defined class):
Suppose you have DummyClass
public class DummyClass {
public int DummyInt;
public string DummyString;
public double DummyDouble;
public DummyClass() {
}
public DummyClass(int dummyInt, string dummyString, double dummyDouble) {
DummyInt = dummyInt;
DummyString = dummyString;
DummyDouble = dummyDouble;
}
}
Then doing GroupBy as shown,
DummyClass dc1 = new DummyClass(1, "This dummy", 2.0);
DummyClass dc2 = new DummyClass(2, "That dummy", 2.0);
DummyClass dc3 = new DummyClass(1, "These dummies", 2.0);
DummyClass dc4 = new DummyClass(2, "Those dummies", 2.0);
DummyClass dc5 = new DummyClass(3, "The dummies", 2.0);
List<DummyClass> dummyList = new List<DummyClass>() { dc1, dc2, dc3, dc4, dc5 };
var groupedDummy = dummyList.GroupBy(x => x.DummyInt).ToList();
Will create three groups, marked by DummyInt
Then to process the group you could do
for (int i = 0; i < groupedDummy.Count; ++i){
foreach (DummyClass dummy in groupedDummy[i]) { //this will process the (i-1)-th group
//do something on this group
//groupedDummy[0] will consists of "this" and "these", [1] "that" and "those", while [2] "the"
//Try it out!
}
}
In your case, you should create group based on doctype.
Once you create groups based on your doctype, everything else would be pretty "natural" for you to continue.
Another LINQ method which you might be interested in would be Distinct. But I think for this case, GroupBy would be the primary method you would like to use.
Use only "for loop" instead of "foreach".
foreach will hold the collection and cannot be modified while looping thru it.
Here is an example using groupBy, hope this help.
//mock a collection
ICollection<string> collection1 = new List<string>();
for (int i = 0; i < 10; i++)
{
collection1.Add("BankStatement");
}
for (int i = 0; i < 5; i++)
{
collection1.Add("BankStatement2");
}
for (int i = 0; i < 4; i++)
{
collection1.Add("BankStatement3");
}
//merge and get count
var result = collection1.GroupBy(c => c).Select(c => new { name = c.First(), count = c.Count().ToString() }).ToList();
foreach (var item in result)
{
Console.WriteLine(item.name + ": " + item.count);
}
Just use AddRange()
response.documents[0].pages.AddRange(response.documents[1].pages);
it will merge all pages of document[1] with the document[0] into document[0]

Get first value in CSV column without duplicates

I am getting a list of items from a csv file via a Web Api using this code:
private List<Item> items = new List<Item>();
public ItemRepository()
{
string filename = HttpRuntime.AppDomainAppPath + "App_Data\\items.csv";
var lines = File.ReadAllLines(filename).Skip(1).ToList();
for (int i = 0; i < lines.Count; i++)
{
var line = lines[i];
var columns = line.Split('$');
//get rid of newline characters in the middle of data lines
while (columns.Length < 9)
{
i += 1;
line = line.Replace("\n", " ") + lines[i];
columns = line.Split('$');
}
//Remove Starting and Trailing open quotes from fields
columns = columns.Select(c => { if (string.IsNullOrEmpty(c) == false) { return c.Substring(1, c.Length - 2); } return string.Empty; }).ToArray();
var temp = columns[5].Split('|', '>');
items.Add(new Item()
{
Id = int.Parse(columns[0]),
Name = temp[0],
Description = columns[2],
Photo = columns[7]
});
}
}
The Name attribute of the item list must come from column whose structure is as follows:
Groups>Subgroup>item
Therefore I use var temp = columns[5].Split('|', '>'); in my code to get the first element of the column before the ">", which in the above case is Groups. And this works fine.
However, I a getting many duplicates in the result. This is because other items in the column may be:
(These are some of the entries in my csv column 9)
Groups>Subgroup2>item2, Groups>Subgroup3>item4, Groups>Subgroup4>item9
All start with Groups, but I only want to get Groups once.
As it is I get a long list of Groups. How do I stop the duplicates?
I want that if an Item in the list is returned with the Name "Groups", that no other item with that name would be returned. How do I make this check and implement it?
If you are successfully getting the list of groups, take that list of groups and use LINQ:
var undupedList = dupedList
.Distinct();
Update: The reason distinct did not work is because your code is requesting not just Name, but also, Description, etc...If you only ask for Name, Distinct() will work.
Update 2: Try this:
//Check whether already exists
if((var match = items.Where(q=>q.Name == temp[0])).Count==0)
{
items.add(...);
}
How about using a List to store Item.Name?
Then check List.Contains() before calling items.Add()
Simple, only 3 lines of code, and it works.
IList<string> listNames = new List();
//
for (int i = 0; i < lines.Count; i++)
{
//
var temp = columns[5].Split('|', '>');
if (!listNames.Contains(temp[0]))
{
listNames.Add(temp[0]);
items.Add(new Item()
{
//
});
}
}

Categories

Resources