I am trying to populate a list of a custom class using the AddRange method.
Here is the custom class:
public class Hook
{
public Hook(System.Data.DataRow values)
{
this.text = values[0].ToString();
this.category = values[1].ToString();
this.weather = values[2].ToString();
this.timeofday = values[3].ToString();
}
public string text { get; set; }
public string category { get; set; }
public string[] associatedLandmarks { get; set; }
public string weather { get; set; }
public string timeofday { get; set; }
}
Here is my object array that I need to filter objects from, I am populating it using values from a datatable:
int numberofhooks = result.Tables[1].Rows.Count - 1;
Hook[] Hooks = new Hook[numberofhooks];
for (int i = 0; i < numberofhooks; i++)
{
Hooks[i] = new Hook(result.Tables[1].Rows[i]);
}
I need to filter out a subset of these objects using a variable that gets updated on the fly, which is a property of the class.
'category' is the property
I am using the AddRange method to do this, but it isn't working. What can be the issue with this? Or is there another way to accomplish this?
List<Hook> RelevantHooks = new List<Hook>();
foreach (string category in relevantHookCategories)
{
RelevantHooks.AddRange(from item in Hooks
where item.category == category
select item);
}
Thanks
I see two problems here:
If you want all the rows from your table why are you using:
int numberofhooks = result.Tables[1].Rows.Count - 1;
It should be:
int numberOfHooks = result.Tables[1].Rows.Count;
Perhaps the last item that you are excluding has the category you are looking for.
When you say "on the fly" I'm assuming that you want to have the subset RelevantHooks updated even after your query, when modifying Hooks, if that's the case then you don't have to use a List, just define your relevantHooks as:
IEnumerable<Hook> relevantHooks = Hooks.Where(h => relevantHookCategories.Contains(h.category));
e.g. If you modify the category property for any of Hooks items:
Hooks[0].category="Other Category Value";
Will either exclude or include the first hook from relevantHooks
RelevantHooks.AddRange(Hooks.Where(hook => category == hook.category));
should be enough. You don't necessarily need the select clause in this case.
No need to use AddRange. Just iterate Hooks once and check if it's one of the categories.
RelevantHooks = Hooks.Where(h => relevantHookCategories.Contains(h.category)).ToList();
Try this:
RelevantHooks.AddRange((from item in Hooks
where item.category == category
select item).ToArray());
I don't think it should matter, but it could be the AddRange method is failing because it's being passed IQueryable when it is expecting IEnumerable
Given my last Comment, here's what I meant:
for (int i = 0; i < numberofhooks; i++)
{
Hooks[i] = new Hook(result.Tables[1].Rows[i]);
if(relevantHookCategories.Contains(Hooks[i].category)
RelevantHooks.Add(Hook[i]);
}
Related
I have re edit this question below I have an example file which as multiple purchase orders in the file which is identified by the second column.
Order Number, Purchase Number,DATE,Item Code ,Qty, Description
1245456,98978,12/01/2019, 1545-878, 1,"Test"
1245456,98978,12/01/2019,1545-342,2,"Test"
1245456,98978,12/01/2019,1545-878,2,"Test"
1245456,98979,12/02/2019,1545-878,3,"Test 3"
1245456,98979,12/02/2019,1545-342,4,"Test 4"
1245456,98979,12/02/2019,1545-878,5,"Test 4"
What I want the end result to be is to be able to place the above into one class like the following
At the min I am using filelpers to parse the csv file this would work fine if I had sep header file and row file but they are combined as you see
var engine = new FileHelperEngine<CSVLines>();
var lines = engine.ReadFile(csvFileName);
So the Class should be like below
[DelimitedRecord(",")]
public class SalesOrderHeader
{
private Guid? _guid;
public Guid RowID
{
get
{
return _guid ?? (_guid = Guid.NewGuid()).GetValueOrDefault();
}
}
public string DocReference { get; set; }
public string CardCode { get; set; }
public string DocDate { get; set; }
public string ItemCode { get; set; }
public string Description { get; set; }
public string Qty { get; set; }
public string Price { get; set; }
[FieldHidden]
public List<SalesOrderHeader> OrdersLines { get; set; }
}
What I imagine I will have to do is two loops as you will see from my createsales order routine i first create the header and then add the lines in.
public void CreateSalesOrder(List<SalesOrderHeader> _salesOrders)
{
foreach (var record in _salesOrders.GroupBy(g => g.DocReference))
{
// Init the Order object
oOrder = (SAPbobsCOM.Documents)company.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oOrders);
SAPbobsCOM.SBObob oBob;
// set properties of the Order object
// oOrder.NumAtCard = record.Where(w=>w.RowID = record.Where()
oOrder.CardCode = record.First().CardCode;
oOrder.DocDueDate = DateTime.Now;
oOrder.DocDate =Convert.ToDateTime(record.First().DocDate);
foreach (var recordItems in _salesOrders.SelectMany(e=>e.OrdersLines).Where(w=>w.DocReference ==record.First().DocReference))
{
oOrder.Lines.ItemCode = recordItems.ItemCode;
oOrder.Lines.ItemDescription = recordItems.Description;
oOrder.Lines.Quantity = Convert.ToDouble(recordItems.Qty);
oOrder.Lines.Price = Convert.ToDouble(recordItems.Price);
oOrder.Lines.Add();
log.Debug(string.Format("Order Line added to sap Item Code={0}, Description={1},Qty={2}", recordItems.ItemCode, recordItems.Description, recordItems.Qty));
}
int lRetCode = oOrder.Add(); // Try to add the orer to the database
}
if(lRetCode == 0)
{
string body = "Purchase Order Imported into SAP";
}
if (lRetCode != 0)
{
int temp_int = lErrCode;
string temp_string = sErrMsg;
company.GetLastError(out temp_int, out temp_string);
if (lErrCode != -4006) // Incase adding an order failed
{
log.Error(string.Format("Error adding an order into sap ErrorCode {0},{1}", temp_int, temp_string));
}
}
}
The problem you will see i have is how do I first split the csv into the two lists and second how do i access the header rows correctly in the strongly type object as you see I am using first which will not work correctly.
With FileHelpers it is important to avoid using the mapping class for anything other than describing the underlying file structure. Here I suspect you are trying to map directly to a class which is too complex.
A FileHelpers class is just a way of defining the specification of a flat file using C# syntax.
As such, the FileHelpers classes are an unusual type of C# class and you should not try to use accepted OOP principles. FileHelpers should not have properties or methods beyond the ones used by the FileHelpers library.
Think of the FileHelpers class as the 'specification' of your CSV format only. That should be its only role. (This is good practice from a maintenance perspective anyway - if the underlying CSV structure were to change, it is easier to adapt your code).
Then if you need the records in a more 'normal' object, then map the results to something better, that is, a class that encapsulates all the functionality of the Order object rather than the CSVOrder.
So, one way of handling this type of file is to parse the file twice. In the first pass you extract the header records. Something like this:
var engine1 = new FileHelperEngine<CSVHeaders>();
var headers = engine1.ReadFile(csvFileName);
In the second pass you extract the details;
var engine2 = new FileHelperEngine<CSVDetails>();
var details = engine2.ReadFile(csvFileName);
Then you combine this information into a new dedicated class, maybe with some LINQ similar to this
var niceOrders =
headers
.DistinctBy(h => h.OrderNumber)
.SelectMany(d => details.Where(d => d.OrderNumber = y))
.Select(x =>
new NiceOrder() {
OrderNumber = x.OrderNumber,
Customer = x.Customer,
ItemCode = x.ItemCode
// etc.
});
I want to get an property value from an object that is in a list and put it into textbox.text
Below i have an example of my code:
The object:
public class Incident
{
public int Incident_id { get; set; }
public string Description { get; set; }
public string Caller { get; set; }
}
Below is my code in my form class:
List<Incident> incidentById = new List<Incident>();
incidentById = db.GetIncidentById(ID);
when my list is filled i want to put the string Caller into an textbox somewhat like below:
textBoxCaller.Text = incidentById[1].Caller;
I'm stuck at this point so i hope someone can help me out.
Thanks!
EDIT:
public List<Incident> GetIncidentById(int id)
{
using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(Helper.CnnVal("IncidentLog")))
{
var output = connection.Query<Incident> ($"select * from Incidents where Incident_id like #id", new { id = "%id%" }).ToList();
return output;
}
}
I wasn't passing the right value into my query
this did the trick!
What you want is $"select * from Incidents where Incident_id = #id", new { id }
do you want first value should go?
check like.
if(incidentById.Count>0)
{
textBoxCaller.Text = incidentById.First().Caller;
}
// or you can join all Caller in list like
if(incidentById.Count>0)
{
textBoxCaller.Text = string.join(",",incidentById.Select(x=>x.Caller));
}
The issue that you are facing is that you are trying to access the second element in the list when there are not two or more elements in the list. If you are trying to access the first element in the list, then you could do either
textBoxCaller.Text = incidentById[0].Caller;
or
textBoxCaller.Text = incidentById.First().Caller;
If you do in fact want the second element of the list, then you should be checking to verify that it's length is at least two:
if(incidentById.Count >= 2)
{
...
}
Finally, as mentioned in a comment, you should rename GetIncidentById to something that makes clear it is returning a list, like GetIncidentsById or something similar.
Let me begin by illustrating what I have vs goal I'm trying to achieve
In controller I get all the categories into single generic list in pretty much random unsorted manner
var categories = new List<Category>(this.categoryService.GetCategories())
Each category has 4 properties that matter here Id, ParentCategoryId, SortOrder, Text
SortOrder has to be applied only towards siblings on same level in the hierarchy and children have to be positioned always underneath their parent. Text has to change by prepending ".." for each level of depth.
I'd like this to be done properly with performance in mind, don't want to loop recursively through massive list multiple times.
Thanks for any input.
This might not be the most performant code, but should work for OP's problem.
Since OP didn't explicitly define the data structure (model) in question, I'm going to assume it's something like this:
public class Category {
public int Id { get; set; }
public int ParentCategoryId { get; set; }
public int SortOrder { get; set; }
public string Text { get; set; }
}
To sort a list of category (List<Category>) into a multilevel categories, we need a tree-like structure to hold the data.
First, I would extend the model (Category class) with new properties such as Level (to indicate the level-depth), Children (to hold the sub/child Category), and DisplayText (for displaying the category text according to its level):
public class CategoryNode : Category {
public CategoryNode(Category category) {
Id = category.Id;
ParentCategoryId = category.ParentCategoryId;
SortOrder = category.SortOrder;
Text = category.Text;
}
public CategoryTree Children { get; set; }
public int Level { get; set;}
public string DisplayText {
get {
// OP wants two-dots prefix per level
return string.Concat(new string('.', Level*2), Text);
}
}
}
Note: Depends of your preference, you could alter the Category class directly, instead of subclassing it into CategoryNode.
Next, I would define a class to wrap the collection of CategoryNode called CategoryTree, it is a simple wrapper of List<CategoryNode> which expose an IEnumerable interace. I'd also add a Flatten() method inside CategoryTree which will flatten the tree-like structure into a single list. This method will come in handy for binding the data into a single-list (non-hierarchical) control such as DropDownList or ListBox. And last, I'd also add a static creation method called Create() to create an instance of CatogoryTree based on a given list of Category:
public class CategoryTree : IEnumerable<CategoryNode> {
private List<CategoryNode> innerList = new List<CategoryNode>();
public CategoryTree(IEnumerable<CategoryNode> nodes) {
innerList = new List<CategoryNode>(nodes);
}
public IEnumerator<CategoryNode> GetEnumerator()
{
return innerList.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerable<CategoryNode> Flatten() {
foreach(var category in innerList.OrderBy(o => o.SortOrder)) {
yield return category;
if (category.Children != null) {
foreach(var child in category.Children.Flatten()) {
yield return child;
}
}
}
}
public static CategoryTree Create(
IEnumerable<Category> categories,
Func<Category, bool> parentPredicate,
int level = 0)
{
var nodes = categories
.Where(parentPredicate)
.OrderBy(o => o.SortOrder)
.Select(item => new CategoryNode(item) {
Level = level,
Children = Create(categories, o => o.ParentCategoryId == item.Id, level + 1)
});
return new CategoryTree(nodes);
}
}
Note: Again, arguably, you could just use List<CategoryNode> directly, extract the methods, and save yourself the hassle of creating a new class here. Your call.
With all pieces in place I could now use the following code to convert a list of Category (List<Category>) into a multilevel list of Category, and flatten that list to bind it into a DropDownList:
...
var categories = new List<Category>(this.categoryService.GetCategories())
// assuming ParentCategoryId == 0 is the root category
var categoryTree = CategoryTree.Create(categories, o => o.ParentCategoryId == 0);
var model = new SomeViewModel();
model.Categories = new SelectList(categoryTree.Flatten(), , "Id", "DisplayText");
return View(model);
...
#Html.DropDownListFor(m => m.SelectedCategory, Model.Categories)
...
Demo (using Console.Out): https://ideone.com/ejOfrr
Have you tried implementing a Dictionary structure?
As in:
Dictionary<Categories, List<T>> dictionary = new Dictionary<Categories, List<T>>();
This might conform to your requirements.
Further if you want a sorted Category structure the you can implement a SortedDictionary with Category as the Key & a corresponding List as Value.
I am using the following code to store a person's information from a grid view row in a list:
public class PlayerInfo
{
public string FirstName { get; set; }
public string Email { get; set; }
public string DaysLeft { get; set; }
}
protected void btn_Click(object sender, EventArgs e)
{
List<PersonInfo> person_list = new List<PersonInfo>();
foreach (GridViewRow row in my_gridview.Rows)
{
CheckBox cb = (CheckBox)(row.Cells[2].FindControl("CheckBox"));
if (cb.Checked)
{
person_list.Add(new PlayerInfo() {FirstName=row.Cells[0].Text,Email=row.Cells[2].Text,DaysLeft=row.Cells[5].Text});
}
else
{
}
}
etc etc..
}
So if I try and output person_list[0] this will not work as there are three items associated to each point in the list, so position 0 in the list contains the first name, email and days left. I can see the items are being stored properly when debugging in visual studio.
How would I retrieve each item individually from this list? For example being able to set the values to variables in a loop, so the email address would be assigned to an email variable, the first name to another etc? I have only ever dealt with normal arrays and have never come across a hierarchical list before.
Thanks in advance.
Iterate it using for like this
for(int i =0; i <person_list.Count; i++)
{
string name = person_list[i].FirstName;
string email = person_list[i].Email;
string daysLeft = person_list[i].DaysLeft;
}
First of all your list variable is local so if you want to access it outside of your class you need to declare it at class level.
Second as you are adding the PersonInfo objects in your list, you will get PersonInfo out of your list.
As far as iteration is concerned it can be done with any loop:
Example:
foreach(PersonInfo pInfo in person_list)
{
// your code
}
A beautiful way of using Lists with GridViews is by Setting DataPropertyNames to the names of the properties in your custom List and using it as a DataSource i.e.
my_gridview.DataSource = person_list;
Don't forget to set my_gridview.AutoGenerateColumns = false; unless you want the columns to be generated automatically based on the properties in your list.
There are several ways to get the values from a list.
You can iterate using for or foreach:
for (var i=0; i<person_list.Count; i++) {
var email=person_list[i].Email;
}
foreach(var person in person_list) {
var email=person.Email;
}
Or you can access to the individual members by using their indexes:
var person=person_list[1];
if (person!=null) {
var email=person.Email;
}
Or you can query the list using Linq :
var person=person_list.FirstOrDefault(p=>p.FirstName=="Jack");
if (person!=null) {
var email=person.Email;
}
Also, to access the items in the GridViewRow, you can use GridViewRow.DataItem
foreach (GridViewRow row in my_gridview.Rows)
{
var cb = (CheckBox)(row.Cells[2].FindControl("CheckBox"));
if (cb.Checked)
{
person_list.Add(row.DataItem as PlayerInfo);
}
}
OK, so I have learned how to create a list, view items in the list, and use the items in the list. I now want to learn how to edit the information that is in the list.
Here is my list:
class ObjectProperties
{
public string ObjectNumber { get; set; }
public string ObjectComments { get; set; }
public string ObjectAddress { get; set; }
}
List<ObjectProperties> Properties = new List<ObjectProperties>();
This is how I am adding values to the list:
ObjectProperties record = new ObjectProperties
{
ObjectNumber = txtObjectNumber.Text,
ObjectComments = txtComments.Text,
ObjectAddress = addressCombined,
};
Properties.Add(record);
I am wanting the user to enter which number they want to update by using a textbox(txtUpdateObjectNumber). I then want to compare that number to the values that are stored in record.ObjectNumber and then if it exist I want to replace the information in record.ObjectNumber and record.ObjectComments where record.ObjectNumber == txtUpdateObjectNumber. If you need me to elaborate on anything just let me know. Any help would be appreciated. Thank You :)
To find the list item, use linq:
ObjectProperties opFound = Properties.Find(x => x.ObjectNumber == txtUpdateObjectNumber.Text);
Or the delegate form:
ObjectProperties opFound = Properties.Find(delegate (ObjectProperties x) { return x.ObjectNumber == txtUpdateObjectNumber.Text; });
Once you've found the item in the list, any changes you make to opFound, including the ObjectNumber, will persist in the list.