why ToList() doesnt work in linq? - c#

i am having a issue while sorting data with more than a field
here is sample code i have used :
var Item = from itm in DB.Items
select new Item
{
};
return Item.ToList().OrderBy(e => e.ExpiryDate).ToList();
above code does'nt show any error but also doesn sort with expirydate field :(
if i use like this
return Item.OrderBy(e => e.ExpiryDate).ToList();
this gives a error that sql doesnt contain translation for orderby
any help
thanks in advance

I think there are a couple things going on here. First, you've named your result variable the same as the class you're creating in the select. Probably doesn't make much difference, but it's confusing. Let's change it:
var items = from itm in DB.Items
select new Item
{
};
Next, your DB.Items context is already built of Item objects. You don't need to create new ones. In the above code, your new Item objects are empty anyway.
var items = from itm in DB.Items
select itm;
If you want to order them right away, you can do that too:
var items = from itm in DB.Items
orderby itm.ExpiryDate
select itm;
If you need that as a List, you can do that in the same line:
var items = (from itm in DB.Items
orderby itm.ExpiryDate
select itm).ToList();
Now items will be a concrete List of Item objects (List<Item>).

Your code creates instances of Item using the default constructor, without passing anything that you get from the DB to it. That is why you get a list of identical empty items; sorting them has no effect.
Remove the first Select to make it work:
var Item = DB.Items; // No "Select ..."
return Item.ToList().OrderBy(e => e.ExpiryDate).ToList();
Your first ToList forces the data into memory, where OrderBy can be applied. You can replace it with an AsEnumerable() call as well, to avoid creating a list in memory for the second time:
return Item.AsEnumerable().OrderBy(e => e.ExpiryDate).ToList();

Related

Why is IEnumerable losing updated data?

Could you explain to me why after executing the following code the Selected property is not updated to true?
The ListItem type used comes from System.Web.UI.WebControls namespace and is a class (not a struct.) I believed the FirstOrDefault function returns a reference to an instance which I can update and pass around in the items enumerable.
// produce list items out of the communities
IEnumerable<ListItem> items = communities.Select(community => new ListItem(community.Name, community.Id.ToString()));
// mark the right list item as selected, if needed
if (platform.CommunityId > 0)
{
string strCommunityId = platform.CommunityId.ToString();
ListItem selectedItem = items.FirstOrDefault(item => item.Value == strCommunityId);
if (selectedItem != null) selectedItem.Selected = true;
}
// now items do not store any updated item!
Is that because the enumerator is executed each time a foreach is called and thus creating new items instead of returning the set containing the item I updated?
The problem is that IEnumerable is not repeatable. You are performing the projection (community => new ListItem) every time it is enumerated - hence it is a new ListItem each time. Select is a non-buffered deferred projection.
You can fix everything here with the simple addition of a .ToList() to force the data into a single list;
var items = communities.Select(
community => new ListItem(community.Name, community.Id.ToString())
).ToList();
Now that the data is in the list, you can loop over the list any number of times - it'll always be the same items, and changes will be retained.
Your problem is that
IEnumerable<ListItem> items = communities
.Select(community => new ListItem(community.Name, community.Id.ToString()));
creates an IEnumerable that's lazily evaluated -- that is, every time it is enumerated, the original communities sequence is re-enumerated and your Select projection is re-executed per item in that sequence.
If you stick a .ToList() at the end, changing the line to:
IEnumerable<ListItem> items = communities
.Select(community => new ListItem(community.Name, community.Id.ToString()))
.ToList();
you will observe a different result. While it is still an IEnumerable, it will no longer be a lazily evaluated one, and the changes you make in it will be observable in later iterations over the same IEnumerable.
It happens, because you use Select:
IEnumerable<ListItem> items = communities
.Select(community => new ListItem(community.Name, community.Id.ToString()));
which creates new objects every time you iterate through items.
I think Marc Gravell's answer is the right one, but you could avoid this confusion and do it in a single line (possibly leading to another kind of confusion). ;)
// produce list items out of the communities
IEnumerable<ListItem> items = communities.Select(community =>
new ListItem(community.Name, community.Id.ToString())
{
Selected = community.Id == platform.CommunityId
});

Remove an item from a list

why cant I select remove or delete? I want to remove a record from a list
IEnumerable<StockLocation_Table> AllCurrentStocklocations = db.StockLocation_Table.ToList();
List<StockLocation> StockLocations = ServerHelper.GetStockLocationsBatch(BatchUrl, i, batchSize);
foreach (StockLocation_Table _stock_table in AllCurrentStocklocations)
{
foreach (StockLocation _stock in StockLocations)
{
if (_stock.ServerLocationId == _stock_table.ServerLocationId)
{
AllCurrentStocklocations.?? why cant i say remove._stock_table
}
}
}
Because it is IEnumerable<T> and the Remove method is not defined in IEnumerable<T>.Since you are using ToList just use a List<T> as type:
List<StockLocation_Table> AllCurrentStocklocations = db.StockLocation_Table.ToList();
Edit: Also you can't modify the collection inside of foreach loop.You can use LINQ instead:
var AllCurrentStocklocations = db.StockLocation_Table
.Where(x => !StockLocations
.Any(s => s.ServerLocationId == x.ServerLocationId).ToList()
What you want to do here is get all of the items from your DB table where the ID is not in this other list. What you should do here is construct a query such that you get just those items without those IDs, rather than pulling down the entire DB table into a list, and then going through this other list for each item) to look for IDs so that you can remove the current item from this list. In addition to being super inefficient, this would also mean removing the item from a collection being iterated, which would break the iterator. Instead write something that can be translated into a DB query:
List<StockLocation> stockLocations = ServerHelper.GetStockLocationsBatch(
BatchUrl, i, batchSize);
var locationIDs = stockLocations.Select(location => location.ServerLocationId);
db.StockLocation_Table.Where(item =>
!locationIDs.Contains(item.ServerLocationId));

