Simplify my C# Linq statement? - c#

class :
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
List
List<Foo> lst = new List<Foo>();
Datatable :
DataTable dt = GetFromDb ()....
I want to fill lst with records from dt.
I've managed doing :
Array.ForEach(dt.AsEnumerable().ToArray(), y = > lst.Add(new Foo()
{
Id = int.Parse(y["id"].ToString()), Name = y["name"].ToString()
}));
question :
Can't I do something else like dt.AsEnumerable().Select(_ => fill lst ) ?
I know that part of the select signature (in this case ) is Func<datarow,void> which wont compile
But still , is there any other way of doing this besides the ugly way of mine ?

Using LINQ to DataSet:
var foos = from row in dt.AsEnumerable()
select new Foo()
{
Id = row.Field<int>("id"),
Name = row.Field<string>("name")
};
// create a new list
List<Foo> lst = foos.ToList();
// update: add items to an exisiting list
fooList.AddRange(foos);

Try this
var fooList = (from t in dt.AsEnumerable()
select new Foo { Id = t.Field<int>("Id"),
Name= t.Field<string>("Name") }).ToList();
The AsEnumerable extension method on a DataTable will return a Collection of DataRows. then you do a projection from your Linq Result with required fields. Since it is dataRow type, you need to specify what each column type will be.

You are on the right track, you can do the following:
lst = dt.AsEnumerable().Select(y = > new Foo()
{
Id = Convert.ToInt32(y["id"]), Name = y["name"] as string
}).ToList();
EDIT: To add to an existing list
I would normally just concatenate two lists:
lst = lst.Concat(dt.AsEnumerable().Select(...)).ToList();
However having seen ken2k's answer, I think I will now start using AddRange instead.

You could do:
List<Foo> lst = dt.AsEnumerable().Select(z => new Foo
{
Id = int.Parse(z["id"].ToString()),
Name = z["name"].ToString()
}).ToList();
EDIT:
If you want to append to an existing List<Foo> instance:
lst.AddRange(dt.AsEnumerable().Select(z => new Foo
{
Id = int.Parse(z["id"].ToString()),
Name = z["name"].ToString()
}));

I got another solution via Rows property :
List<Foo> results = (from DataRow myRow in dt.Rows
select new Foo()
{
Id =int.Parse( row["id"].ToString()),
Name = row["name"].ToString()
}).ToList();

Related

JSON File and LINQ Sum and Group

