c# - LINQ select from Collection - c#

I'm attempting to write a simple Select method on a class that inherits from IList.
public class RowDataCollection : IList<RowData> {
private List<RowData> rowList;
internal RowDataCollection(List<RowData> data) {
rowList = data;
}
// ...
}
public RowDataCollection Rows;
public RowDataCollection Select(string colName, object value) {
List<RowData> rowList = from item in Rows
where item[colName].Value == value
select item;
return new RowDataCollection(rowList);
}
Some problems I'm having:
First:
VS2010 reports Cannot implicitly convert type 'IEnumerable<RowData>' to 'List<RowData>'. An explicit conversion exists (are you missing a cast?)
OK, where does the CAST go?
Second:
Someone could pass in an invalid colName value (i.e. String.IsNullOrEmpty(colName)) or a null parameter (object value == null).
How would I handle the way my function returns if the input parameters are invalid?
[Solved]
I edited my Select statement (even renamed it per the suggestions here). I had to use a switch to cast to the data type that the data was in, but it does work.
public RowDataCollection SelectRow(string colName, object value) {
if (!String.IsNullOrEmpty(colName) && (value != null) && (0 < Rows.Count)) {
switch (Rows[0][colName].GetValueType()) {
case TableDataType.Boolean:
return new RowDataCollection(Rows.Where(r => (bool)r[colName].Value == (bool)value).ToList());
case TableDataType.Character:
return new RowDataCollection(Rows.Where(r => (char)r[colName].Value == (char)value).ToList());
case TableDataType.DateTime:
return new RowDataCollection(Rows.Where(r => (DateTime)r[colName].Value == (DateTime)value).ToList());
case TableDataType.Decimal:
return new RowDataCollection(Rows.Where(r => (Decimal)r[colName].Value == (Decimal)value).ToList());
case TableDataType.Integer:
return new RowDataCollection(Rows.Where(r => (int)r[colName].Value == (int)value).ToList());
case TableDataType.String:
return new RowDataCollection(Rows.Where(r => r[colName].Value.ToString() == value.ToString()).ToList());
}
}
return null;
}
[Solved (short version)]
Jon Skeet posted this about the same time I posted my solution, and (as always) his code is much nicer.
public RowDataCollection SelectRow(string colName, object value) {
List<RowData> rowList = Rows.Where(r => r[colName].Value.Equals(value)).ToList();
return new RowDataCollection(rowList);
}
#Jon Skeet: If I ever see your face in the same line at some software developer position I'm applying for, I'm just going to turn around and go home.
#Everyone: Thanks for all the help!

The result of a query like that isn't a List<T>, it's an IEnumerable<T>. If you want to convert that into a List<T>, just call ToList:
List<RowData> rowList = (from item in Rows
where item[colName].Value == value
select item).ToList();
As it happens, you're only calling Where in your query. I would rewrite this as:
List<RowData> rowList = Rows.Where(item => item[colName].Value.Equals(value))
.ToList();
I'd also rename the method to something which is obviously filtering rather than projecting, given that the latter is the more common use of the term "select" in LINQ.
As for input parameters - I suggest you validate the arguments and throw an exception if they're not valid:
if (string.IsNullOrEmpty(colName))
{
throw new ArgumentException("colName");
}

You're getting the error message because LINQ Queries return IEnumerable, not List.
If you need a List, it's easy enough:
List<RowData> rowList = (from item in Rows
where item[colName].Value == value
select item).ToList();

