Linq VAR and Typed Object - c#

I would like a sample of code.
At the moment I use linq in c# and asp.net 4 ef4
var querySlotOrder = from slot in context.CmsSlots
where slot.SlotId == myCurrentSlotId
select slot;
if (querySlotOrder.SlotOrder == myNewSlotOrder)
e.Cancel = true;
This linq query return only a record.
Using VAR I cannot get the Typed Object and I cannot access its property SlotOrder.
How to change the query? thanks for your help
Usefully resource on the topic:
http://msdn.microsoft.com/en-us/library/bb384065.aspx
http://msdn.microsoft.com/en-us/library/bb397947.aspx
http://msdn.microsoft.com/en-us/library/bb397678.aspx

Even if your query returns a single object, the Select method, which you are using behind the scenes, doesn't. It returns an IQueryable<T> in EF.
You should use a method such as Single, SingleOrDefault, First, FirstOrDefault if you want to store a single object.
var querySlotOrder = (from slot in context.CmsSlots
where slot.SlotId == myCurrentSlotId
select slot).Single();
The difference between the four methods is:
Single: Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.
SingleOrDefault: Returns the only element of a sequence, or a default value if the sequence is empty; this method throws an exception if there is more than one element in the sequence.
First: Returns the first element of a sequence.
FirstOrDefault: Returns the first element of a sequence, or a default value if the sequence contains no elements.
(Definitions from MSDN)

The LINQ select-statement always returns a queryable collection. Therefore you need to fetch a single object from it.
var querySlotOrder = (from slot in context.CmsSlots
where slot.SlotId == myCurrentSlotId
select slot).FirstOrDefault();

