linq query must return a string - c#

I use HtmlAgilityPack to get data from a website for a hobby project. I want to get the shoe article number from a site that sells shoes.
But my linq query doesn't return a string. Instead it returns the type:
System.Linq.Enumerable.WhereSelectEnumerableIterator<HtmlAgilityPack.HtmlNode,string>
How can I get the query to simply return a string?
foreach (var node in query)
{
Shoe shoe = new Shoe();
var num = from x in node.Descendants()
where x.Name == "img" && x.Attributes.Contains("class") && x.Attributes["class"].Value == "thumb lazy"
select x.Attributes["title"].Value.Substring(11);
shoe.articleNumber = Convert.ToInt32(num); //error
shoes.Add(shoe);
}
The error: InvalidCastException was unhandled.
Unable to cast object of type
'WhereSelectEnumerableIterator`2[HtmlAgilityPack.HtmlNode,System.String]'
to type 'System.IConvertible'.

Your LINQ query returns a collection. Use First/FirstOrDefault/Single/SingleOrDefault to get only one element from the collection:
var num = (from x in node.Descendants()
where x.Name == "img" && x.Attributes.Contains("class") && x.Attributes["class"].Value == "thumb lazy"
select x.Attributes["title"]).First().Value.Substring(11);

Assuming there's only going to be one entry that matches your query, you just need to modify it to use FirstOrDefault() or First():
foreach (var node in query)
{
Shoe shoe = new Shoe();
var num = (from x in node.Descendants()
where x.Name == "img" && x.Attributes.Contains("class") && x.Attributes["class"].Value == "thumb lazy"
select x.Attributes["title"].Value.Substring(11)).First();
shoe.articleNumber = Convert.ToInt32(num);
shoes.Add(shoe);
}
Just bear in mind that the above will fail with an exception if the item is not present.

your query does not return a single result but a list of results. In your case this will be a list containing a single id. You should modify it to this:
var num = (from x in node.Descendants()
where x.Name == "img" && x.Attributes.Contains("class") && x.Attributes["class"].Value == "thumb lazy"
select x.Attributes["title"].Value.Substring(11)).FirstOrDefault();
Difference between single, singleorDefault ,first ,FirstorDefault
Here

Related

How do optimize nested loop and filter from another list using linq c#

I am trying to filter a flattened nested loop and filter from another list. So below is what I have been able to do. I tried this approach but when I run the query it does not save anything in the database. I need help to check and ignore existing records (same MemberId and Description) in the Tasklist table.
var addedtop = from a in db.TaskLists select new {a.MemberId,a.Description};
var membtasks = from m in members
from n in tasks
select new { m, n };
TaskList taskList = new TaskList();
foreach (var item in membtasks)
{
var exist = db.TaskLists.Where(a => a.Description == item.n && a.MemberId == item.m);
if(exist == null)
{
taskList .Description = item.n;
taskList .MemberId = item.m;
db.taskList .Add(taskList );
db.SaveChanges();
}
}
return Redirect(url);
The problem is exists will never be null. The line var exist = db.TaskLists.Where(a => a.Description == item.n && a.MemberId == item.m); returns an IQueryable that describes your query but hasn't actually executed against the database yet.
Try changing the line to:
var exist = db.TaskLists.Where(a => a.Description == item.n && a.MemberId == item.m).SingleOrDefault();
This executes the query and checks if there is a single item that satisfies your query. If there is no result at all the query returns null which will execute the code inside your if statement.
Your statement has not been executed query, you can change Where to Any like this:
var exist = db.TaskLists.Any(a => a.Description == item.n && a.MemberId == item.m);
Any function that returns data type is bool

Linq Query using navigation properties and Where clause

