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();
}
Related
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();
Here im getting Multipul values as 1,2,3 for ENQUIRY_CODE
public void HRUpdateStatus(string ENQUIRY_CODE, int uid)
{
var x = (from e in db.EMS_ENQUIRYREGISTRATION_MASTER
where e.ENQUIRY_CODE == ENQUIRY_CODE
select e).ToList();
foreach (var abc in x)
{
abc.HRowner = uid;
db.SaveChanges();
}
...
}
Please help me where im doing mistake
A LINQ statement will never change the source sequence.
LINQ is not meant to do that.
The proper solution depends on which kind of DbContext you are using. If you use entity framework you'll have to fetch the items before you can update one or more of the properties.
In your case, you want to change one property of all fetched values to the same new value. Consider creating an extension function for your DbContext. See extenstion methods demystified
The following takes an IQueryable sequence of some source class (TSource), and an action that should be performed on each source element.
public void ForEach<TSource>(this IQueryable<TSource> source,
Action<TSource> action)
{
var fetchedItems = source.ToList();
foreach (var fetchedItem in fetchedItems)
{
action(fetchedItem);
}
}
Usage:
using (var dbContext = new MyDbContext())
{
db.EMS_ENQUIRYREGISTRATION_MASTER
.Where (registrationMaster => registrationMaster.ENQUIRY_CODE == ENQUIRY_CODE)
.ForEach(registrationMaster => registrationMaster.HRowner = uid);
db.SaveChanges();
}
I chose to return void instead of IEnumerable<TSource> to indicate to users of the function that the queried data is materialized and might have been changed. After all the following might have been confusing:
IQueryable<Student> students = ...
var updatedStudents = students.ForEach(student => student.Grades.Add(new Grade(...))
.Take(2)
.ToList();
You want to communicate that all students are updated, not just the 2. Consider returning an IReadonlyList<TSource> or similar, so you don't have to materialize the data again.
I think this is what you mean:
var codes = ENQUIRY_CODE.Split(',').ToList();
var x= db.EMS_ENQUIRYREGISTRATION_MASTER.Where(s => codes.Contains(s.ENQUIRY_CODE)).ToList();
I am trying to remove duplicate code throughout my project and I am at a standstill trying to figure this out. What I am trying to do is create a base linq query that will be reused to add things like Where, Take...etc in multiple different methods.
public IQueryable<Object> FooLinq(int id)
{
using (var ctx = new dbEntities())
{
var results =
(from account in ctx.account
join memberProducts in ctx.tblMemberProducts on account.Id equals memberProducts.AccountId
orderby account.date descending
select new{account,memberProducts}).ToList();
return results;
}
}
So that would be by base query above and I would have a seperate method that would reuse VioLinq but this time would use a where clause in it.
public List<IncomingViolations> Foo1(int id)
{
//Linq query FooLinq() where Name == "Bob"
}
You'll need to do two things:
Return the query prior to materializing it.
Make sure the context is still in scope when the final query is materialized.
These two requirements will play off each other somewhat, and there are a number of approaches you can take to meet them.
For example, you could make your method take the context as a parameter, forcing the caller to provide it and manage its lifecycle.
public IQueryable<AccountInfo> FooLinq(DbEntities ctx, int id)
{
return
from account in ctx.account
orderby account.date descending
select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
};
}
public List<IncomingViolations> Foo1(int id)
{
using(var ctx = new dbEntities())
{
//Linq query FooLinq() where Name == "Bob"
return FooLinq(ctx).Where(v => v.Name == "Bob").ToList();
}
}
You could alternatively inject the context as a constructor-injected dependency, and use a DI framework to manage the context's lifecycle.
You can do it as Queryable then add conditions to it.
For example:
public List<account> GetAccountsByName(string name, bool usePaging, int offset = 0, int take = 0) {
var query = GetMyQuery();
query = query.Where(x => x.Name == name);
query = query.OrderBy(x => x.Name);
if(usePaging) {
query = query.Take(take).Skip(offset);
}
query = PrepareSelectForAccount(query);
return query.ToList(); .
}
public IQueryable<account> GetMyQuery(){
return ctx.account.AsQueryable();
}
public IQueryable<account> PrepareSelectForAccount(IQueryAble<account> query){
return query.Select(select new AccountInfo()
{
Name = account.Name,
Mid = account.MemberID,
Date = account.Date,
Address = account.Address,
}
);
}
Sure, but don't call .ToList(), and return IQueryable<T> instead of List<T>. LINQ is based on the concept of deferred execution which means the query is not actually performed until the enumerable is iterated over. Until then, all you have done is built an object which knows how to do the query when the time comes.
By returning an IQueryable<T> from a function that sets up the "basic query," you are then free to tack on additional LINQ methods (such as .Where() or .Take()) to produce a modified query. At this point you are still simply setting up the query; it is actually performed only when you iterate over the enumerable, or call something like .ToList() which does that for you.
Is there a way to clear an IQueryable from its ordeyby clauses?
For example, we have a function that returns an ordered query:
public IQueryable<SomeType> GetOrderedQuery()
{
var query = from item in db.itemsTable
where item.x != null
orderby item.y, item.z
select item;
return query;
}
And we have another function that needs to use the same query, but it needs to have it unordered:
public IQueryable<SomeType> GetUnorderedQuery()
{
var query = GetOrderedQuery();
query.RemoveOrders(); // How to implement a RemoveOrders function?
return query;
}
How can a RemoveOrders function be implemented? (Doesn't matter if as an extension method or not)
If you don't want it ordered; don't order it. There's no robust way to walk back through an IQueryable<T> to get earlier states, let alone remove individual bits out of the middle. I suspect you want two queries:
public IQueryable<SomeType> GetUnorderedQuery()
=> db.itemsTable.Where(item => item.x != null);
public IOrderedQueryable<SomeType> GetOrderedQuery()
=> GetUnorderedQuery().OrderBy(item => item.y).ThenBy(item => item.z);
I have a basic IQueryable,
private static IQueryable<TestObject> GetFilteredQuery(Guid componentId, Guid productId)
{
IQueryable<TestObject> query = from t in ModelQuery.Query<TestObject>()
where t.ComponentId == componentId && t.ProductId == productId
select t;
return query;
}
This is trivial if I have to compare single componentId and productId.
My problem is how can I handle when I have a list of value pairs,
Guid[] componentIds, Guid[] productIds
where, its kind of a keyValue pair.
something like,
private static IQueryable<TestObject> GetFilteredQuery(Guid[] componentIds, Guid[] productIds)
{
IQueryable<TestObject> query = from t in ModelQuery.Query<TestObject>()
where (t.ComponentId must be present in componentIds[] && t.ProductId must be present in productIds)
select t;
return query;
}
Use Contains:
private static IQueryable<TestObject> GetFilteredQuery(Guid[] componentIds, Guid[] productIds)
{
IQueryable<TestObject> query =
from t in ModelQuery.Query<TestObject>()
where (componentIds.Contains(t.ComponentId)
&& productIds.Contains(t.ProductId))
select t;
return query;
}
Edit
AFAIK there is no way Linq2Sql is going to map a sequence of Guid tuples to native Sql (you would likely need an #Table parameter for this)
So here's one approach, viz to run a query the same contains as above, but using OR on the 2 filter lists. Sql will hopefully be able to filter a significant amount of data out at the database level.
The results (candidates) then need to be materialized, and then filtered in memory against the component and product pairs. I've done this by zipping the 2 guid arrays together (assuming similar length - possibly you want to remodel the arrays as an array of Pairs to express the intention more explicitly?)
private static IQueryable<TestObject> GetFilteredQuery(Guid[] componentIds,
Guid[] productIds)
{
var candidates = ModelQuery
.Query<TestObject>()
.Where(componentIds.Contains(
t.ComponentId) || productIds.Contains(t.ProductId))
.ToList();// Need to materialize
var guidPairs = componentIds.Zip(productIds,
(c, p) => new {ComponentId = c, ProductId = p});
return candidates
.Join(guidPairs,
c => new {ComponentId = c.ComponentId, ProductId = c.ProductId},
gp => gp,
(c, gp) => c)
.AsQueryable();
}
Note that the resultant queryable isn't really suitable for further composition, given that it has already been materialized. Also, if you can do additional filtering before hitting this, it would be beneficial. And I'm afraid I haven't actually tested this.
Use Contains:
where componentIds.Contains(t.ComponentId) && productIds.Contains(t.ProductId)