I have the following code in my controller which gets the first result of a search with multiple words. Similar to the answer to this question: ASP:NET MVC multiple words in search
var items = from s in db.Items select s;
if (!string.IsNullOrEmpty(searchString) && !searchString.Any(x => Char.IsWhiteSpace(x)))
{
searchString.Trim();
items = items.Where(s => s.keywords.Contains(searchString) || s.notes.Contains(searchString)
}
else if (!string.IsNullOrEmpty(searchString) && searchString.Any(x => Char.IsWhiteSpace(x)))
{
searchString.Trim();
string[] strings = searchString.Split(' ');
var finalItems = new List<Items>();
foreach (var splitString in strings)
{
finalItems.Add(items.FirstOrDefault(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString));
}
items = finalItems.ToList().AsQueryable();
return View(items.ToPagedList(pageNumber, pageSize));
}
I want to get all the matching results instead of the first match. Now when I try to change the .FirstOrDefault line to:
finalItems.Add(items.Where(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString)
I get a 'cannot convert from 'System.Linq.IQueryable(MyProject.Models.Items)' to 'MyProject.Models.Items.
I've tried changing my items assignment to:
var items = from s in db.Items
select s as IQueryable(Items);
That seems to fix the issue but then it breaks all my s.keywords.Contains or s.notes.Contains with the error 'IQueryable(Items) does not contain a definition for keywords or notes and no extension method keywords or notes accepting a first argument of type IQueryable(Items) could be found'.
FirstOrDefault() returns only one element of some type(MyProject.Models.Items in your case) or null.
Where() returns collection of items.
Add() method expects MyProject.Models.Items - one element, not collection.
You should use AddRange() to add collection:
finalItems.AddRange(items.Where(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString);
Related
I'm getting following string as result while returning a list of string using lambda expression:
System.Linq.Enumerable+WhereSelectEnumerableIterator`2[HOrg.ServiceCatalog.Contracts.Models.IOfferProperty,System.String]
My code is:
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.Add(offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)
.ToString());
}
Within foreach loop, offer variable contains expected values. But when I make condition using lambda expression, it returns System.Linq.Enumerable+WhereSelectEnumerableIterator`2 as a result.
When I search for this, I got a few suggestions like,
Copying results of lambda expressions in to a seperate list
Use ToList() for lambda expression then assign it to a result variable
and more suggestion. But no answer is helpful for me.
Is anybody know what's wrong in this code?
Instead of converting sequence to String:
// How can .Net convert sequence into string? The only way is to return type name
// which is unreadable System.Linq.Enumerable+WhereSelectEn...
offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)
.ToString()
Join the items into a string
// Join items into string with "; " delimiter, e.g. "1; 2; 3; 4"
offerIds.Add(string.Join("; ", offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)));
If you expect a single result for every offer, try:
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.Add(offer.Where(x => x.PropertyDefinitionId == propertyDefinitionId).Select(x => x.OfferId).FirstOrDefault()?.ToString());
}
It seems to me that you want a collection of offerIds as a string, where multiple are attached to the offerproperties.
If so, then you are looking for the addrange function. Also move your ToString() call inside the select statement, not after it.
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.AddRange(offer.Where(x => x.PropertyDefinitionId == propertyDefinitionId).Select(x => x.OfferId.ToString()));
}
Now for each offer, a selection of OfferId-strings is added to your offerIds IList
I want to select some values from a collection of strings, I wrote it one using LINQ and one using a foreach statement.
With the foreach version I get a list of about 300 entries.
List<string> res = new List<String>();
foreach (var l in anchors)
{
if (l.Attributes["href"] != null)
{
res.Add(l.Attributes["href"].Value);
}
}
With the LINQ version I get null:
IEnumerable<string> res2 = anchors.Select(l => l?.Attributes["href"]?.Value);
With the linq, you're getting null values as well and adding it to your enumerable. It's not identical to your foreach. Change it to:
IList<string> res2 = anchors.Where(l=>l.Attributes["href"] != null).Select(l => l.Attributes["href"].Value).ToList();
The .? syntax returns null if the item it is applied to returns null. In this case, null is added to the output.
With the check if (l.Attributes["href"] != null) it is not added to the output.
To mimic that in LINQ, add a Whereclause.
With the following structure
[[1,10],[2,20],[5,45],[10,34]]
this foreach loop finds the first element that matches "planYear". If planYear=5 then the third element value of "45" would be selected.
List<object> gifts = gifts;
foreach (List<object> item in gifts)
{
if (item[0] == planYear)
{
gift = Convert.ToDouble(item[1]);
break;
}
}
What would be an analogous Linq statement to achieve this same result?
var gift = gifts.Cast<List<object>>()
.Where(x => x[0] == planYear)
.Select(x => Convert.ToDouble(x[1]))
.FirstOrDefault();
If no matching entry has been found gift will be 0. If that's not what you want, use First() instead. This will throw an exception if no matching item exists.
This answer assumes - just like your foreach loop - that every item inside gifts is actually a List<object>. If even one item is of a different type, this code will throw an InvalidCastException. If this is a problem, use OfType instead of Cast.
var gift = Convert.ToDouble(
gifts.Cast<List<object>>().First(x => x[0] == planYear)[1]);
I have an array of strings called "Cars"
I would like to get the first index of the array is either null, or the value stored is empty. This is what I got so far:
private static string[] Cars;
Cars = new string[10];
var result = Cars.Where(i => i==null || i.Length == 0).First();
But how do I get the first INDEX of such an occurrence?
For example:
Cars[0] = "Acura";
then the index should return 1 as the next available spot in the array.
You can use the Array.FindIndex method for this purpose.
Searches for an element that matches
the conditions defined by the
specified predicate, and returns the
zero-based index of the first
occurrence within the entire Array.
For example:
int index = Array.FindIndex(Cars, i => i == null || i.Length == 0);
For a more general-purpose method that works on any IEnumerable<T>, take a look at: How to get index using LINQ?.
If you want the LINQ way of doing it, here it is:
var nullOrEmptyIndices =
Cars
.Select((car, index) => new { car, index })
.Where(x => String.IsNullOrEmpty(x.car))
.Select(x => x.index);
var result = nullOrEmptyIndices.First();
Maybe not as succinct as Array.FindIndex, but it will work on any IEnumerable<> rather than only arrays. It is also composable.
The following LINQ statement:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
return (from t in db.Tasks
where searchTerms.All(term =>
t.Title.ToUpper().Contains(term.ToUpper()) &&
t.Description.ToUpper().Contains(term.ToUpper()))
select t).Cast<Item>().ToList();
}
}
gives me this error:
System.NotSupportedException: Local
sequence cannot be used in LINQ to SQL
implementation of query operators
except the Contains() operator.
Looking around it seems my only option is to get all my items first into a generic List, then do a LINQ query on that.
Or is there a clever way to rephrase the above LINQ-to-SQL statement to avoid the error?
ANSWER:
Thanks Randy, your idea helped me to build the following solution. It is not elegant but it solves the problem and since this will be code generated, I can handle up to e.g. 20 search terms without any extra work:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
switch (searchTerms.Count())
{
case 1:
return (db.Tasks
.Where(t =>
t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0])
)
.Select(t => t)).Cast<Item>().ToList();
case 2:
return (db.Tasks
.Where(t =>
(t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0]))
&&
(t.Title.Contains(searchTerms[1])
|| t.Description.Contains(searchTerms[1]))
)
.Select(t => t)).Cast<Item>().ToList();
case 3:
return (db.Tasks
.Where(t =>
(t.Title.Contains(searchTerms[0])
|| t.Description.Contains(searchTerms[0]))
&&
(t.Title.Contains(searchTerms[1])
|| t.Description.Contains(searchTerms[1]))
&&
(t.Title.Contains(searchTerms[2])
|| t.Description.Contains(searchTerms[2]))
)
.Select(t => t)).Cast<Item>().ToList();
default:
return null;
}
}
}
Ed, I've run into a similiar situation. The code is below. The important line of code is where I set the memberList variable. See if this fits your situation. Sorry if the formatting didn't come out to well.
Randy
// Get all the members that have an ActiveDirectorySecurityId matching one in the list.
IEnumerable<Member> members = database.Members
.Where(member => activeDirectoryIds.Contains(member.ActiveDirectorySecurityId))
.Select(member => member);
// This is necessary to avoid getting a "Queries with local collections are not supported"
//error in the next query.
memberList = members.ToList<Member>();
// Now get all the roles associated with the members retrieved in the first step.
IEnumerable<Role> roles = from i in database.MemberRoles
where memberList.Contains(i.Member)
select i.Role;
Since you cannot join local sequence with linq table, the only way to translate the above query into SQL woluld be to create WHERE clause with as many LIKE conditions as there are elements in searchTerms list (concatenated with AND operators). Apparently linq doesn't do that automatically and throws an expception instead.
But it can be done manually by iterating through the sequence:
public override List<Item> SearchListWithSearchPhrase(string searchPhrase)
{
List<string> searchTerms = StringHelpers.GetSearchTerms(searchPhrase);
using (var db = Datasource.GetContext())
{
IQueryable<Task> taskQuery = db.Tasks.AsQueryable();
foreach(var term in searchTerms)
{
taskQuery = taskQuery.Where(t=>t.Title.ToUpper().Contains(term.ToUpper()) && t.Description.ToUpper().Contains(term.ToUpper()))
}
return taskQuery.ToList();
}
}
Mind that the query is still executed by DBMS as a SQL statement. The only drawback is that searchTerms list shouldn't be to long - otherwise the produced SQL statement won'tbe efficient.