I am trying to compose a linq query using navigation properties. I am selecting properties from 3 entities:
Lockers
SectionColumn
Contracts
I require ALL rows from the Lockers table where all the following conditions are met: the LockerTypeId = "308", .OutOfOrder != true, x.SectionColumn.SectionId == "52").
The query below without the condition x.SectionColumn.SectionId == "52" works and returns exactly what I require except rows with Section id of any value are returned as I would expect.
from l in Lockers.Where(x => x.LockerTypeId == "308" && x.OutOfOrder !=
true).DefaultIfEmpty()
select new
{
ColumnNumber = l.ColumnNumber,
LockerTypeId = l.LockerTypeId,
OutOfOrder = l.OutOfOrder,
Rented = l.Contracts.Select(x => x.Contract_ID < 0 ?
false : true).FirstOrDefault(),
Section = l.SectionColumn.SectionId
}
When I add the condition 'x.SectionColumn.SectionId == "52"' as below I get the error "The cast to value type 'System.Int32' failed because the materialized value is null". Either the result type's generic parameter or the query must use a nullable type" in linqpad. SectionId is a string (varchar in SQL Server).
from l in Lockers.Where(x => x.LockerTypeId == "308" && x.OutOfOrder !=
true).DefaultIfEmpty()
I would be grateful for assistance in correctly writing this query.
First off, your code might be a little more straight forward if you stick to pure LINQ. In that case, your code should look something like the following.
var results = from l in Lockers
where l.LockerTypeId == "308" && l.OutOfOrder != true && l.SectionColumn.SectionId == "52"
select new
{
ColumnNumber = l.ColumnNumber,
LockerTypeId = l.LockerTypeId,
OutOfOrder = l.OutOfOrder,
Rented = l.Contracts.Select(x => x.Contract_ID < 0 ? false : true).FirstOrDefault(),
Section = l.SectionColumn.SectionId
}
If l.SectionColumn.SectionId represents valid navigational properties and is of type string, then this should work correctly.
You really haven't done a thorough job of describing the issue (and it looks like you didn't stick around to field questions), but if l.SectionColumn is nullable, you should be able to update your code to something like this.
var results = from l in Lockers
let sectionId = (l.SectionColumn != null) ? l.SectionColumn.SectionId : null
where l.LockerTypeId == "308" && l.OutOfOrder != true && sectionId == "52"
select new
{
ColumnNumber = l.ColumnNumber,
LockerTypeId = l.LockerTypeId,
OutOfOrder = l.OutOfOrder,
Rented = l.Contracts.Select(x => x.Contract_ID < 0 ? false : true).FirstOrDefault(),
Section = l.SectionColumn.SectionId
}

Filtering results with Linq

I have two dropdowns on my page. First dropdown shows Authors for books and the other dropdown shows status's i.e Overdue or All.
If they choose Overdue then I need to return all books that have been borrowed more than a week ago so the dueback date (Datetime variable) will be taken into consideration.
I currently have this working correctly filtering on the Author as shown here:
model.ListBooks = (from x in tempModel
where ((x.BookAuthor == model.ListAuthors.SelectedAuthor || model.ListAuthors.SelectedAuthor == null))
select x).ToList(); // Filter the results
But as soon as I pass in the additional search filter I.e status it fails to shows me any books that match the selected Author even though I haven't chosen a status this is what it currently looks like.
model.ListBooks = (from x in tempModel
where (
(x.BookAuthor == model.ListAuthors.SelectedAuthor || model.ListAuthors.SelectedAuthor == null)
&&
(model.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue && x.DueBack < DateTime.Now.)
)
select x).ToList(); // Filter the results
Can someone see what I'm doing wrong here?
I think your query fails if selected status is not OverDue. In that case you have
where authorFilter && (false && dateFilter)
that gives you false for all books. Thus you have only two statuses, you can just add status.SelectedStatusId != (int)Enums.Registration.OverDue check just as you did with null-check for selected author:
var authors = model.ListAuthors;
var status = model.BookStatus;
model.ListBooks = (from x in tempModel
where (authors.SelectedAuthor == null || x.BookAuthor == authors.SelectedAuthor) &&
(status.SelectedStatusId != (int)Enums.Registration.OverDue || x.DueBack < DateTime.Now)
select x).ToList();
I would use method syntax here to make query more readable:
var books = tempModel; // probably you will need IEnumerable<T> or IQueryable<T> here
if (model.ListAuthors.SelectedAuthor != null)
books = books.Where(b => b.BookAuthor == model.ListAuthors.SelectedAuthor);
if (model.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue)
books = books.Where(b => b.DueBack < DateTime.Now);
model.ListBooks = books.ToList();
Here:
(model.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue && x.DueBack < DateTime.Now.)
Should be instead:
(x.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue && x.DueBack < DateTime.Now.)
Because You like to compare element of LINQ query, not the model.

