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.
Related
I am working on one project and I have a question regarding the converting type. I want to create simple search for my project, but it can't return result with this message :
Error 1 Cannot implicitly convert type 'System.Collections.Generic.List' to 'EmployeeDataAccess.TimeWorkMonthly'
public TimeWorkMonthly Get(int id)
{
using (EmployeeDbEntities Entities = new EmployeeDbEntities())
{
List<TimeWorkMonthly> persons = new List<TimeWorkMonthly>();
var result = Entities.TimeWorkMonthlies
.Where(e => e.KartNo == id)
.Select(e => e)
.ToList();
return result.ToList();
}
}
The return type of your method is TimeWorkMonthlies but inside the method body return List<TimeWorkMonthlies>
You should either
change your method return type to IEnumerable<TimeWorkMonthlies>
(You could use List<TimeWorkMonthlies> but using an interface to abstract a collection type is better for many reasons)
Use FirstOrDefault, First, SingleOrDefault or Single extension methods of IEnumerable if you aim to return only one element and you do not care about anything except for the first element
Which of those methods is better depends on your data and search criteria - i.e. whether you expect this ID to be unique or not.
From your semantics it looks like you're doing a sort of repository like ID lookup, so my guess would be solution 2) and using Single or SingleOrDefault
The last choice is how you want your program to behave if nothing is found by ID
If you want an exception, use Single
If you want a null use SingleOrDefault
In Summary, all you have to do is change your last line of code to
return result.Single();
(And ofcourse, you don't need a call to ToList() just before that)
Your method signature indicates you just want to return a single object. But you're returning a List of objects. Using .ToList() is not appropriate when you just want to return one object. There are four appropriate extension methods:
First - will return the first item from the collection and throw an exception if the collection is empty.
FirstOrDefault - will return the first item in the collection, or the default of the type if the collection is empty.
Single - if there is one item in the collection, it will return it. If there are no items in the collection an exception is thrown. If there are multiple items in the collection, an exception is thrown.
SingleOrDefault - if there is one item in the collection it will return it. If there are no items in the collection it will return the default value for the type. If there are multiple items in the collection it will thrown an exception.
Since you're searching by ID, you probably don't ever to expect to match two or more elements. So that rules out First and FirstOrDefault. You should use Single or SingleOrDefault depending on what you want the behavior to be if there is no item found that has the matching ID.
public TimeWorkMonthly Get(int id)
{
using (EmployeeDbEntities Entities = new EmployeeDbEntities())
{
var result = Entities.TimeWorkMonthlies.Where(e => e.KartNo == id).Single();
return result;
}
}
Note I eliminated the persons variable because you never did anything with it. And your usage of the .Select extension method was superflous since you just selected the same object already being iterated over. Select is for when you want to transform the object.
The problem is your qry only . If you want to convert it with Tolist() function you have to change your qry
like this
public TimeWorkMonthly Get(int id)
{
using (EmployeeDbEntities Entities = new EmployeeDbEntities())
{
var result = from x in Entities.TimeWorkMonthlies
Where x.KartNo == id
Select x;
return result.ToList();
}
}
You can now convert it to list by tolist() and use it according to your need.
I have a data set I need to filter on a string value. If a property contains a specific string, the item is selected. That works fine
I need to change this to allow for a LIST of strings to test against
I could iterate objects and loop through selected values, storing matches in seperate list, but feel that there should be a better way.
Hope someone has a good example on how to acomplish this
//Gets a set of addresses, objects have several properties, one of them beeing (example):
// o.ZipCity ="1000 Copenhagen"
List<AddresObjectType> result = getAllAddresses();
// Example : 1000,2000
var listOfZip = context.Request["zip"].Split(Convert.ToChar(","));
//Current code, just one value
result = result.Where(t => t.ZipCity.Contains(context.Request["zip"])).ToList();
//Code I need...
//IF any of the passed values are matched then include
result = result.Where(t => t.ZipCity.Contains(listOfZip)).ToList();
SO desiered effect :
- Requested values "1000,2000,3000" (one to many values)
- Result set includes all that has a ZipCity value that contains at least one of the values
You could try this one:
result = result.Where(t => listofZip.Contains(t.ZipCity)).ToList();
The listOfZip will contain the values of 1000, 2000, 3000 and you try to get all the cities whose zip is one of them.
Update
result = result.Where(t => listofZip.Any(zip=>t.ZipCity.Contains(zip)).ToList();
The Any extension method return true if there is any element in the listofZip that satisfies the predicate:
zip=>t.ZipCity.Contains(zip)
or false if there isn't any.
What does the predicate checks?
It checks if the current zip, is contained in the ZipCity. If so returns true. Otherwise, it returns false.
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;
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;
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.