So I have a search-input and checkboxes that passes the values to the controller when there are inputs. And I want to use these values to get something back from the database. The search-input is a string and it works and intended. Here is the code for the search-input:
public async Task<ViewResult> Index(string searchString, List<int> checkedTypes)
{
var products = from p in _db.Products select p;
ViewData["CurrentFilter"] = searchString;
if (!string.IsNullOrEmpty(searchString))
{
products = products.Where(p => p.Name.ToLower().Contains(searchString));
}
return View(products);
}
However the checkboxes values are stored in a list. So basically I want to do the same as the code above, but with a list. So basically an idea is like this:
if(checkedTypes != null)
{
foreach (var i in checkedTypes)
{
products = products.Where(p => p.TypeId == i));
}
}
If I do it like the code above, I just get the last (i) from the loop. Another solution I did was this:
if(checkedTypes != null)
{
var temp = new List<Product>();
foreach (var i in checkedTypes)
{
temp.AddRange(products.Where(p => p.TypeId == i));
}
products = temp.AsQueryable();
}
But when I did it like that I get this error:
InvalidOperationException: The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IAsyncQueryProvider can be used for Entity Framework asynchronous operations.
So anyone have a solution that I can use? Or is there a better way to handle checkboxes in the controller?
Assuming you are using EF Core (also the same is true for linq2db) - it supports translating filtering with local collection, i.e. Where(x => checkedTypes.Contains(x.SomeId)).
If you have "and" logic to filter by searchString and checkedTypes than you can conditionally add Where clause:
if (!string.IsNullOrEmpty(searchString))
{
products = products.Where(p => p.Name.ToLower().Contains(searchString));
}
if(checkedTypes != null)
{
products = products.Where(p => checkedTypes.Contains(p.TypeId));
}
P.S.
Also you should be able to change your first line to:
var products = _db.Products.AsQueryable();
Related
So here is the summary:
Sitecore - SOLR index query
I have items that I am trying to retrieve using a set of sites that can vary.
I have a query:
query = query.Where(x => x.Language == this.ItemLanguage)
.Where(x => x.Templates.Contains(new Guid("94c1f3e5ac174a319cc5bbb942fe80c6")));
this will return all of the items correctly.
What I need is to add a dynamic list call for this query.
Something like:
.Where(x => x.Site.Any(y => siteNames.Contains(y)));
I have tried adding this line of code and I get an error:
System.ArgumentException: 'Argument must be array'
(some details)
Items returned (x) have a field "Site" of List<string>
siteNames is a List<String> of variable site names
The following code works in other places:
.Where(x => x.Site.Contains("somesite"));
Is there a way to manage a dynamic list or will I need to manually generate this expression based on the number of items in the siteNames list?
.Where(x => x.Site.Contains("dynamic") || x.Site.Contains("dynamic")
|| x.Site.Contains("dynamic") || x.Site.Contains("dynamic")
|| and so on);
Here is the full code example:
using (var context = Sitecore.ContentSearch.ContentSearchManager.GetIndex(AccelConstants.SEARCH_INDEX).CreateSearchContext())
{
IQueryable<SearchResultModel> query = context.GetQueryable<LMSSearchResultModel>();
SearchResults<SearchResultModel> results = null;
List<string> siteNames = new List<string>();
siteNames = this.SiteNames;
// Define the base search
query = query.Where(x => x.Language == this.ItemLanguage)
.Where(x => x.Templates.Contains(new Guid("94c1f3e5ac174a319cc5bbb942fe80c6")))
.Where(x => x.Site.Any(p => siteNames.Contains(p)));
// Execute the query
results = query.GetResults();
}
the site field is a solr field of Site_SM which outputs like this:
The reason the name "Site" is used is that Sites is also a field.
This is not code that I have control over so I am working with what I have.
"site_sm":["login",
"admin",
"service",
"covid19",
"scheduler",
"system",
"publisher"],
The search results model simply converts computed solr fields to c#
public class SearchResultModel : SearchResultItem
{
[IndexField("_templates")]
public List<Guid> Templates { get; set; }
[IndexField("site_sm")]
public List<string> Site { get; set; }
}
Use Predicate Builder to create a dinamic OR (I just use it to fix this issue).
Sitecore have a implementation for this is:
//using Sitecore.ContentSearch.Linq.Utilities
private IQueryable<LMSSearchResultModel> LimitSearchBySite(IQueryable<LMSSearchResultModel> query, IEnumerable<string> sites)
{
if (sites != null && sites.Any())
{
// https://www.albahari.com/nutshell/predicatebuilder.aspx
var predicate = PredicateBuilder.False<StoreLocatorResult>();
foreach (string s in site)
predicate = predicate.Or(p => p.Site.Contains(s));
return query.Where(predicate);
}
return query;
}
if you are using Solr and C# directly the best way is to use this site as reference:
https://www.albahari.com/nutshell/predicatebuilder.aspx
At the end is the same development I did but you need to add the PredicateBuilder class to your project
So this will be what you need to replace:
using (var context = Sitecore.ContentSearch.ContentSearchManager.GetIndex(AccelConstants.SEARCH_INDEX).CreateSearchContext())
{
IQueryable<SearchResultModel> query = context.GetQueryable<LMSSearchResultModel>();
SearchResults<SearchResultModel> results = null;
List<string> siteNames = new List<string>();
siteNames = this.SiteNames;
// Define the base search
query = query.Where(x => x.Language == this.ItemLanguage)
.Where(x => x.Templates.Contains(new Guid("94c1f3e5ac174a319cc5bbb942fe80c6")));
query = LimitSearchBySite(query, siteNames);
// Execute the query
results = query.GetResults();
}
OK, so here is a rather hacked way of making my search work.
using (var context = Sitecore.ContentSearch.ContentSearchManager.GetIndex(AccelConstants.SEARCH_INDEX).CreateSearchContext())
{
IQueryable<SearchResultModel> query = context.GetQueryable<LMSSearchResultModel>();
SearchResults<SearchResultModel> results = null;
List<string> siteNames = new List<string>();
siteNames = this.SiteNames;
// Define the base search
// remove site filtering from query
query = query.Where(x => x.Language == this.ItemLanguage)
.Where(x => x.Templates.Contains(new Guid("94c1f3e5ac174a319cc5bbb942fe80c6")));
// Execute the query
results = query.GetResults();
// Get the results
foreach (var hit in results.Hits)
{
//move site filter code to here
if (hit.Document != null && hit.Document.Site.Any(p => siteNames.Contains(p)))
{
// Add the model to the results.
}
else
{
}
}
}
By moving the site filter code to after the query, the Site List<string> is instantiated and has value at the time of the call. Which seems to have been my issue.
This then allows me to filter by the sites in the siteNames List<string> and get my final result set.
I don't know if this is the best way of making this section of code work but it does work.
I ran across this exact problem and found this StackOverflow answer searching for the error. It turns out you have to take the error literally and change the List<string> Site to a string[] Site in order to use .Any() in a predicate.
public class SearchResultModel : SearchResultItem
{
[IndexField("_templates")]
public List<Guid> Templates { get; set; }
[IndexField("site_sm")]
public string[] Site { get; set; }
}
I have a table named dbo.EmployeeType with three records:
PK_EmployeetypeID EmployeeTypeName
1 Project Manager
2 Business Analyst
3 Developer
I have this piece of Linq code:
public static string GetTypeByID(int id)
{
using (ProjectTrackingEntities1 db = new ProjectTrackingEntities1())
{
var type = db.EmployeeTypes.Select(o => new LOOKUPEmployeeType
{
PK_EmployeeTypeID = id,
EmployeeTypeName = o.EmployeeTypeName
});
return type.FirstOrDefault().EmployeeTypeName;
}
}
No matter what id I send to it, it returns Project Manager, and I'm confused as to why.
You need to apply a filter, otherwise you're just returning the first record and hard coding the ID. Try this:
public static string GetTypeByID(int id)
{
using (ProjectTrackingEntities1 db = new ProjectTrackingEntities1())
{
//Here we apply a filter, the lambda here is what creates the WHERE clause
var type = db.EmployeeTypes
.FirstOrDefault(et => et.PK_EmployeeTypeID == id);
if(type != null)
{
return type.EmployeeTypeName;
}
else
{
return "";
}
}
}
Note that using FirstOrDefault means if there are no matches, or multiple matches, type will be null and you will get an empty string returned.
Set a breakpoint on type = ... and inspect it. You have no Where in there so you get all - and Select just makes LOOKUPEmployeeTypes out of all of them.
FirstOrDefault then returns the first of those 3 which is always the ProjManager
Fix:
var type = db
.EmployeeTypes
.Where( o => o.Id == id)
.Select(o => new LOOKUPEmployeeType
{
PK_EmployeeTypeID = id,
EmployeeTypeName = o.EmployeeTypeName
});
In your code you only return the first value. You need to tell EF which value you need to return.
Let us assume you need the value with Id=2. Instead of Select(), use Single(x => x.Id == 2) or First(x => x.Id == 2).
I need to make a query like getUsers (list IDs);
I need to search by ids, if found then return users with ids in list, if not found then return all users, or if found only one return one user.
How can I write this query?
This is how I started:
public List<User> getUsers (List<int> ids)
{
using(var uow = _repository.CreateUnitOfWork())
{
var u = uow.GetEntities<User>().Where(c => c.Id in ids);
if (u == null)
u.ToList();
}
}
Your question doesn't make sense because the return type of a function must always be the same. It cannot return a List some of the time and a User the rest of the time.
I would suggest something like this:
public IEnumerable<User> GetUsersByIdOrAllUsers(IEnumerable<int> ids)
{
using (var uow = _repository.CreateUnitOfWork())
{
var users = uow.GetEntities<User>();
if (users.Any(c => ids.Contains(c.ID)))
{
return users.Where(c => ids.Contains(c.ID));
}
return users;
}
}
You can then test if you only found one user:
var matchingUsers = GetUsersByIdOrAllUsers(ids);
if (matchingUsers.Any() && !matchingUsers.Skip(1).Any())
{
var singleUser = matchingUsers.Single();
// Do stuff with the single user...
}
Note that the use of IEnumerable makes operations lazy, and therefore more efficient. If you really want a List, just do:
var matchingUsersList = matchingUsers.ToList()
Assuming uow.GetEntities<User>() returns IQueryable<User>, when there are users with ids from the list, the proposed solutions involve executing two expensive IN (...) SQL queries, also building and passing the ids list twice to the database - one for Any and one for Where.
I would rather structure it differently. I would execute a query with Where based on ids.Contains and materialize the result in memory. Then I would check locally if it contains data, and if yes, will return the result, otherwice will execute a second query without filter, which should be much more efficient.
Something like this:
public List<User> getUsers (List<int> ids)
{
using(var uow = _repository.CreateUnitOfWork())
{
var allUsers = uow.GetEntities<User>();
var matchingUsers = allUsers
.Where(user => ids.Contains(user.Id))
.ToList();
return matchingUsers.Any() ? matchingUsers : allUsers.ToList();
}
}
To recap, you cannot do what you want with a single database query. It requires executing at least two queries and the only question is to use the most efficient approach.
a simple way is to use Join
public List<User> getUsers (List<int> ids)
{
using(var uow = _repository.CreateUnitOfWork())
{
var u = uow.GetEntities<User>()
.Join(ids,x=>x.Id,y=>y,(x,y)=>x).ToList();
if (u.Count==0)
return uow.GetEntities<User>().ToList()
return u;
}
}
Not sure if there's a less chatty way to do it, but logically maybe something like this?:
if (uow.GetEntities<User>().Any(u => ids.Contains(u.ID))
return uow.GetEntities<User>().Where(u => ids.Contains(u.ID)).ToList();
return uow.GetEntities<User>().ToList();
I'm assuming here that uow.GetEntities<User>() simply returns a queryable and doesn't itself materialize anything from the database or have any significant performance penalty. If that's not the case, the code for this operation may need to be placed deeper in the DAL.
public IList<User> getUsers (List<int > ids = null)
{
var query = _repository.GetEntities<User>();
if (ids == null)
return query.ToList();
if (ids.Count()==1)
{
var singleUser = query.FirstOrDefault(user => ids.Contains(user.Id));
if (singleUser!= null)
return new List<User>{ singleUser; };
return new List<User>();
}
return query.Where(user => ids.Contains(user.Id)).ToList();
}
I have a loop inside my program, which loops through thousands of object to find the right one with particular id.
is there any better and faster way than this
int id;
SPList list = SPContext.Current.Web.Lists.TryGetList("DataLibrary");
IEnumerable<SPListItem> _dataitems = list.Items.OfType<SPListItem>();
foreach (SPListItem item in _dataextantitems)
{
if (item.ID == id)
{
title= item.Title;
}
}
Use the GetItemById of SPList.
var title = SPContext.Current.Web.Lists["DataLibrary"].GetItemById(id).Title;
If your list has a lot of columns, and you want to avoid pulling them all down, you can pull down just the Title column instead:
var title = SPContext.Current.Web.Lists["DataLibrary"]
.GetItemByIdSelectedFields(id, "Title").Title;
Now if you really want to use LINQ here you could use LINQ to Sharepoint, but it's not actually going to simplify the code a ton. After using SPMetal.exe to generate a file based on your lists, you'd be able to write:
using(var context = new YourContextNameHere(SPContext.Current.Site.Url))
{
var title = context.DataLibrary
.Where(item => item.ID == id)
.Select(item => item.Title)//to avoid pulling down other columns
.First();
}
Make sure your list is sorted. Then you can use the BinarySearch method of the list or write your own implementation. If not you can shorten your code using linq.
var itemToLookup = list.Items.OfType<SPListItem>().FirstOrDefault(x => x.ID == id);
if (itemToLookup != null)
{
//...
}
i'm sorry if this question has already been asked, but i'm in trouble with my method of updating collection in Entity Framework.
Let me explain the situation :
- I have for example one model CUSTOMER with some properties and a collection of ORDERS (for example).
- Let's imagine we have an admin page on wich we can edit all the ORDERS for a customer, and when we submit the form, it will send us back the object CUSTOMERS with updated ORDERS (some added, some updated and some deleted).
For the moment i use something like this in order to compare old collection and new collection and determine which object i need to delete/update/add
var toRemove = new List<ORDERS>();
var toAdd = new List<ORDERS>();
foreach (
var order in
oldList.Where(
order =>
newList.FirstOrDefault(t => t.link_id == order.link_id) == null))
{
toRemove.Add(order);
}
foreach (
var order in
newList.Where(
order =>
oldList.FirstOrDefault(t => t.link_id == order.link_id) == null))
{
toAdd.Add(order);
}
foreach (var ORDERSe in toRemove)
{
bdd.ORDERS.Remove(ORDERSe);
}
foreach (var ORDERSe in toAdd)
{
ORDERSe.pjt_id = project_id;
bdd.ORDERS.Add(ORDERSe);
}
foreach (
var order in
newList.Where(
order =>
oldList.FirstOrDefault(t => t.link_id == order.link_id) != null))
{
var child = oldList.FirstOrDefault(t => t.link_id == order.link_id);
bdd.Entry(child).CurrentValues.SetValues(order);
}
But i'm unconfortable with this, because in my mind, entity framework should be able to do the work for me !
I was hoping something like :
customer.orders = newOrders;
Did i missed anything about entity framework or ?
Because when i do this, it just duplicate my orders.
Thanks in advance for your answer.
You can certainly make it cleaner using .Except() and .Intersect(), but the concept doesn't really change, AFAIK you still have to individually remove, update & add the entries in loops...
var oldList = new List<ORDERS>();
var newList= new List<ORDERS>();
var IdsToRemove = oldList.Select(t => t.link_id).Except(newList.Select(t => t.link_id));
var IdsToAdd = newList.Select(t => t.link_id).Except(oldList.Select(t => t.link_id));
var IdsToUpdate = newList.Select(t => t.link_id).Intersect(oldList.Select(t => t.link_id));
//remove
bdd.orders.where(x => IdsToRemove.Contains(x.link_id)).ForEach(x => bdd.Remove(x));
//add
foreach(var order in newList.Where(x -> IdsToAdd.Contains(x.link_id))
{
bdd.Orders.Attach(order);
bdd.Entries(order).EntityState = EntityState.Added;
}
//update
foreach(var order in newList.Where(x -> IdsToUpdate .Contains(x.link_id))
{
bdd.Orders.Attach(order);
bdd.Entries(order).EntityState = EntityState.Modified;
}
bdd.SaveChanges();
But i'm unconfortable with this, because in my mind, entity framework
should be able to do the work for me !
In fact, EF does the Work for you. Using the data context SaveChanges method EF should be able to save all your changes at once:
DbContext.SaveChanges()
For your convinience you can still override this method. Internally you should use something like this:
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditable>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
entry.Entity.ModifiedBy = UserName;
}
}
return base.SaveChanges();
}