LINQ sentence, check if remain elements - c#

We're new in LINQ and we want to know if the are any method to check if there are elements remaining in the sentence like the ResultSet.next() from Java.
In Java the resultSet return true if there are elements remaining and false if not. We want to know if the is a method like that in LINQ.
public List<Product> FindProductsByKeyword(string productName, Category category, int page, int size)
{
DbSet<Product> products = Context.Set<Product>();
List<Product> result;
if (category == null)
{
result = (from p in products
where p.productName.Contains(productName) //, StringComparison.OrdinalIgnoreCase)
orderby p.productName descending
select p).Skip(page).Take(size).Include("Category").ToList();
}
else
{
result = (from p in products
where p.productName.Contains(productName)
&& p.categoryId == category.id
orderby p.productName descending
select p).Skip(page).Take(size).ToList();
}
return result;
}
This our code( I dont know if this will help) we take elements with a number of Size but we dont know if there are more elements.

Using the terminating method ToList() initiates a DB query and parses the returned table as a simple in-memory List<T> (same as ArrayList<T> in Java).
If you want to check if there are any entries in the result, you can just check the list's Count property:
if (result.Count > 0)
{
// Do something
}
Or use LINQ's Any() extension method, which is a bit more readable in this context:
if (result.Any())
{
// Do something
}

If you returned an Enumerable, then you could use Enumerable.MoveNext()
For example:
var p = products.AsEnumerable();
var i = p.GetEnumerator();
while( i.MoveNext())
i.Current.productfieldname.Dump() // LinqPad .Dump() shows value

Related

Dynamic Linq using Or

I have the following dynamic linq
var results=(from a in alist where a.id==id select a)
if(...something)
{
results=(from a in results where a.amount>input1 && a.typeId==1 select a)
}
if(...something else)
{
results=(from a in results where a.amount>input2 && a.typeId==2 select a)
}
if(...something else again)
{
results=(from a in results where a.amount>input3 && a.typeId==3 select a)
}
However this produces an AND statement which means all the statements need to be true for anything to be returned.
I need the last 3 statements to be ORed together.
eg I want
Where (a.id==id) && ((a.amount>input1 && a.typeId==1) ||
(a.amount>input2 && a.typeId==2) || (a.amount>input3 && a.typeId==3))
How do I do this?
Check the PredicateBuilder class. This is a famous implementation of extensions methods for Linq to easily perform dynamic logic operations with OR and AND.
Given your list is of a TypeA for sample, you coul try this:
Expression<Func<TypeA, bool>> filter = a => a.id == id;
if(...something)
{
filter = filter.Or(a => a...);
}
if(...something)
{
filter = filter.Or(a => a...);
}
if(...something)
{
filter = filter.Or(a => a...);
}
var results = alist.Where(filter).ToList();
Use .Concat()
I am not absolutely sure if I understood you question correctly, but this code will create a resultset that is appended to if your if conditions are true, rather than replacing the original resultset.
var results=(from a in alist where a.id==id select a)
if(...something)
{
results = results.Concat((from a in alist where a.amount>input1 && a.typeId==1 select a))
}
if(...something else)
{
results = results.Concat((from a in alist where a.amount>input2 && a.typeId==2 select a))
}
//....
Edited as per Peter B's comment.
If multiple lists may contain the same element and you only wish to have every element at most once, use .Union instead of .Concat. This has some performance penalty of course (having to compare the elements).
After your edit
Your edit clarified things a bit. You have two options:
Move your a.id == id check into the inner queries:
var results=Enumerable.Empty<typeofa>()
if(...something)
{
results = results.Concat((from a in alist where a.id == id && a.amount>input1 && a.typeId==1 select a))
}
if(...something else)
{
results = results.Concat((from a in alist where a.id == id && a.amount>input2 && a.typeId==2 select a))
}
//....
First filter the set using the id, materialize that, then further narrow that down using the method I showed above.
var results=Enumerable.Empty<typeofa>();
var fileterdList = (from a in alist where a.id==id select a).ToList();
if(...something)
{
results = results.Concat((from a in fileterdList where a.amount>input1 && a.typeId==1 select a))
}
if(...something else)
{
results = results.Concat((from a in fileterdList where a.amount>input2 && a.typeId==2 select a))
}
//....
Whichever works better depends on your situation. General advice is that prefiltering is more efficient if it narrows down the list considerably and/or the original source is relatively expensive to query (sql for example), but as always, you should profile your concrete example yourself.

How to create dynamic Multiple And linq conditions in foreach loop

