LINQ All not working as expected when used with Contains - c#

I have a many-to-many relationship like this:
Organisations (OrganisationID, Name)
Categories (CategoryID, Name)
Organisations_Categories (OrganisationID, CategoryID)
I'm trying to get a list of Organisations that belong to all the Categories that get passed in as a parameter
e.g. If 10 Organisations belong to CategoryID=1, and 0 Organisations belong to CategoryID=2, and I pass in [1,2] as the CategoryID parameter, 0 Organisations should be returned, because 0 Organisations belong to both CategoryID=1 and CategoryID=2
Here is the code so far:
int[] catIdsSelected = ((catIds.Length > 0) ? Array.ConvertAll(catIds.Split(','), int.Parse) : new int[0]);
if (catIdsSelected.Length > 0)
{
orgs = orgs.Where(l => l.Categories.Any(m => catIdsSelected.AsQueryable().Contains(m.CategoryID)));
}
However this returns a list of Organisations that belong to any of the Categories passed in
I've tried replacing 'Any' with 'All' without success

You can do a little trick:
if (catIdsSelected.Length > 0)
{
foreach (var id in carIdsSelected)
{
var localId = id;
orgs = orgs.Where(o => o.Categories.Any(cat => cat.CategoryId == localId));
}
}
It will work for sure, but I am not sure how efficient query is generated by EF provider.

Related

How to dynamically add "OR" conditions in Where method provided by Entity Framework

I have a list of Ids and I want to fetch those records from my Products table (present in database) where Product Id matches to any Ids given in the following list.
List<int> ids = new List<int> { 1, 2, 3 };
I know I can do like this ->
_unitOfWork.Product.GetAll(p => p.Id == 1 || p.Id == 2 || p.Id == 3);
But problem with this is my list is dynamic. Here just for example I hard coded 3 values but it could be the list of n numbers. So in that case it will fail.
So, I want to know if there a way to or condition like ->
_unitOfWork.Product.GetAll(p => p.Id == //all ids present in list with OR conditions, something like foreach loop which will iterate through my list of ids & internally will make condition like I made above with hard coded values);
I am using repository pattern in my project, hence my GetAll() method looks like this:
public IEnumerable<T> GetAll(Expression<Func<T, bool>>? filter = null, string? includeProperties = null)
{
IQueryable<T> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (includeProperties != null)
{
query = IncludeProperties(query,includeProperties);
}
return query.ToList();
}
You can use .Any(),
List<int> ids = new List<int> { 1, 2, 3 };
_unitOfWork.Product.GetAll(p => ids.Any(x => x == p.Id));
Alternate way you can also use .Contains()
_unitOfWork.Product.GetAll(p => ids.Contains(p.Id));

Delete records with multiple ids based on condition

Below is my class :
public partial class Ads
{
public int Id { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
public int Group { get; set; }
}
Records :
Id Name Group
1 abc 1
2 xyz 1
3 lmn 1
4 xxx 2
5 ppp 2
6 ttt 3
7 ggg 3
Now I want to remove all records/only that record with particular id of same group for some ids.
Code :
public void Delete(int[] ids,bool flag = false)
{
using (var context = new MyEntities())
{
context.Ads.RemoveRange(
context.Ads.Where(t => (flag ?
(context.Ads.Any(x => ids.Contains(x.Id) && x.Group == t.Group)) : false)));
context.SaveChanges();
}
}
What I am trying to do is something like below :
If flag is false with ids=3,5 then
I want to delete only records with Id=3,5
Else if flag is true with ids=3,5 then
I want to delete records with Id=3,5 but all other records too of the group to which ids=3,5 belong to.
Here id=3 belongs to group 1 so I want to delete all records of group1 i.e id=1,2 like wise ids=5 belongs to
group 2 so I want to delete all records of group 2 i.e id=4.
Expected output for this last case(flag=true) :
Id Name Group
6 ttt 3
7 ggg 3
But I think that I haven't done this is a proper way, and there is some source of improvement in the query.
Note : ids[] will always contains ids from different group and that too highest ids from different group.
How can I to improve my query for both the cases(flag=true and false)?
What about
var removeRecs=context.Ads.where(t => ids.contains(t.id))
if(flag)
removeRecs.AddRange(context.Ads.where(t=> removeRecs.Any(r =>t.groupId==r.Id)))
Ads.RemoveRange(removeRecs);
Do not make it too hard for your self, not everything must/can be done in the where statement of a query. Also a general rule of thumb in a loop try to factor out all the constant values and checks. So try this:
public static void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var query = context.Ads.AsQueryable();
query = flag
? query.Where(x => context.Ads
.Where(i => ids.Contains(i.Id))
.Select(i => i.Group)
.Contains(x.Group))
: query.Where(x => ids.Contains(x.Id));
context.Ads.RemoveRange(query);
context.SaveChanges();
}
}
public void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var items = context.Ads.Where(x => ids.Any(a => x.Id == a));
if (!flag)
{
//flag=false --> delete items with Id in ids[]
context.Ads.RemoveRange(items);
}
else
{
var groups = items.GroupBy(a => a.Group).Select(a => a.Key);
//flag=true --> delete all items in selected groups
context.Ads.RemoveRange(context.Ads.Where(x => groups.Any(a => x.Group == a)));
}
context.SaveChanges();
}
You should separate your tasks...
if (flag)
{
groupIds = db.Ads.Where(x => ids.Contains(x.Id)).Select(x => x.Group).ToList();
db.Ads.RemoveRange(db.Ads.Where(x => groupIds.Contains(x.Group)).ToList());
}
else
{
db.Ads.RemoveRange(db.Ads.Where(x => ids.Contains(x.Id)).ToList());
}
To me it looks like you have two different deletes here.
In first case you are only deleting the ads with given ID and this is pretty straight forward.
In second case you are deleting the ads with given ID and all other ads that contain the group of the recently deleted Ads. So in this case instead of deleting the ads with given Id first why not actualy get distinct groups for these ID-s and than just delete the groups.
EDIT
You can do it like this.
using (var context = new TestEntities())
{
if (!flag)
context.Ads.RemoveRange(context.Ads.Where(a => ids.Contains(a.Id)));
else
context.Ads.RemoveRange(context.Ads.Where(a => context.Ads.Where(g => ids.Contains(g.Id)).Select(x => x.Group).Distinct().Contains(a.Group)));
context.SaveChanges();
}
For the more complicated case I am trying to get distinct groups for given id-s. So for ID-s 3 and 5 I am selecting the groups and than I am doing distinct on the groups since it might happen that the id-s have the same group. Than I am fetching all the ads that have these groups. So for passed values of 3 and 5 I would get groups 1 and 2 which I would than use to get all the ads that have that group. That in turn would yield id-s 1, 2, 3, 4, and 5 which I would than delete.
EDIT 2
If the complexity of second Linq query bothers you than write a SQL query.
context.Database.ExecuteSqlCommand(
"DELETE Ads WHERE Group IN (SELECT Group FROM Ads WHERE Id IN(#p1, #p2))", new SqlParameter("#p1", ids[0]), new SqlParameter("#p2", ids[1]));
This should be extra performant rather than rely on EF which will delete it one by one.

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 - Having trouble getting List of IDs from a List of ObjectModel