Using Linq to filter by List<ListItem>

I am trying to extend my linq query with additional search criteria to filter the data by sending also a List<Listitem> to the function for processing. The List can contain 1 or more items and the objective is to retreive all items which match any criteria.
Since i am sending several search criteria to the function the goal is to make a more accurate filter result the more information i am sending to the filter. If one or several criterias are empty then the filter will get less accurate results.
Exception is raised every time i execute following code, and I cant figure out how to solve the using statement to include the List<ListItem>. Appreciate all the help in advance!
Exception: Unable to create a constant value of type 'System.Web.UI.WebControls.ListItem'. Only primitive types or enumeration types are supported in this context.
using (var db = new DL.ENTS())
{
List<DL.PRODUCTS> products =
(from a in db.PRODUCTS
where (description == null || description == "" ||
a.DESCRIPTION.Contains(description)) &&
(active == null || active == "" || a.ACTIVE.Equals(active, StringComparison.CurrentCultureIgnoreCase)) &&
(mID == null || mID == "" || a.MEDIA_ID == mID) &&
(mID == null || objTypes.Any(s => s.Value == a.OBJECTS)) //Exception here!
select a).ToList<DL.PRODUCTS>();
return products;
}
Pass collection of primitive values to expression:
using (var db = new DL.ENTS())
{
var values = objTypes.Select(s => s.Value).ToArray();
List<DL.PRODUCTS> products =
(from a in db.PRODUCTS
where (description == null || description == "" || a.DESCRIPTION.Contains(description)) &&
(active == null || active == "" || a.ACTIVE.Equals(active, StringComparison.CurrentCultureIgnoreCase)) &&
(mID == null || mID == "" || a.MEDIA_ID == mID) &&
(mID == null || values.Contains(a.OBJECTS))
select a).ToList<DL.PRODUCTS>();
return products;
}
That will generate SQL IN clause.
Note - you can use lambda syntax to compose query by adding filters based on some conditions:
var products = db.PRODUCTS;
if (!String.IsNullOrEmpty(description))
products = products.Where(p => p.DESCRIPTION.Contains(description));
if (!String.IsNullOrEmpty(active))
products = products.Where(p => p.ACTIVE.Equals(active, StringComparison.CurrentCultureIgnoreCase)));
if (!String.IsNullOrEmpty(mID))
products = products.Where(p => p.MEDIA_ID == mID);
if (mID != null)
products = products.Where(p => values.Contains(p.OBJECTS));
return products.ToList();
Linq isn't able to convert the predicate on ListItem to something useful to Sql.
I would suggest that you pre-project the values of the ListItems into a simple List<string> before using this with Contains (which is converted to IN)
var listValues = objTypes.Select(_ => _.Value).ToList();
List<DL.PRODUCTS> products = ...
listValues.Contains(a.OBJECTS))

Using variables in the lambda

I am using Linq to filter some things and some times I need to use reflection to get the value. Here is the example:
//...
PropertyType[] properties = myType.GetProperties();
var filtered = properties.Where(p=>p.PropertyType==typeof(MyMetaData)
&& ((MyType)p.GetValue(obj)).Name=="name"
&& ((MyType)p.GetValue(obj)).Length==10
).ToList();
//...
In my example I am using GetValue() method more than one time. Is there way if I can use variable to store it? I think that will help with performance.
It looks like that to include some variable in a LINQ we have to use the expression query, not method query, like this:
var filtered = (from x in properties
let a = (x.PropertyType is MyType) ? (MyType) x.GetValue(obj) : null
where a != null && a.Name == "name" && a.Length == 10).ToList();
I think this also works for method query with some Select:
var filtered = properties.Select(p=> new {p, a = (p.PropertyType is MyType) ? (MyType) p.GetValue(obj) : null})
.Where(x=>x.a != null && x.a.Name == "name" && x.a.Length == 10)
.Select(x=>x.p).ToList();
Something like following should work with method expression(lambda syntax)
var filtered = properties.Where(p =>
{
if(!p.PropertyType is MyMetaData)
{
return false;
}
var item = (MyType) p.GetValue(obj);
return item.Name == "name"
&& item.Length == 10
}
).ToList();

Categories