You can't directly cast an IEnumerable<RowData> to a List<RowData>, however, there does exist a convenience function Enumerable.ToList<T>(), used like so:
List<RowData> rowList = (from item in Rows
where item[colName].Value == value
select item).ToList();
As for your second question, an exception would occur during the ToList() call as the LINQ expression is evaluated immediately. You have a few options, including throwing ArgumentExceptions or returning an empty list. It depends on your use cases. I'd suggest simply throwing an exception (assuming you have some HasColumn() method on your RowData class):
if (colName == null)
{
throw new ArgumentNullException("colName");
}
else if (!Rows.All(row => row.HasColumn(colName)))
{
throw new ArgumentException("No such column " + colName, "colName");
}
Per your edit, another approach, if a column missing is not necessarily a "problem":
...
// note the change to Any()
else if (!Rows.Any(row => row.HasColumn(colName))
{
throw new ArgumentException("No such column " + colName, "colName");
}
List<RowData> rowList = (from item in Rows
where item.HasColumn(colName)
&& item[colName].Value == value
select item).ToList();

You have to convert IQueriable<> to List<>, by calling ToList();
public RowDataCollection Select(string colName, object value) {
List<RowData> rowList = from item in Rows
where item[colName].Value == value
select item;
return new RowDataCollection(rowList.ToList());
}

Related

Linq code doesn't return correct record

I have a table named dbo.EmployeeType with three records:
PK_EmployeetypeID EmployeeTypeName
1 Project Manager
2 Business Analyst
3 Developer
I have this piece of Linq code:
public static string GetTypeByID(int id)
{
using (ProjectTrackingEntities1 db = new ProjectTrackingEntities1())
{
var type = db.EmployeeTypes.Select(o => new LOOKUPEmployeeType
{
PK_EmployeeTypeID = id,
EmployeeTypeName = o.EmployeeTypeName
});
return type.FirstOrDefault().EmployeeTypeName;
}
}
No matter what id I send to it, it returns Project Manager, and I'm confused as to why.
You need to apply a filter, otherwise you're just returning the first record and hard coding the ID. Try this:
public static string GetTypeByID(int id)
{
using (ProjectTrackingEntities1 db = new ProjectTrackingEntities1())
{
//Here we apply a filter, the lambda here is what creates the WHERE clause
var type = db.EmployeeTypes
.FirstOrDefault(et => et.PK_EmployeeTypeID == id);
if(type != null)
{
return type.EmployeeTypeName;
}
else
{
return "";
}
}
}
Note that using FirstOrDefault means if there are no matches, or multiple matches, type will be null and you will get an empty string returned.
Set a breakpoint on type = ... and inspect it. You have no Where in there so you get all - and Select just makes LOOKUPEmployeeTypes out of all of them.
FirstOrDefault then returns the first of those 3 which is always the ProjManager
Fix:
var type = db
.EmployeeTypes
.Where( o => o.Id == id)
.Select(o => new LOOKUPEmployeeType
{
PK_EmployeeTypeID = id,
EmployeeTypeName = o.EmployeeTypeName
});
In your code you only return the first value. You need to tell EF which value you need to return.
Let us assume you need the value with Id=2. Instead of Select(), use Single(x => x.Id == 2) or First(x => x.Id == 2).

Iterate over an IQueryable without calling ToList()

I have a DB used for a production line. It has an Orders table, and Ordertracker table, an Item table, and an Itemtracker table.
Both Orders and Items have many-to-many relationships with status. The tracker tables resolves these relationships in such a way that an item can have multiple entries in the tracker - each with a particular status.
I tried to upload a picture of the tables to make things clearer but alas, I don't have enough points yet :C
I need to find items whose last status in the Itemtracker table meets a condition, either '3' or '0'.
I then need to get the first one of these items.
The steps I am using to accomplish this are as follows:
Get all the Orders which have a certain status.
Get all the Items in that Order.
Get all the Items whose last status was = 0 or 3.
Get the first of these items.
My code is as follows:
public ITEM GetFirstItemFailedOrNotInProductionFromCurrentOrder()
{
var firstOrder = GetFirstOrderInProductionAndNotCompleted();
var items = ERPContext.ITEM.Where(i => i.OrderID == firstOrder.OrderID) as IQueryable<ITEM>;
if (CheckStatusOfItems(items) != null)
{
var nextItem = CheckStatusOfItems(items);
return nextItem ;
}
else
{
return null;
}
}
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
List<ITEM> listOfItemsToProduce = new List<ITEM>();
foreach (ITEM item in items.ToList())
{
var lastStatusOfItem = ERPContext.ITEMTRACKER.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID).FirstOrDefault();
if (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
{
listOfItemsToProduce.Add(item);
}
}
return listOfItemsToProduce.FirstOrDefault();
}
Now, this all works fine and returns what I need but I'm aware that this might not be the best approach. As it is now my IQueryable collection of items will never contain more than 6 items - but if it could grow larger, then calling ToList() on the IQueryable and iterating over the results in-memory would probably not be a good idea.
Is there a better way to iterate through the IQueryable items to fetch out the items that have a certain status as their latest status without calling ToList() and foreaching through the results?
Any advice would be much appreciated.
Using LINQ query syntax, you can build declaratively a single query pretty much the same way you wrote the imperative iteration. foreach translates to from, var to let and if to where:
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
let lastStatusOfItem = ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.FirstOrDefault()
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}
or alternatively using from instead of let and Take(1) instead of FirstOrDefault():
private ITEM CheckStatusOfItems(IQueryable<ITEM> items)
{
var query =
from item in items
from lastStatusOfItem in ERPContext.ITEMTRACKER
.Where(it => it.ItemID == item.ItemID)
.OrderByDescending(it => it.ItemTrackerID)
.Take(1)
where (lastStatusOfItem.ItemStatus == (int)ItemStatus.Failed || lastStatusOfItem.ItemStatus == (int)ItemStatus.Confirmed)
select item;
return query.FirstOrDefault();
}