The type of the returned object is IQueryable<CmdSlot> (assuming that CmdSlot is the type of the elements), and querySlotOrder gets that type (that's the effect of var; var itself is not a type). If you are absolutely sure that there will always be exactly one element in the result collection, you can retrieve it with querySlotOrder.Single().

The linq query returns not a record, but a collection of records that has only 1 element. If you want to get the first element and if you are sure that there is only 1 element in the collection, use Single extension method:
var querySlotOrders = from slot in context.CmsSlots
where slot.SlotId == myCurrentSlotId
select slot;
var querySlotOrder = querySlotOrders.Single();
if (querySlotOrder.SlotOrder == myNewSlotOrder)
e.Cancel = true;

As Dennis is pointing out in his answer, you're not getting an instance of one object, you're getting an IEnumerable back from your query. In order to access the SlotOrder property you need to pick a particular item from the collection, most likely the first - judging by your query.

This is madd0's code but reformated to use C# style instead of SQL style
var querySlotOrder = context.CmsSlots
.Where(slot => slot.SlotId == myCurrentSlotId)
.Single();
it's better to read and understand whan SQL style

Why do you use a var? if you know the type of the object you expect you should type querySlotOrder:
MyObjectType querySlotOrder = (from slot in context.CmsSlots
where slot.SlotId == myCurrentSlotId
select slot).FirstOrDefault();
if (querySlotOrder.SlotOrder == myNewSlotOrder)
e.Cancel = true;

Related

simple linq issue, but I can't figure it out

I have a simple LINQ issue. I'm trying to query a list and match a member ID with a lists ID and then return some text:
var IDtext = IDMethod(MembID);
var t = from ctext in cIDtext
where ctext.cid.ToString() == MembID
select cxt.C_ID;
ViewBag.thisID = t.ToString();
All this returns is:
System.Linq.Enumerable+WhereSelectArrayIterator`2[pu15.Models.C_ID,System.Int32]
But this never changes or shows what I want it to show.
What am I doing wrong?
You want to use Single or SingleOrDefault or First or FirstOrDefault, depending on what you want exactly, e.g.:
ViewBag.thisID = t.Single();
Single will throw an exception if there is more than one result or if there are no results.
If you'll use SingleOrDefault instead it will return null in case of more than one result or no result.
First on the other hand will fail only for an empty collection - when there is more than one element it will return the first one.
The most tolerant, FirstOrDefault will return null for empty result list, and the first element if there is more than one.
You have to remember that a where clause returns an IEnumerable<T> and not just one value. You have to take the first or a specific element of that collection and show it or if it is a collection of objects, show a property from that element.
In your case this would be:
var IDtext = IDMethod(MembID);
var allIDs = from ctext in cIDtext
where ctext.cid.ToString() == MembID
select cxt.C_ID;
var single = allIDs.SingleOrDefault();
//or:
var first = allIDs.FirstOrDefault();
ViewBag.thisID = single.ToString();
//or
ViewBag.thisID = first.ToString();
you can use method syntax, skip the Where clause, and just use First()
ViewBag.thisID = cIDtext
.First(c => c.cid.ToString() == MembID)
.C_ID.ToString();
t is an IEnumerable that may return many items. If you only want to use one of these items, you need to call t.First() or t.Single() ,eg:
ViewBag.thisID = t.First().ToString();
The difference between the two methods is that Single will throw an exception if there are more than 1 items in the results, while First will only throw if there are no matching results.
If you want to check that a result actually exists, you can use FirstOrDefault or SingleOrDefault, eg:
var id=t.FirstOrDefault();
if (id!=null)
ViewBag.thisID = id.ToString();
You can condense the call if you pass the condition as the predicate to First, FirstDefault etc:
var id=cIDtext.FirstOrDefault(ctext=> ctext.cid.ToString() == MembID);
The issue is that t is an IEnumerable and ToString will just give you the name of the type. You need to turn your enumeration into something like a comma separated list
ViewBag.thisID = string.Join(",", t.Select(v => v.ToString()));
Or more likely you only want one value and you need to use either First or Single.
ViewBag.thisID = t.First().ToString();
or
ViewBag.thisID = t.Single().ToString();
Use First if there are multiple return values and you only want the first, or you can use Last if you wan the last one. If the query should return only one value then you should use Single. There are also FirstOrDefault, LastOrDefault, and SingleOrDefault that will return a default value if the enumeration is empty or in the case of Single if the enumeration has more than one value. Single, First, and Last will throw an exception if the enumeration is empty or in the case of Single if the enumeration has more than one value. Just choose the appropriate one for your use case.

Calling FirstOrDefault multiple times in LINQ to set object properties

I have a LINQ query like so:
var Item = (from s in contextD.Items where s.Id == Id select s).ToList();
Further down the Item object properties are set like so:
Item.FirstOrDefault().Qty = qtyToUpdate;
Item.FirstOrDefault().TotalPrice = UItem.FirstOrDefault().Qty * UItem.FirstOrDefault().Price;
...
My question is, will calling FirstOrDefault always loop through the result set returned by the query?
Shouldn't a single call be made and put in an object like so:
MyObject objMyObject = new MyObject;
objMyObject = Item.FirstOrDefault();
and then go about setting objMyObject properties.
The first part using FirstOrDefault is actually in the production, I am trying to find out if that's the right way.
Regards.
will calling FirstOrDefault always loop through the result set
returned by the query?
FirstOrDefault() never loops through all result set - it either returns first item, or default if set is empty. Also in this particular case even enumerator will not be created - thus you are calling FirstOrDefault() on variable of List<T> type, simply item at index 0 will be returned (if list is not empty). If you will investigate Enumerable.FirstOrDefault() implementation:
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
But this will be invoked each time you are calling FirstOrDefault().
Also you are missing 'default' case. If you are chaining methods like in your first sample, you can get NullReferenceException if list is empty. So, make sure something was returned by query:
var item = Item.FirstOrDefault();
if (item == null)
return; // or throw
item.Qty = qtyToUpdate;
var uitem = UItem.FirstOrDefault();
if (uitem == null)
return; // or throw
item.TotalPrice = uitem.Qty * uitem.Price;
One more note - you have little difference in performance if you are performing FirstOrDefault() on in-memory collection. But difference will be huge if you will perform it without saving query results into list. In that case each FirstOrDefault() call will cause new database query.

IEnumerable<T>.Union(IEnumerable<T>) overwrites contents instead of unioning

I've got a collection of items (ADO.NET Entity Framework), and need to return a subset as search results based on a couple different criteria. Unfortunately, the criteria overlap in such a way that I can't just take the collection Where the criteria are met (or drop Where the criteria are not met), since this would leave out or duplicate valid items that should be returned.
I decided I would do each check individually, and combine the results. I considered using AddRange, but that would result in duplicates in the results list (and my understanding is it would enumerate the collection every time - am I correct/mistaken here?). I realized Union does not insert duplicates, and defers enumeration until necessary (again, is this understanding correct?).
The search is written as follows:
IEnumerable<MyClass> Results = Enumerable.Empty<MyClass>();
IEnumerable<MyClass> Potential = db.MyClasses.Where(x => x.Y); //Precondition
int parsed_key;
//For each searchable value
foreach(var selected in SelectedValues1)
{
IEnumerable<MyClass> matched = Potential.Where(x => x.Value1 == selected);
Results = Results.Union(matched); //This is where the problem is
}
//Ellipsed....
foreach(var selected in SelectedValuesN) //Happens to be integer
{
if(!int.TryParse(selected, out parsed_id))
continue;
IEnumerable<MyClass> matched = Potential.Where(x => x.ValueN == parsed_id);
Results = Results.Union(matched); //This is where the problem is
}
It seems, however, that Results = Results.Union(matched) is working more like Results = matched. I've stepped through with some test data and a test search. The search asks for results where the first field is -1, 0, 1, or 3. This should return 4 results (two 0s, a 1 and a 3). The first iteration of the loops works as expected, with Results still being empty. The second iteration also works as expected, with Results containing two items. After the third iteration, however, Results contains only one item.
Have I just misunderstood how .Union works, or is there something else going on here?
Because of deferred execution, by the time you eventually consume Results, it is the union of many Where queries all of which are based on the last value of selected.
So you have
Results = Potential.Where(selected)
.Union(Potential.Where(selected))
.Union(potential.Where(selected))...
and all the selected values are the same.
You need to create a var currentSelected = selected inside your loop and pass that to the query. That way each value of selected will be captured individually and you won't have this problem.
You can do this much more simply:
Reuslts = SelectedValues.SelectMany(s => Potential.Where(x => x.Value == s));
(this may return duplicates)
Or
Results = Potential.Where(x => SelectedValues.Contains(x.Value));
As pointed out by others, your LINQ expression is a closure. This means your variable selected is captured by the LINQ expression in each iteration of your foreach-loop. The same variable is used in each iteration of the foreach, so it will end up having whatever the last value was. To get around this, you will need to declare a local variable within the foreach-loop, like so:
//For each searchable value
foreach(var selected in SelectedValues1)
{
var localSelected = selected;
Results = Results.Union(Potential.Where(x => x.Value1 == localSelected));
}
It is much shorter to just use .Contains():
Results = Results.Union(Potential.Where(x => SelectedValues1.Contains(x.Value1)));
Since you need to query multiple SelectedValues collections, you could put them all inside their own collection and iterate over that as well, although you'd need some way of matching the correct field/property on your objects.
You could possibly do this by storing your lists of selected values in a Dictionary with the name of the field/property as the key. You would use Reflection to look up the correct field and perform your check. You could then shorten the code to the following:
// Store each of your searchable lists here
Dictionary<string, IEnumerable<MyClass>> DictionaryOfSelectedValues = ...;
Type t = typeof(MyType);
// For each list of searchable values
foreach(var selectedValues in DictionaryOfSelectedValues) // Returns KeyValuePair<TKey, TValue>
{
// Try to get a property for this key
PropertyInfo prop = t.GetProperty(selectedValues.Key);
IEnumerable<MyClass> localSelected = selectedValues.Value;
if( prop != null )
{
Results = Results.Union(Potential.Where(x =>
localSelected.Contains(prop.GetValue(x, null))));
}
else // If it's not a property, check if the entry is for a field
{
FieldInfo field = t.GetField(selectedValues.Key);
if( field != null )
{
Results = Results.Union(Potential.Where(x =>
localSelected.Contains(field.GetValue(x, null))));
}
}
}
No, your use of union is absoloutely correct.
The only thing to keep in mind is it excludes duplicates as based on the equality operator. Do you have sample data?
Okay, I think you are are haveing a problem because Union uses deferred execution.
What happens if you do,
var unionResults = Results.Union(matched).ToList();
Results = unionResults;

How to typecast from var to an object

i just linq to find the max object from a list of object now i want change it back from var to object. How is that done.
List < MyObject> lt = matchings.ToList();
var wwe = lt.Max(ya => ya.Similarity);
var itemsMax = lt.Where(xa => xa.Similarity == wwe);
MyObject sm =(TemplateMatch) itemsMax;//it gives error here
var is not a type. It's a keyword meaning "fill in the type for me".
Where returns a collection. You are attempting to cast a collection to a single item.
Instead of Where, use FirstOrDefault.
var itemsMax = lt.FirstOrDefault(xa => xa.Similarity == wwe);
Now you get back a single item, and can cast it if needed.
Note the above works if you only want a single item with that value.
If it is possible to have multiple items with the max value--and you want multiple items back--combine Where with OfType or Cast.
var itemsMax = lt.Where(xa => xa.Similarity == wwe);
var allItemsCasted = itemsMax.Cast<NewType>(); // this throws
var typeCompatibleItems = itemsMax.OfType<NewType>(); // this filters
The problem seems to be that Where returns a collection of objects, but you need only a single object. Try itemsMax.Single()
Instead of enumerating through the matchings twice (once to calculate the "max similarity" and a second time to find the item that has the max similarity), you could do this in one pass using IEnumerable<T>.Aggregate:
var sm = matchings.Aggregate((maxItem, next) =>
next.Similarity > maxItem.Similarity ? next : maxItem);
Your lt.Where returns an enumerable. So, that's why it does not cast to a single object. Add a .Single() method on the end and it should cast.

Using Linq to find the element after a specified element in a collection

I have an ordered list of People. I have a person that I know exists in that collection. How can I determine which person is next in the list?
You could do something like this:
IEnumerable<Person> persons = ..
var firstPersonAfterJack = persons.SkipWhile(p => p.Name != "Jack")
.ElementAt(1); //Zero-indexed, means second
The idea is to produce a sequence resulting in skipping elements until you meet the condition, then take the second element of that sequence.
If there's no guarantee that the query will return a result (e.g. a match is never found, or is the last element of the sequence), you could replace ElementAt with ElementAtOrDefault, and then do a null-test to check for success / failure.
I notice you say in your question that you have an ordered list of people. If you could explain what that means in more detail, we might be able to provide a better answer (for example, we may not have to linear-search the sequence).
SkipWhile is a method that takes a predicate and skips everything until the predicate is false. It returns that element and everything after.
var remainingPeople = collectionOfPeople.SkipWhile(p => !isThePerson(p));
if (remainingPeople.Count() == 1)
{
// the person was the last in the list.
}
var nextPerson = remainingPeople.Skip(1).First();
where isThePerson is a method that takes a person and returns true if it is the person you are interested it.
You can use code like this:
String toDir = Environment.GetCommandLineArgs().SkipWhile(x => x != "/to").Skip(1).Take(1).FirstOrDefault();
This value gets == null if "/to" command line argument not given, non-null if path was provided.

Categories