Add single item to linq to sql query

I would like to add a single item to the results of a linq query. I know it's not possible to join a local source and a SQL source. So, is it possible to construct a query to do the same as this?
SELECT ID FROM Types
UNION
SELECT 1
The best I've come up with is this:
List<int> OrgList = DBContext.Types.Select(b => b.ID).ToList();
OrgList.Add(1);
but I'd rather add the item beforehand and still have an IQueryable. Or is there a good reason to not do it this way?
You must get the data from the db and then add the new item like you have done in your code.
The only way to have an IQueryable would be to deffer the adding of the new item to the point were the query is resolved.
You can use Union:
var query = DBContext.Types.Select(b => b.ID).ToList().Union(new[]{1});
Not tested but it should work
Try Concat
var result = DBContext.Types.Select(p => p.ID)
.Concat(new List<int>() { 1 }).ToList();

Prevent new values from LINQ query

I have some data in a List I want to make a query against. In the meantime however, other users can add to this List and I get wrong items in return:
var query = from s in selected
where s.contains("www")
select s);
Then a user Can add item to selected list before The query is run, and I Will get this also. Can I prevent this behaviour?:
selected.add("123www")
foreach (var s in query)
/// gives me 123www
The var "query" just has the query assigned to it, but the query itself is first performed when the query is accessed in for example a foreach loop - hence you get the newly added data.
If you don't want this, you can use an extension method like "ToList()", where the collection stays the same:
var queryResultList = (from s in selected
where n.contains("www")
select s).ToList();
Here the ToList() iterates the collection immediately, and you can now iterate the queryResultList and get the right results and even though new elements arrive the output stays the same.
The query represents an action, which can be triggered any time you want
and it will yield different results if the source of data changed.
Just make a "snapshot" of the results at the time you desire to do so with a .ToArray():
var query = from s in selected
Where s.contains("www")
Select s)
string[] snapshot = query.ToArray();
When you call .ToList() or .ToArray() the query is executed. Therefore to avoid your problem you can write this code:
var query = from s in selected
where s.Contains("www")
select s);
var result = query.ToList();
selected.Add("123www");
foreach(var item in result)
{
/* You won't see "123www" */
}

Linq to update a collection with values from another collection?

I have IQueryable<someClass> baseList
and List<someOtherClass> someData
What I want to do is update attributes in some items in baseList.
For every item in someData, I want to find the corresponding item in baselist and update a property of the item.
someOtherClass.someCode == baseList.myCode
can I do some type of join with Linq and set baseList.someData += someOtherClass.DataIWantToConcantenate.
I could probably do this by iteration, but is there a fancy Linq way I can do this in just a couple lines of code?
Thanks for any tips,
~ck in San Diego
To pair elements in the two lists you can use a LINQ join:
var pairs = from d in someData
join b in baseList.AsEnumerable()
on d.someCode equals b.myCode
select new { b, d };
This will give you an enumeration of each item in someData paired with its counterpart in baseList. From there, you can concatenate in a loop:
foreach(var pair in pairs)
pair.b.SomeData += pair.d.DataIWantToConcantenate;
If you really meant set concatenation rather than +=, take a look at LINQ's Union, Intersect or Except methods.
LINQ is for querying - not for updating. That means it'll be fine to use LINQ to find the corresponding item, but for the modification you should be using iteration.
Admittedly you might want to perform some appropriate query to get baseList into an efficient form first - e.g. a Dictionary<string, SomeClass> based on the property you'll be using to find the corresponding item.
You can convert the IQueryable<SomeClass> into a List<SomeClass>, use the ForEach method to loop over it and update the elements, then convert back to IQueryable:
List<SomeClass> convertedList = baseList.ToList();
convertedList.ForEach(sc =>
{
SomeOtherClass oc = someData.First(obj => obj.SomeCode == sc.MyCode);
if (oc != null)
{
sc.SomeData += oc.DataIWantToConcatenate;
}
});
baseList = convertedList.AsQueryable(); // back to IQueryable
But it may be more efficient during this using non-LINQ constructs.
As mentioned before, it should be a combination of loop and LINQ
foreach (var someDataItem in someData)
{
someDataItem.PropertyToUpdate = (baseList.FirstOrDefault(baseListItem => baseListItem .key == someDataItem.key) ?? new SomeClass(){OtherProperty = "OptionalDefaultValue"}).OtherProperty;
}
You can't simply find objects that are in one list but not the other, because they are two different types. I'll assume you're comparing a property called OtherProperty that is common to the two different classes, and shares the same type. In that case, using nothing but Linq queries:
// update those items that match by creating a new item with an
// updated property
var updated =
from d in data
join b in baseList on d.OtherProperty equals b.OtherProperty
select new MyType()
{
PropertyToUpdate = d.PropertyToUpdate,
OtherProperty = d.OtherProperty
};
// and now add to that all the items in baseList that weren't found in data
var result =
(from b in baseList
where !updated.Select(x => x.OtherProperty).Contains(b.OtherProperty)
select b).Concat(updated);

Categories