unable to cast of type 'System.Data.EnumerableRowCollection`1[System.String]' to type 'System.IConvertible'

I need the column value(FundSpreadDurationContribution) of a data table(residing in dataset) through LINQ which fetches the above error (heading)
Elaborated: when a row cell has value Spread Duration--IR Swap, need the corresponding column cell FundSpreadDurationContribution value.
double testvalue = Convert.ToDouble(raptorDS.Tables[RaptorTable.DurationContribBySector].AsEnumerable().Where(r =>
r.Field<string>(RaptorColumns.FundCode) == fundDescriptionColumn &&
r.Field<string>(RaptorColumns.Component) == Component.B8_DURATION_CONTRIBUTION_BY_SECTOR &&
r.Field<string>(RaptorColumns.Sector) == "Spread Duration--IR Swap").Select(s => s.Field<string>(RaptorColumns.FundSpreadDurationContribution)))
I am learner of LINQ.
// First check if this query will return any results
var records =
raptorDS.Tables[RaptorTable.DurationContribBySector].AsEnumerable().Where(r =>
r.Field<string>(RaptorColumns.FundCode) == fundDescriptionColumn &&
r.Field<string>(RaptorColumns.Component) == Component.B8_DURATION_CONTRIBUTION_BY_SECTOR &&
r.Field<string>(RaptorColumns.Sector) == "Spread Duration--IR Swap");
// CHeck if any result
if (records.Any())
{
// We have results so let's go through each record and try to get that value
// converted to a double
List<double> values = new List<double>();
List<string> badValues = new List<string>();
foreach (var thisRecord in records)
{
var fsdc = thisRecord.Field<string>(RaptorColumns.FundSpreadDurationContribution);
if (!string.IsNullOrWhiteSpace(fsdc))
{
double val = 0;
if (double.TryParse(fsdc, val))
{
values.Add(val);
}
else
{
badValues.Add(fsdc);
}
}
}
// Do whatever you need to do with values here
// and bad values here
}
Where returns a collection, which cannot be converted to a double. Use one of these Linq methods to get one answer to convert:
Single
SingleOrDefault
First
FirstOrDefault
This is just playing around with Datatypes which fixed the type casting issue
var sumFundSpreadDuration = raptorDS.Tables[RaptorTable.DurationContribBySector].AsEnumerable().Where(r =>
r.Field<string>(RaptorColumns.FundCode) == fundDescriptionColumn &&
r.Field<string>(RaptorColumns.Component) == Component.B8_DURATION_CONTRIBUTION_BY_SECTOR &&
r.Field<string>(RaptorColumns.Sector) == "Spread Duration--IR Swap")
.Select(s => s.Field<double?>(RaptorColumns.FundSpreadDurationContribution)).FirstOrDefault();
If you see the datatype of variable is changed to Variant from double. Also, the datatype of column FundSpreadDurationContribution is changed to double. Thanks to CodingYoshi for providing insights of datatypes in linq

LINQ select from TList?