if (rowCount == 1)
{
query =
(from x in partJoinTableRepository.GetPartJoinQuery()
join y in partRepository.GetPartsQuery() on x.PartId equals y.Id
join z in partProductTypeReposiotry.GetPartProductTypesQuery() on x.PartId equals z.PartId
where y.IsSkipped == 0 && (y.IsDisabled != "Y" || y.IsDisabled == null) && z.CreatedDate == x.CreatedDate
&& x.CreatedDate == Convert.ToDateTime(fromDate) && cpaclassids.Contains(x.ProductTypeId.ToString())
select x).Cast<PartJoinTable>().AsQueryable();
predicate = PredicateBuilder.True(query);
}
else
{
query = query.Join(partJoinTableRepository.GetPartJoinQuery(), "PartID", "PartID", "inner", "row1", null).Cast<PartJoinTable>().AsQueryable();
// predicate = PredicateBuilder.True(query);
} //query contains multiple dynamic inner joins
//repids contains the list ,I used the predicate builder for the linq to create AND Queries
foreach(var item in repids)
{
predicate = PredicateBuilder.True(query);
if (typeid == "3")
{
predicate = predicate.And(z => ids.Contains(z.ProductTypeId.ToString()) &&
z.CreatedDate == Convert.ToDateTime(fromDate));
}
}
var count = query.Where(predicate).Distinct().Count();
the above line is taking long time to execute,ids contains the lists and query contains the linq query.basically I need to form a multiple "AND" conditions
//The Query is taking lot of time to execute and multiple and conditions are not working
Remove ToList to improve performance. Because ToList execute your query and retrieve object list to memory. But you need only count. you don't need objects.
var count = query.Where(predicate).Distinct().Count();
If I understood you right, your problem is that this query has a long running time. Let's see your code in the last line:
var count = query.Where(predicate).Distinct().ToList().Count();
In LINQ to SQL (and to entities), your query doesn't execute thought you use ToList(), ToArray() etc.. For example, consider the following query:
var strings = Db.Table
.Where((string s) => s.Contains("A")) // Will convert to something like WHERE s LIKE '%A%'
.Select(s => s.ToUpper()) // Will convert to something like SELECT upper(s)
.ToList(); // Here the query sends to the DB and executes
The final query is SELECT upper(s) FROM [Table] WHERE s LIKE '%A%'.
In you case, first you send the query to the DB and get all the objects corresponding to the condition (.Where()), and then get their count inside your app.
Instead, if you'll get from the DB only the count, the query will be faster:
var count = query.Where(predicate).Distinct().Count(); // No .ToList()! Here, .Count() executes the query.

Select top N elements and remember occurence's order

I have a requirement to select top N elements of related products from a big list of products.
So far, I have below code and it works perfectly.
class Product
{
public string Name;
public double Rating;
public List<Product> RelatedProducts;
public List<Product> GetTopRelatedProducts(int N)
{
var relatedSet = new HashSet<Product>();
var relatedListQueue = new Queue<List<Product>>();
if (RelatedProducts != null && RelatedProducts.Count > 0)
relatedListQueue.Enqueue(RelatedProducts);
while (relatedListQueue.Count > 0)
{
var relatedList = relatedListQueue.Dequeue();
foreach (var product in relatedList)
{
if (product != this && relatedSet.Add(product) && product.RelatedProducts != null && product.RelatedProducts.Count > 0)
relatedListQueue.Enqueue(product.RelatedProducts);
}
}
return relatedSet.OrderByDescending(x => x.Rating).Take(N).OrderBy(/*How to order by occurrence here? */).ToList();
}
}
Now, I want GetTopRelatedProducts method to remember the occurrence order of top N products. First added product to the HashSet will be at the begining of the returned List.
For example, if I have this scenario:
//...
relatedSet.Add(new Product(){Name="A", Rating=3});
relatedSet.Add(new Product(){Name="B", Rating=4});
relatedSet.Add(new Product(){Name="C", Rating=5});
//...
and if N = 2, the method should return : B,C instead of C,B because B was added first to the HashSet.
So I changed the return statement in the method to:
var relatedSetCopy = relatedSet.ToList();
return (from p in relatedSet.OrderByDescending(x => x.Rate).Take(N)
join c in relatedSetCopy on p.Name equals c.Name
let index = relatedSetCopy.IndexOf(c)
orderby index
select p).ToList();
Basically, I use LINQ Join to re-order the list in the same way it was before the ordering on Rating.
I want to do it this way because first added product has more similarity with selected product than others.
I have two questions here:
Is there a better way to re-order the returned list?
Is there a better design to handle relation between products? (I was thinking about implementing a tree structure. So object navigation and retrieval will be faster)
Is there a better way to re-order the returned list?
You can simply Intersect the relatedSet with the top N related reordered set because Intersect yields the items based on their order in the first sequence.
So instead of
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
you would use
return relatedSet.Intersect(relatedSet.OrderByDescending(x => x.Rating).Take(N)).ToList();

