How to Get a Object from IEnumerable collection using LINQ Lambda? - c#

storageColl is having a IStorage with property "Id" as "Test".
What I am doing-
string id="Test";
IEnumerable<IStorageObject> storageColl = getStorageCollection();
IStorageObject storageObject = storageColl.ToList().Where(m => m.Properties["Id"] == id)
.ToList()
.Cast<IStorageObject>().ToArray()[0];
Is there a better way to do this. As this may throw array out of bound exception if the storageColl will not have that "Test".

You can use FirstOrDefault on the IEnumerable.
var storageObject = storageCol1.Where(m => m.Properties["Id"] == id).FirstOrDefault();
Or as David Hedlund pointed out, use the predicate overload on FirstOrDefault and remove the Where.
var storageObject = storageCol1.FirstOrDefault(m => m.Properties["Id"] == id);

Your storageColl is a sequence of objects that implement IStorageObject. The use of the Where only limits the elements you get when you enumerate over the sequence, it does not change them.
It is a waste of processing power to convert the sequence to a list when you only need the first element of the sequence or the a subset of it.
Familiarize yourself with the following Ling functions:
Any() returns true if the sequence contains at least one element
Any( item => ....) return true if any of the elements in the sequence meets the requirement
First() returns the first element of the sequence. Exception if not Any()
FirstOrDefault returns the first element of the sequence or the default (usually null) if not Any()
The nice thing about these functions is that they don't have to enumerate over all elements in the sequence, but can stop as soon as they found something.
If you use ToList() the code enumerates over all elements, throws most of them away and uses only the first element. FirstOrDefault() would have stopped after the first enumeration.

since the collection is implement IStorageObject you don't neet to cast them and for get item by index you can use any class that utilizes Array or IList
since LINQ operates on IEnumerable (Array itself is enumerable->to iterate) you don't need to cast them to array.you can utilize ElementAt method or Use IList classes (as List)
IStorageObject storageObject = storageColl.Where(m => m.Properties["Id"] == id).First();

You can simply achieve this by
var result = storageColl.Where(m => m.Properties["Id"] == id);

You should check firstOrDefault because can return null.
var Object = storageCol.Where(p => p.Properties["Id"] == id).FirstOrDefault();
if(Object != null)
{
// Do some Work
}

Related

Skip an empty sequence inside a LINQ .Where statement

I have an array of Tuple arrays like this:
(Type, Func<int, bool>)[][] Formats;
I also have a Type typ and a int i.
I then need an IEnumerable<(Type, Func<int,bool>[])> (aka return items) where a potential match is evaluated by Item1 (i.e. Type) matching typ in the format .IsAssignableFrom(typ). If we get a match, then we aren't done: we only include it if Item2.Invoke(i) evaluates to true.
var results = Formats.Where(f => f.Where(o => o.Item1.IsAssignableFrom(typ)).First().Item2.Invoke(i));
This is not valid because I think if a return item doesn't have an inner item to retrieve in the .First() call, it throws an InvalidOperationException "Sequence contains no data" because you can't call .First() on an empty sequence.
Can you help restate this Linq to ignore the sequence if empty?
This is a perfect opportunity to use the Enumerable.FirstOrDefault() method:
Returns the first element of a sequence, or a default value if no element is found.
In this case, the "default value" would be (null, null). So, use FirstOrDefault() instead of First(), and then ignore items appropriately. Something like this might work:
var results = Formats.Where(f => f.Where(o => o.Item1.IsAssignableFrom(typ)).FirstOrDefault().Item2?.Invoke(i) ?? false);

Indexing IQueryable<int>?