I'm new to LINQ and I've been poking around the Internet and going through tutorials all day. I have some code I thought would work but it's complaining about the return type. Here is my code:
// The orgs parameter is a list of all buildings in the entire organization
// I want to retrieve only the IDs of all buildings in a particular region
private IList<int> GetRegionBuildingIDs(int regionId, List<OrgModel> orgs)
{
var ids = from org in orgs
where org.regionId == regionId
select new { id = org.buildingId };
return (IList<int>)ids;
}
This returns a list of ids but they are of Anonymous Type and the cast is not working. I get a System.InvalidCastException.
The closest I've found to answering my question is still confusing to me. It's here. I tried to follow the answer but my select(org.buildingId) only offers .ToString
So this is my latest try but it's wrong:
private IList<int> GetRegionBuildingIDs(int regionId, List<OrgModel> orgs)
{
IEnumerable<int> ids = from org in orgs
where org.regionId == regionId
select (org.buildingId)._________; // This should be .ToList
return (IList<int>)ids;
}
Looking forward to a little help here. Thank you.
You don't need select new {}, you can select it directly:
var ids = from org in orgs
where org.regionId == regionId
select org.buildingId;
return ids.ToList();
This is because select new {} uses an anonymous type with a single int member, as opposed to returning the int value directly.
You also cannot cast a Linq query to List<T> because they aren't instances of List<T>, but something else (a lazily-evaluated state-machine built-around the yield return C# language feature).
Of course, your code can be made much simpler
private static IEnumerable<Int32> GetRegionBuildingIds(Int32 regionId, IEnumerable<OrgModel> orgs) {
return orgs
.Where( org => org.regionId == regionId )
.Select( org => org.buildingId );
}
And if you feel like it, make your own extension method:
public static IEnumerable<Int32> GetRegionBuildingIds(this IEnumerable<OrgModel> orgs, Int32 forRegionId) {
return orgs
.Where( org => org.regionId == regionId )
.Select( org => org.buildingId );
}
Used like so:
IEnumerable<Orgs> orgsModel = GetFromDatabaseOrWhateverYourBackingStoreIs();
return orgsModel.GetRegionBuildingIds( forRegionId: 123 );
As far as casting goes. try this:
// The orgs parameter is a list of all buildings in the entire organization
// I want to retrieve only the IDs of all buildings in a particular region
private IList<int> GetRegionBuildingIDs(int regionId, List<OrgModel> orgs)
{
var ids = (from org in orgs
where org.regionId == regionId
select (int)org.buildingId).ToList();
return ids;
}
or if the buildingId is nullable, you can try this
select (int)(org.buildingId ?? 0)

Linq query for referal manager

I have a requirement in my LINQ in MVC application i.e. I need to populate the list of employeeId in a dropdown, to assign mgr to employee's, whose are not subordinate of his or his subordinates subordinate. To make my point more clear below is the scenario-
Id MgrId
1
2 1
3
4 2
5
6 3
I try
empBO.GetAllEmpList().Where(s => s.Id != model.ID && s.MgrId != model.ID);
this works for only one level, from the above when I select empid 1 for edit to assign the mgr the drop down should only contain empid of 3,5 and 6. I haven't had much expertise in LINQ and hopt this could be done using LINQ any suggestion/help would be appreciated.
You can use recursive method to find all subordinates of given employee (note, if hierarchy depth is big, you can use query/stack implementation instead of recursion):
public static IEnumerable<Employee> GetSubordinates(
IEnumerable<Employee> employees, int employeeId)
{
foreach (var employee in employees)
{
if (employee.MgrId != employeeId)
continue;
yield return employee;
foreach (var subordinate in GetSubordinates(employees, employee.Id))
yield return subordinate;
}
}
Getting available employees will be simple:
var employees = GetAllEmpList();
var available = employees.Except(GetSubordinates(employees, selectedId));
This method will return employees with ids { 2, 4 }. Then calling Enumerable.Except will give you all employees besides those who are direct or indirect subordinated of selected one.

Categories