Linq return string array

/// <summary>
/// Returns list of popular searches
/// </summary>
public static string[] getPopularSearches(int SectionID, int MaxToFetch)
{
using (MainContext db = new MainContext())
{
return (from c in db.tblSearches where c.SectionID == SectionID && c.Featured select new[] { c.Term });
}
}
I looked at other questions but they seem to be slightly different, I get the error:
Cannot implicitly convert type 'System.Linq.IQueryable<string[]>' to 'string[]'
I know this is probably simple, could someone point out what's wrong here please?
Sure - you're trying to return from a method declared to return a string[], but you're returning a query - which isn't a string in itself. The simplest way of converting a query to an array is to call the ToArray extension method.
However, as you're already selecting a string array for every element in the query, that would actually return string[][]. I suspect you really want to select a single string per query element, and then convert the whole thing into an array, i.e. code like this:
public static string[] GetPopularSearches(int sectionID, int maxToFetch)
{
using (MainContext db = new MainContext())
{
var query = from c in db.tblSearches
where c.SectionID == sectionID && c.Featured
select c.Term;
return query.Take(maxToFetch)
.ToArray();
}
}
Note that:
I've renamed the method and parameters to match .NET naming conventions
I've added a call to Take in order to use the maxToFetch parameter
You are attempting to return an unmaterialized query. The query is only evaluated when it is enumerated. Luckily for you, the ToArray method take the pain out of enumerating and storing. Simply adding it to the end of your query should fix everything.
return (
from c in db.tblSearches
where c.SectionID == SectionID && c.Featured
select new[] { c.Term }
).ToArray();
EDIT
Looking in more detail, perhaps:
return (
from c in db.tblSearches
where c.SectionID == SectionID && c.Featured
select new[] { c.Term }
).SelectMany(x => x).ToArray();
to flatten the results of your query, or even (less redundantly):
return (
from c in db.tblSearches
where c.SectionID == SectionID && c.Featured
select c.Term
).ToArray();
Add .ToArray() at the end of the return statement.

LINQ return items in a List that matches any Names (string) in another list

I have 2 lists. 1 is a collection of products. And the other is a collection of products in a shop.
I need to be able to return all shopProducts if the names match any Names in the products.
I have this but it doesn't seem to work. Any ideas?
var products = shopProducts.Where(p => p.Name.Any(listOfProducts.
Select(l => l.Name).ToList())).ToList();
I need to say give me all the shopproducts where name exists in the other list.
var products = shopProducts.Where(p => listOfProducts.Any(l => p.Name == l.Name))
.ToList();
For LINQ-to-Objects, if listOfProducts contains many items then you might get better performance if you create a HashSet<T> containing all the required names and then use that in your query. HashSet<T> has O(1) lookup performance compared to O(n) for an arbitrary IEnumerable<T>.
var names = new HashSet<string>(listOfProducts.Select(p => p.Name));
var products = shopProducts.Where(p => names.Contains(p.Name))
.ToList();
For LINQ-to-SQL, I would expect (hope?) that the provider could optimise the generated SQL automatically without needing any manual tweaking of the query.
You could use a join, for example:
var q = from sp in shopProducts
join p in listOfProducts on sp.Name equals p.Name
select sp;
A fuller guide on join is here.
You could create an IEqualityComparer<T> that says products with equal names are equal.
class ProductNameEqulity : IEqualityComparer<Product>
{
public bool Equals(Product p1, Product p2)
{
return p1.Name == p2.Name
}
public int GetHashCode(Product product)
{
return product.Name.GetHashCode();
}
}
Then you can use this in the Intersect extension method.
var products = shopProducts.Intersect(listOfProducts, new ProductNameEquality());
Try this please
var products = shopProducts.Where(m=> listOfProducts.Select(l=>l.Name).ToList().Contains(m=>m.Name));
var products = shopProducts
.Where(shopProduct =>
listOfProducts.Any(p => shopProduct.Name == p.Name))
.ToList();

Categories