How do you index an IQueryable?
I am using a LINQ to sql query to get in values from a particular column. The query is as follows,
var intitalQuery = (from a in sql.GetTable<Staff_Time_TBL>()
where a.Info_Data == SelectedOption
select a.Staff_No).Distinct();
From there I want to be able index the intitalQuery variable and get values as needed.
That value is then used in another query.
My first try was this,
Column1.DataContext = sql.Staff_Time_TBLs.Where(item =>
item.Section_Data == SelectedOption &&
item.Staff_No == intitalQuery[0];
Then I tried this from here with no luck.
Column1.DataContext = sql.Staff_Time_TBLs.Where(item =>
item.Section_Data == SelectedOption &&
item.Staff_No == intitalQuery.First());
From what I can from the link is that that way gets just the first value, I want to be able to get all values via indexing. How do you go about that?
IQueryable<T> inherits from IEnumerable and as such has a wealth of extension methods to accomplish almost anything you'd need from a sequence. In particular, .ToList() turns an enumerable into a List<T> that allows efficient indexing.
.ToList() is slightly more efficient than the more obvious .ToArray() when working with sequences of unknown initial length, because .ToArray() requires an additional copy to end up with an array of exactly the right size. (But arrays are faster to loop over, so it all depends on what you're doing.)
You can do this:
public static List<Staff_Time_TBLs> GetIndexed(string staffNo){
var stuff = sql.Staff_Time_TBLs.Where(item =>
item.Section_Data == SelectedOption &&
item.Staff_No == staffNo;
return stuff.ToList();
}
//to use it...
initialQuery.ForEach(p=>{
var indexvalue = GetIndexed(p)
});

Sequence contains more than one matching element

When I am trying to set IsDefault property of each dressing items where match a condition it throws an error saying:
Sequence contains more than one matching sequence.
(this.DressingItems
.Where(xx => xx.DressingInfo.CatID == catId
&& xx.ProductID == this.ProductID)
.Single()).IsDefault = false;
Well, this exception says that at least two items of the sequence DressingItems match your Where condition. The call to Single then causes the exception because it asserts that only one item is passed in.
Reading your question makes me think that you want to do something on each item of the input sequence, so you will probably use a foreach loop:
foreach(var item in this.DressingItems.Where(xx => xx.DressingInfo.CatID == catId && xx.ProductID == this.ProductID))
{
item.IsDefault = false;
}
this.DressingItems.Where(x=> x.DressingInfo.CatID == catId &&
x.ProductID == this.ProductID).ToList()
.ForEach(item=>item.IsDefault = false);
The point of the Single operator is to assert that a given sequence only has one item. For instance when retrieving a specific instance by primary key.
I suppose you want to mutate the state of any DressingItem matching the criteria, in which case you have some options, all involving enumerating the resultset, and executing some behavior.
There is no LINQ operator to specifically do this, since LINQ operators are meant to be pure. Pure functions are functions that do not have side effects, and this is exactly what you are trying to do.
There is, however, an extensionmethod on List<T> which does allow this. e.g.
this.DressingItems.Where(di => di.DressingInfo.CatID == catId
&& di.ProductID == this.ProductID)
.ToList()
.ForEach(di =>
{
di.IsDefault = false
});
Or you could roll your own:
public static class EnumerableExtensions
{
public static IEnumerable<T> ForEach<T>(
this IEnumerable<T> source,
Action<T> mutator)
{
var buffered = source.ToList();
buffered.ForEach(mutator);
return buffered;
}
}
You might ask why the guys at Microsoft decided against adding this to the BCL: As I recall, the idea was that an extensionmethod vs. a foreach() { } construct would not yield much benefits in terms of typing anyway, and it wouldn't help at all in terms of ambiguity. All other operators are side-effect free, and this one is explicitely designed to induce them.
It is an InvalidOperationException thrown by the Single method.
The method is supposed to return only one element, please check the criteria that you use on your query.
However an exception is also thrown when it fails to find any element
you have more than one item in this.DressingItems that match the given CatId and Product Id.
if you are sure there must be one (single), then you have to review how is this.DressingItems loaded.
if it's expected there are more than one, then you have to use foreach to set the values.
Since you are looking for a one liner, you could create your own method doing that.
public static void DoActionForEachElement<T>(IEnumerable<T> items, Func<T, bool> predicate, Action<T> action)
{
foreach (var item in items)
{
if (predicate(item))
action(item);
}
}
and then call it by
DoActionForEachElement(
DressingItems,
xx => xx.DressingInfo.CatID == catId && xx.ProductID == ProductID,
x => x.IsDefault = false);
This way you don't have to cast the result from Where to a List first.

how to convert from IEnumerable<type> to type?

I am using EF 4 with repository pattern which has the generic query method shown below:
public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
{
return objectSet.Where(filter);
}
I know I can select a complete object like this:
context.PeriodRepository.Query(a => a.EntityId == selectedEntityId);
But I want to pass a Linq query that returns it as type instead of IEnumerable<type> using a LINQ expression without changing the method. Please advise me how to do that.
Use the First() or FirstOrDefault() methods and pass in a predicate to find the element you want if it is not the first.
A Query describes the operations performed on the list; you must perform the query to get the result(s).
Using .ToList()/.ToArray() will return all items that match the query. Use .First() to get the first item that matches or .FirstOrDefault() to get the first item or the default value for no matches.
The default value for a class is null.
I think your code should be like this:
var myMatch = context.PeriodRepository
.Query(a => a.EntityId == selectedEntityId)
.First();
Also note that, First() will throw an exception when there is no match.

What does LINQ return when the results are empty

I have a question about LINQ query. Normally a query returns a IEnumerable<T> type. If the return is empty, not sure if it is null or not. I am not sure if the following ToList() will throw an exception or just a empty List<string> if nothing found in IEnumerable result?
List<string> list = {"a"};
// is the result null or something else?
IEnumerable<string> ilist = from x in list where x == "ABC" select x;
// Or directly to a list, exception thrown?
List<string> list1 = (from x in list where x == "ABC" select x).ToList();
I know it is a very simple question, but I don't have VS available for the time being.
It will return an empty enumerable. It won't be null. You can sleep sound :)
You can also check the .Any() method:
if (!YourResult.Any())
Just a note that .Any will still retrieve the records from the database; doing a .FirstOrDefault()/.Where() will be just as much overhead but you would then be able to catch the object(s) returned from the query
var lst = new List<int>() { 1, 2, 3 };
var ans = lst.Where( i => i > 3 );
(ans == null).Dump(); // False
(ans.Count() == 0 ).Dump(); // True
(Dump is from LinqPad)
.ToList returns an empty list. (same as new List<T>() );
In Linq-to-SQL if you try to get the first element on a query with no results you will get sequence contains no elements error. I can assure you that the mentioned error is not equal to object reference not set to an instance of an object.
in conclusion no, it won't return null since null can't say sequence contains no elements it will always say object reference not set to an instance of an object ;)
Other posts here have made it clear that the result is an "empty" IQueryable, which ToList() will correctly change to be an empty list etc.
Do be careful with some of the operators, as they will throw if you send them an empty enumerable. This can happen when you chain them together.
It won't throw exception, you'll get an empty list.

Categories