Could I please get some help with querying from a JSON file? Populating a datagrid view works just fine but what I am trying to do now is filter the data using LINQ which I'm really struggling with.
This works just fine, populating the datagridview with all of my jsonfile data
//dataGridView1.DataSource = (from p in movie2
// select p).ToArray();
Below is what I have been playing around with. When I group by employee ID into g, I can not longer use my p references to fields.
using (StreamReader file = File.OpenText(#"C:\temp\GRMReportingJSONfiles\Assigned_FTE\" + myFile))
{
JsonSerializer serializer = new JsonSerializer();
IEnumerable<AssgnData> movie2 = (IEnumerable<AssgnData>)serializer.Deserialize(file, typeof(IEnumerable<AssgnData>));
dataGridView1.DataSource = (from p in movie2
group p by p.EMPLID[0] into g
select new {
EMPLID = p.EMPLID,
(decimal?)decimal.Parse(p.MNTH1) ?? 0).Sum(),
};
);
//dataGridView1.DataSource = (from p in movie2
// select Int32.Parse(p.MNTH1).Sum();
dataGridView1.DataSource = (from p in movie2
group p by p.EMPLID[0] into g
select (decimal?)decimal.Parse(p.MNTH1) ?? 0).Sum(); //dataGridView1.DataSource = (from p in movie2
// select p).ToArray();
//where p.Resource_BU == "7000776"
//chart1.DataBindCrossTable(movie2, "MNTH1", "1", "PROJECT_ID", "Label = FTE");
//chart1.Refresh();
}
Here is part of the array layout, removed other fields for now as I was just trying to focus on these two, dataset has 100k rows and 50 columns
public class AssgnData
{
public string EMPLID { get; set; }
public string MNTH1 { get; set; }
}
In my opinion, using Fluent Syntax usually makes it a bit easier to understand what is going wrong here.
As soon as you group your data you are no longer working on the individual objects, but on a 'group', which is the key and an enumerable of objects.
Getting the sum per employee should then be grouping by the full employee id and then parsing the MNTH1 fields of your objects and summing them.
dataGridView1.DataSource = movie2
.GroupBy(p => p.EMPLID) // create a group of data per employee
.Select(g => new
{
EMPLID = g.Key, // the employee id is the group key
Sum = g.Sum(data => decimal.Parse(data.MNTH1)) // parse and sum
})
.ToArray();
Edit: you are right, you need the ToArray to evaluate the query. I just verified on my computer and it works.
Try following :
class Program
{
static void Main(string[] args)
{
IEnumerable<AssgnData> movie2 = null;
dataGridView1.DataSource = movie2.GroupBy(x => new {id = x.EMPLID, month = x.MNTH1})
.Select(x => new {
EMPLYID = x.Key.id,
MONTH = x.Key.month,
SUM = x.Sum(y => y.value)
});
}
}
public class AssgnData
{
public string EMPLID { get; set; }
public string MNTH1 { get; set; }
public int value { get;set;}
}

How can I create a list inside a list with a foreach in c# ?

In other words I need all the elements of list "Categories" to be the "Parent" and elements of list "commodities" be the children.
Example
public string GetCommodities()
{
List<dynamic> categories = new List<dynamic>();
List<dynamic> commodities = new List<dynamic>();
foreach (var comcat in QuickQuoteRepo.CommodityCategories.All().OrderBy(o => o.Order))
{
categories.Add(new
{
comcat.Category,
});
foreach (var com in comcat.Commodities.OrderBy(o => o.Name))
{
commodities.Add(new
{
com.Name,
});
}
}
var response = new JavaScriptSerializer().Serialize(commodities);
return response;
}
And see if it's possible to all commodities names inside each category, within this foreach.
I tried adding a dynamic list such as:
dynamic listOfElements = new { CAT = categories, COMM = commodities };
But it does't return elemnts as parents or dependency of categories. Is the same as adding
commodities.Add(new
{
comcat.Category,
com.Name,
});
public string GetCommodities()
{
List<dynamic> categoryCommodityList = new List<dynamic>();
foreach (var comcat in QuickQuoteRepo.CommodityCategories.All().OrderBy(o => o.Order))
{
var allCommodities = comcat.Commodities.OrderBy(o => o.Name).Select(com => com.Name).ToList();
categoryCommodityList.Add(new { Catagory = comcat.Category, Items = allCommodities } );
}
return new JavaScriptSerializer().Serialize(categoryCommodityList);
}
You class structure does not support parent-child relationships. I mean, if what you want is that each Category holds a list of commodities, then you would need something like this:
var result = from c in comcat
select new { Category = c, Commoddities = c.Commoddities};
This will return a hierarchy of Categories including all Commodities underneath it.
If you are just receiving a flat data set, then you need something like this:
var result = from c in comcat
select new { Category = c,
Commoddities = c.Where(x=>x.Category.Name == c.Name).Select(x=>x.Commodity) };
Hopefully you get the idea...

Nested group by based on flatten record set

I have the following responses from the API. How can I group them into the following structure?
Student[]
- Name
- Classes[]
- ClassName
- ClassId
- ClassCategories[]
- CategoryName
- CategoryWeight
- Assignments[]
- AssignmentName
- Score
I was managed to group them until the "Classes" level but unable to get the ClassCategories for each of the classes
var data = (from result in results
group result by new { result.StudentId, result.FirstName, result.LastName, result.MiddleInitial }
into StudentGroup
select new GroupedStudent
{
StudentId = StudentGroup.Key.StudentId,
FullName = string.Format("{0} {1} {2}", StudentGroup.Key.FirstName, StudentGroup.Key.MiddleInitial, StudentGroup.Key.LastName).Replace(" ", " "),
Classes = from result in results
group result by new { result.ClassId, result.ClassName } into ClassGroup
select new groupedClass
{
ClassName = ClassGroup.Key.ClassName,
ClassId = ClassGroup.Key.ClassId,
ClassCategories = ...
})
}).ToList();
Can anyone please assists me? Thank you.
First, you have make ClassGroup from StudentGroup not from results.
Classes = from s in StudentGroup group result by new { s.ClassId, s.ClassName } into ClassGroup
The complete linq query is as follows:
var data =
(from result in results
group result by new { result.StudentId, result.FirstName, result.LastName, result.MiddleInitial } into StudentGroup
select new
{
StudentId = StudentGroup.Key.StudentId,
FullName = string.Format("{0} {1} {2}", StudentGroup.Key.FirstName, StudentGroup.Key.MiddleInitial, StudentGroup.Key.LastName).Replace(" ", " "),
Classes = (from s in StudentGroup
group s by new { s.ClassId, s.ClassName } into ClassGroup
select new
{
ClassId = ClassGroup.Key.ClassId,
ClassName = ClassGroup.Key.ClassName,
ClassCategories = (from c in ClassGroup
group c by new { c.CategoryName, c.CategoryWeight } into CategoryGroup
select new
{
CategoryName = CategoryGroup.Key.CategoryName,
CategoryWeight = CategoryGroup.Key.CategoryWeight,
Assignments = (from ct in CategoryGroup
group ct by new { ct.AssignmentName, ct.Score } into AssingnmentGroup
select new
{
AssignmentName = AssingnmentGroup.Key.AssignmentName,
Score = AssingnmentGroup.Key.Score
}).ToList()
}).ToList()
}).ToList()
}).ToList();
For example, if you want to access to the first Assignment's score, you can get it like this:
var student = data.FirstOrDefault();
var score = student.Classes[0].ClassCategories[0].Assignments[0].Score;
This is usually how I do It.
Create a class to store your data
Create a list of that class type
In your case instead of string dataRow maybe you can use a sub class
.
// get data from webservice
var json = webClient.DownloadString(url);
var values = JsonConvert.DeserializeObject<JArray>(json);
// create a list to save all the element
List<myClass> classList = new List<myClass>();
// process every row
foreach (string dataRow in values)
{
string[] dataField = dataRow.Split(',');
// have a constructor to assign each value to this element
myClass ctROW = new myClass(dataField);
classList.add(ctROW );

Get datatable and filling model from it

In my project i have a fill method.
public IList<MyModel> Fill(DataTable dt)
{
IList<MyModel> IProperty = new List<MyModel>();
for (int i = 0; i < dt.Rows.Count; i++)
{
MyModel Property = new MyModel
{
Name= dt.Rows[i]["Name"].ToString(),
Surname = dt.Rows[i]["Surname"].ToString(),
Age = dt.Rows[i]["Age"].ToString(),
};
IProperty.Add(Property);
}
return IProperty;
}
It fill my model from datatable. But i must write this fill method for all model. I dont want to write this method all the time. I need a solution for this. I'm open to any kind of proposal
Not:I dont want to use Entity framework or other ORM frameworks.
If you can make sure the field names and properties of the object with the same name, try this:
public IEnumerable<T> Fill<T>(DataTable dt)
where T : new()
{
return dt.AsEnumerable().Select(row =>
{
var obj = new T();
var properties =
from p in obj.GetType().GetProperties()
join c in dt.Columns.Cast<DataColumn>() on p.Name equals c.ColumnName
select new {p, c};
foreach (var item in properties)
item.p.SetValue(obj, row[item.c]);
return obj;
});
}

Parse Hierarchial Linq to XML

I am trying to parse this data:
<Product>
<ProductName>Climate Guard</ProductName>
<Tag>ClimateGuard</Tag>
<SupportPage>~/Support/ClimateGuard.aspx</SupportPage>
<ProductPage>~/Products/ClimateGuard.aspx</ProductPage>
<ProductCategories>
<ProductCategory>Climate Guard</ProductCategory>
<PartNumbers>
<PartNumber Primary="true">CLIMATE GUARD</PartNumber>
<PartNumber>CLIMATEGUARD LT</PartNumber>
<PartNumber>CLIMATE GUARD STARTER KIT</PartNumber>
<PartNumber>SENSOR MODULE</PartNumber>
<PartNumber>SWCH INP MODULE</PartNumber>
<PartNumber>TEMP SENSOR</PartNumber>
<PartNumber>HUMIDITY SENSOR</PartNumber>
<PartNumber>DOOR CONTACT</PartNumber>
<PartNumber>MOTION SENSOR</PartNumber>
<PartNumber>FLOOD DETECTOR</PartNumber>
<PartNumber>SMOKE DETECTOR</PartNumber>
<PartNumber>TILT SENSOR</PartNumber>
<PartNumber>SENSOR CABLE</PartNumber>
<PartNumber>PWR INP CABLE</PartNumber>
<PartNumber>100FT 2-WIRE</PartNumber>
<PartNumber>RJ25 COUPLER</PartNumber>
</PartNumbers>
</ProductCategories>
<Downloads>
<Download>
<Version>1.0.27</Version>
<Url>~/Files/Downloads/ClimateGuard_Firmware_1_0_27.bin</Url>
<Comment>Firmware</Comment>
</Download>
<Download>
<Version>1.0.6</Version>
<Url>~/Files/Downloads/ClimateGuard_BuiltInModule_1_0_6.bin</Url>
<Comment>Built-in Module</Comment>
</Download>
<Download>
<Version>1.0.2</Version>
<Url>~/Files/Downloads/ClimateGuard_SensorModule_1_0_2.bin</Url>
<Comment>Sensor Module</Comment>
</Download>
<Download>
<Version>1.0.0</Version>
<Url>~/Files/Downloads/ClimateGuard_SwitchInputModule_1_0_0.bin</Url>
<Comment>Switch Input Module</Comment>
</Download>
</Downloads>
</Product>
I am trying to get a List of part numbers, however, only the first appears:
Product Category Climate Guard
Part Number Climate Guard
What is wrong with my part numbers code:
public List<Products> GetProducts()
{
XElement myElement = XElement.Load(HttpContext.Current.Server.MapPath("~/App_Data/products.xml"));
var query = from a in myElement.Elements("Product")
select new Products
{
ProductName = a.Element("ProductName").Value,
Tag = a.Element("Tag").Value,
SupportPage = a.Element("SupportPage").Value,
ProductPage = a.Element("ProductPage").Value,
ProductCategories = from b in a.Elements("ProductCategories")
select new ProductCategories
{
ProductCategory = b.Element("ProductCategory").Value,
//PartNumbers = GetPartNumbers(myElement.Elements("Product").Elements("ProductCategories").Elements("PartNumbers").Elements("PartNumber"))
PartNumbers = from c in b.Elements("PartNumbers")
select new PartNumbers
{
PartNumber = c.Element("PartNumber").Value
}
},
Downloads = from bb in a.Elements("Downloads").Elements("Download")
select new Downloads
{
Comment = bb.Element("Comment").Value,
Url = bb.Element("Url").Value,
Version = bb.Element("Version").Value
},
};
return query.ToList();
}
All of the types (ProductName, Tag, etc.) are strings. PartNumbers is an IEnumerable.
Currently instead of getting collection of PartNumber element values, you are getting only element for their parent PartNumbers with value of first PartNumber child inside. If you want to have PartNumbers class instead of simple list of string values, then it should look like:
public class PartNumbers
{
// list instead of single value
public List<string> Numbers { get; set; }
}
And it should be parsed this way:
PartNumbers = new PartNumbers {
Numbers = b.Element("PartNumbers").Elements()
.Select(c => (string)c).ToList()
}
BTW why are you choosing so strange range variable names (b for ProductCategories elements, a for products, etc)? Also you can use simple List<string> to store part numbers (without creating class for that):
PartNumbers = b.Element("PartNumbers").Elements().Select(c => (string)c).ToList()
You may have forgotten the ToList() for ProductCategories, PartNumbers and Downloads.
public List<Products> GetProducts()
{
XElement myElement = XElement.Load(HttpContext.Current.Server.MapPath("~/App_Data/products.xml"));
var query = from a in myElement.Elements("Product")
select new Products
{
ProductName = a.Element("ProductName").Value,
Tag = a.Element("Tag").Value,
SupportPage = a.Element("SupportPage").Value,
ProductPage = a.Element("ProductPage").Value,
ProductCategories = (from b in a.Elements("ProductCategories")
select new ProductCategories
{
ProductCategory = b.Element("ProductCategory").Value,
//PartNumbers = GetPartNumbers(myElement.Elements("Product").Elements("ProductCategories").Elements("PartNumbers").Elements("PartNumber"))
PartNumbers = (from c in b.Elements("PartNumbers")
select new PartNumbers
{
PartNumber = c.Element("PartNumber").Value
}).ToList()
}).ToList(),
Downloads = (from bb in a.Elements("Downloads").Elements("Download")
select new Downloads
{
Comment = bb.Element("Comment").Value,
Url = bb.Element("Url").Value,
Version = bb.Element("Version").Value
}).ToList(),
};
return query.ToList();
}

Categories

Resources