I have a list of RowData items (a custom class).
A RowData item contains a list of CellData items (another custom class).
Currently, the only way I can access each individual CellData item is through the RowData's this[int index], which means I'd have to create a loop for each search.
What I want is to create a method that will return all of the CellData items for a given index value:
private List<RowData> rowList;
public IEnumerable<CellData> Cells(int index) {
foreach (var row in rowList) {
if (row.ID == index) {
return row.AsEnumerable<CellData>();
}
}
return null;
}
I don't like returning NULL, and I'd like to know how to accomplish this task using some LINQ techniques (either Query or Method syntax).
To be able to use LINQ syntax for getting cells collection from row, your RowData need to be enumerable, i.e. implement IEnumerable interface.
By .AsEnumerable<CellData>() in your example, I assume your RowData already implements it and enumerating RowData returns collection of CellData objects.
If not, you need to implement IEnumerable in RowData on your own. If your this[int index] accessor reads the data from a collection, you can just return that collection's GetEnumerator.
And then, you'll be able to fetch CellData collection from RowData collection using LINQ queries, i.e. like this:
var cells = rowList.Where(x => x.ID == index).SelectMany(x => x);
Assuming only one row is returned given an index:
public IEnumerable<CellData> Cells(int index)
{
var foundRow = rowList.FirstOrDefault(row => row.ID == index);
//if no row was found, foundRow will be null
if(foundRow != null)
return foundRow.AsEnumerable<CellData>();
else
return new CellData[]; //return empty cells IEnumerable. Could also be new List<CellData>()
}
}
This code assumes foundRow.AsEnumerable<CellData>() returns a IEnumerable<CellData>.
You have to check for nullity, because row at a given index might nor be found.
This will achieve the same result.
var resultsList = from x in rowList
where x.ID == index
select x;
return resultsList;
public IEnumerable<CellData> Cells(int index){
var cells = (from r on rowList
where r.ID == index
select r.Cells).FirstOrDefault();
return cells;
}
What about:
List<CellData> cells = rowList.Where(x => x.Index == index).SelectMany(x => x.Cells).ToList();

Remove item from list based on condition

I have a struct like this:
public struct stuff
{
public int ID;
public int quan;
}
and want to to remove the product where ID is 1.
I'm trying this currently:
prods.Remove(new stuff{ prodID = 1});
and it's not working.
THANKS TO ALL
If your collection type is a List<stuff>, then the best approach is probably the following:
prods.RemoveAll(s => s.ID == 1)
This only does one pass (iteration) over the list, so should be more efficient than other methods.
If your type is more generically an ICollection<T>, it might help to write a short extension method if you care about performance. If not, then you'd probably get away with using LINQ (calling Where or Single).
Using linq:
prods.Remove( prods.Single( s => s.ID == 1 ) );
Maybe you even want to use SingleOrDefault() and check if the element exists at all ...
EDIT:
Since stuff is a struct, SingleOrDefault() will not return null. But it will return default( stuff ), which will have an ID of 0. When you don't have an ID of 0 for your normal stuff-objects you can query for this ID:
var stuffToRemove = prods.SingleOrDefault( s => s.ID == 1 );
if( stuffToRemove.ID != 0 )
{
prods.Remove( stuffToRemove );
}
If you have LINQ:
var itemtoremove = prods.Where(item => item.ID == 1).First();
prods.Remove(itemtoremove)
prods.Remove(prods.Find(x => x.ID == 1));
Here is a solution for those, who want to remove it from the database with Entity Framework:
prods.RemoveWhere(s => s.ID == 1);
And the extension method itself:
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
namespace LivaNova.NGPDM.Client.Services.Data.Extensions
{
public static class DbSetExtensions
{
public static void RemoveWhere<TEntity>(this DbSet<TEntity> entities, Expression<Func<TEntity, bool>> predicate) where TEntity : class
{
var records = entities
.Where(predicate)
.ToList();
if (records.Count > 0)
entities.RemoveRange(records);
}
}
}
P.S. This simulates the method RemoveAll() that's not available for DB sets of the entity framework.
prods.Remove(prods.Single(p=>p.ID == 1));
you can't modify collection in foreach, as Vincent suggests
You can only remove something you have a reference to. So you will have to search the entire list:
stuff r;
foreach(stuff s in prods) {
if(s.ID == 1) {
r = s;
break;
}
}
prods.Remove(r);
or
for(int i = 0; i < prods.Length; i++) {
if(prods[i].ID == 1) {
prods.RemoveAt(i);
break;
}
}
A bit late to the game, however, a simple extension method can implement a RemoveAll on IList<T>. The trick is to loop over the collection in reverse order to avoid the extra logic of deleting the current item and having to try on the current index if removed. Also the reverse order prevents having to copy all the remaining items. Depending on the .NET version, this copy could be expensive.
public static int RemoveAll<T>(this IList<T> list, Predicate<T> match)
{
if (list == null) throw new ArgumentNullException("list");
if (match == null) throw new ArgumentNullException("match");
int count = 0;
for (int i = list.Count - 1; i >= 0; i--)
{
if (match(list[i]))
{
++count;
list.RemoveAt(i);
}
}
return count;
}
You could use Linq.
var prod = from p in prods
where p.ID != 1
select p;

Categories

Resources