Filtering Entity Collections [duplicate] - c#

This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 6 years ago.
I want to be able to filter a child collection and only return items in that child collection which match certain conditions. Here's the code that I have now:
var q = from u in context.DbContext.Users select u;
q = q.Include(u => u.UserRoles.Select(ur => ur.Role))
.Where(u=> u.UserRoles.Any(ur=> ur.EnvironmentId == environmentId)
);
My issue with this code is that this is also returning UserRole objects in the UserRole collection that do not match.
For example, if my environmentId variable has a value of 1, in only want the UserRoles returned in the collection if they have a value of 1 for the EnvironmentId property.
As of right now, it is returning every UserRole regardless of the EnvironmentId value.
Edit
This is not a duplicate question as Gert Arnold has suggested. I do not want to create new or anonymous objects, and the solution i proposed below solves this problem, whereas the article linked to by Gert Arnold does not.

Your Where condition is not applied to the right collection. Here, you are applying the Where to the User collection so that it will only return users that have at least one role where EnvironmentId is 1. What you want to do instead is apply that to your Role collection to only join the ones you want. this doesn't workI believe something like this should work:
q = q.Include(
u => u.UserRoles.Where(ur => ur.EnvironmentId == environmentId)
.Select(ur => ur.Role))
What you can do instead would be to return a new object via a select (I'm getting into unsure territory right now :)
q = q.Select(u =>
new {
User = u,
Roles = u.UserRoles.Where(ur => ur.EnvironmentId == environmentId)
};
Now here comes the weird part... this will return you an anonymous object where the User propertie is your returned user, and Roles, your returned roles. If you wish you create a new class so that you can carry that value around outside of the scope of that block.
new class
public class UserWithRoles
{
Public User User {get; set;}
IEnumarable<Roles> Roles {get; set;}
}
query
q => q.Select(u =>
new UserWithRoles() {
User = u,
Roles = u.UserRoles.Where(ur => ur.EnvironmentId == environmentId)
};
That way you can declare a List<UserWithRoles> UserList and you could do UserList = q.ToList(); This might not be (probably is not) the best way to do it, but it is one way I believe it will work. If anyone is moire knowledgeable than me in LINQ's Include and knows how to make this work better, please post another answer or comment this one, I'd like to know too :)

you might consider just returning a list of userroles and you can select the users from this list if you need the user objects
var roles = from ur in context.DbContext.UsersRoles.Include("User")
where ur.EnvironmentId == environmentId
select ur;
var users = roles.SelectMany(a => a.Users).Distinct();

using the example provided here Filtering Related Entity Collections I came up with what seems to be a clear and elegant solution to the problem. this only loads the items in the collection if they match, and doesn't require creating any anonymous objects. (note: LazyLoading must be turned off for explicit loading to work)
User user;
var data = from u in context.DbContext.Users select u;
user = data.FirstOrDefault();
// load UserRoles and UserRoles.Role
context.Entry(user)
.Collection(u => u.UserRoles)
.Query()
.Include(ur => ur.Role)
.Where(ur => ur.EnvironmentId == environmentId)
.Load()
;

Related

How to return IQueryable LINQ result from two joined tables into a List<string>?

This is an add-on question to one asked here: Entity Framework Core 5.0 How to convert LINQ for many-to-many join to use Intersection table for ASP.NET Membership
How can I return the results of an the following LINQ IQueryable result, which is from two join tables, for the RoleName column to a List<string>?
var queryResult = (this.DbContext.aspnet_UsersInRoles
.Where(x => x.UserId == dpass.UserId)
.Join(
this.DbContext.aspnet_Roles,
ur => ur.RoleId,
r => r.RoleId,
(ur, role) => new
{
ur,
role
}
)
.Select(x => new { x.ur.UserId, x.role.RoleName })
);
UPDATE 1
I need the List in the form of an array of values so that I can use the Contains() method. I need to search for specific RoleNames assigned to a UserId. If I use ToList() on the IQueryable, then the array result is in the form of:
{ RoleName = "admin"}
{ Rolename = "user"}
I am unable to use the .Contains() method because I get the following error:
cannot convert from 'string' to <anonymous type: string RoleName>.
It seems be to expecting a class that the query result can be assigned to. But, one doesn't exist because I am doing this on-the-fly.
UPDATE 2
I need the queryResult in a List that is in the form of:
{ "admin"}
{ "user"}
With this output, I can use the .Contains() method to perform multiple checks. This is used for determining Windows Forms field properties. So, if the UserId belongs to the admin role then the form enables certain check boxes and radio buttons whereas if the UserId belongs to the user role then the form enables different check boxes. This is not an exhaustive list of roles available along with the checks that are performed by the form. But, what is important is that there are multiple checks on the List that need to be performed in separate IF statements.
Currently, I am able to use the queryResult to do the following:
Get a list of the RoleNames
Perform separate LINQ queries on the queryResult by checking for the specific RoleName
Perform a .Count() > 0 check to see if the UserId is in a specific role.
This seems like an ugly hack because I have the intermediate step of creating 1 + N variables to retrieve, by LINQ, and store each RoleName and then check to see if the .Count() is greater than zero. I think that the List method would be cleaner and more efficient. If that is possible.
var varUser = from d in queryResult
where d.RoleName == "user"
select new { d.RoleName };
var varAdmin = from u in queryResult
where u.RoleName == "admin"
select new { u.RoleName };
//... more declarations and LINQs ...
Short answer:
Select only the RoleName, and use SelectMany instead of Select
Better answer
So you have a table of Roles, and a table of Users (I'm simplifying your long identifiers, not part of the problem and way too much typing).
There seems to be a many to many relation between Roles and Users: Every Role is a role for zero or more Users, every User has zero or more Roles.
This many-to-many relation is implemented using a standard junction table: UsersInRoles. This junction table has two foreign keys: one to the User and one to the Roles.
You have a UserId, and it seems that you want all names of all Roles of the user that has this Id.
How about this:
int userId = ...
// Get the names of all Roles of the User with this Id
var namesOfRolesOfThisUser = dbContext.UsersInRoles
// only the user with this Id:
.Where(userInRole => userInRole.UserId == userId)
// get the names of all Roles for this userInRole
.SelectMany(userInRole => dbContext.Roles.Where(role => role.RoleId == userInRole.RoleId)
.Select(role => role.RoleName));
In words: from the table of UsersInRoles, keep only those UsersInRoles that have a value for property UserId that equals userId.
From every one of the remaining UsersInRoles, select all Roles that have a RoleId that equeals the UserInRole.RoleId. From these Roles take the RoleName.
I use SelectMany to make sure that I get one sequence of strings, instead of a sequence of sequences of strings.
If you suspect double RoleNames, consider to append Distinct() at the end.
But I want to Join!
Some people really like to do the joins themselves.
int userId = ...
var namesOfRolesOfThisUser = dbContext.UsersInRoles
.Where(userInRole => userInRole.UserId == userId)
.Join(dbContext.Roles,
userInRole => userInRole.RoleId, // from every UserInRole take the foreign key
role => role.RoleId, // from every Role take the primary key
// when they match, take only the name of the Role
(userInRole, role) => role.RoleName);
Try to use GroupBy(). Be careful, this method is not supported by direct IQueryable to SQL conversion. If you will try to call GroupBy() before .ToList(), it will throw an error.
In your example you could this: select a list in memory and then work with it:
var queryResult = (this.DbContext.aspnet_UsersInRoles
.Where(x => x.UserId == dpass.UserId)
.Join(this.DbContext.aspnet_Roles,
ur => ur.RoleId,
r => r.RoleId,
(ur, role) => new { ur, role }
)
.Select(x => new { x.ur.UserId, x.role.RoleName })
.ToList() // MATERIALIZE FIRST
.GroupBy(x => x.UserId) //ADD THIS
);
queryResult.Contains(roleName=> roleName == "ROLE_TO_SEARCH")
var userId = queryResult.Key;

LINQ: Is there a way to combine these queries into one?

I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.

Is it possible to extend an Entity model with common queries?

Say I have a set of Sites that have a collection of Users. I find myself violating the DRY principal whenever I have to redefine a query to get, say, the last visited User for a given site.
For example, my query may look like this:
from site in Context.Sites
where site.ID == 99
select new {
ID = site.ID,
Name = site.Name,
URL = site.URL,
LastVisitedUser = site.Users.OrderByDescending(u => u.LastVisited).Select(u => new {
ID = u.ID,
Username = u.Username,
Email = u.EmailAddress
})
.FirstOrDefault()
}
That query returns what I want, but I find myself repeating this same select for the LastVisitedUser in multiple places as I'm selecting the site in different ways to populate my various ViewModels.
So, I thought that I would simply extend my Site Entitiy class with a property like so:
public partial class Site {
public LastVisitedUser {
get {
var query = from user in Users
where user.SiteID == this.ID
orderby user.LastVisited descending
select user;
return query.FirstOrDefault()
}
}
}
In this manner, whenever I am selecting a site it would be fairly trivial to grab this property. This almost works, however I am stuck trying to assign an Entity user into my UserViewModel property into the LastVisited property of my return, without an obvious way on how to project the User into my ViewModel version.
Perhaps an example would help explain. What I'm trying to accomplish would be something like this:
from site in Context.Sites
where site.ID == 99
select new SiteViewModel {
ID = site.ID,
Name = site.Name,
URL = site.URL,
LastVisitedUser = site.LastVisitedUser <--- NOTE
}
NOTE = This is my point of failure. LastVisitedUser is a ViewModel with a subset of User data.
Am I going about this in the correct manner? Can I achieve what I'm trying to do, or am I horribly misguided? Am I about to sove this issue and run into others?
Thanks!
Edit: The former answer was not correct. You cannot use extension method on the navigation property but you can still create extension method for the whole Site projection.
Just create simple reusable extension method:
public static IQueryalbe<SiteModel> GetSiteModels(this IQueryable<Site> query)
{
return query.Select(site => new SiteModel {
ID = site.ID,
Name = site.Name,
URL = site.URL,
LastVisitedUser = site.Users
.OrderByDescending(u => u.LastVisited)
.Select(u => new LastVisitedUser {
ID = u.ID,
Username = u.Username,
Email = u.EmailAddress
}});
}
Now you should be able to use that extension in your former query:
Context.Sites.Where(site => site.ID == 99).GetSiteModels();
Your example will not work because your property is not visible for Linq-to-entities.
If you mean that you what to reuse common queries with different extensions, you just nead to write some base query and get different results and some Where lambda expressions. If you profile it, you will see that you have just one query to DB just return IQuerable in a base query

Use LINQ-to-SQL to return an object that has child objects filtered

I have a MembershipGroups table that is associated with a child Members table. The Members table has a Status column which can be set to Active or Inactive.
I want to select all MembershipGroups and only their active Members
As an example,
MembershipGroups
ID----Title
1-----Group #1
2-----Group #2
Members
MembershipGroupID-Name--Status
1-------------------------John----Active
1-------------------------Sally----Inactive
1-------------------------David---Inactive
I'm trying to create a query that looks something like the following (which doesn't currently work):
var query = from mg in db.MembershipGroups
where mg.Members.Status = "Active"
select mg
The result for this example should return a MembershipGroup of ID#1 with only one child Member entity
How can use LINQ-to-SQL to select a parent object that filters on child objects? If I were using straight T-SQL then this would be a simple join with a where clause but it seems to be much more difficult to do using LINQ-to-SQL.
Edit - Updated answer to return the MemberShipGroup object
var query = (from mg in db.MembershipGroups
join m in db.Members.Where(mem => mem.Status == "Active")
on mg.ID equals m.MembershipGroups into members
select new
{
MembershipGroup = mg,
Members = members
}).AsEnumerable()
.Select(m => new MembershipGroup
{
ID = m.MembershipGroup.ID,
Title = m.MembershipGroup.Title,
Members = m.Members
});
In LINQ to SQL, you can use the AssociateWith method on the DataLoadOptions to set your child filter at the context level.
DataLoadOptions opt = new DataLoadOptions();
opt.AssociateWith<Member>(m => m.Status == "Active");
db.LoadOptions = opt;
With this in place, you can simply return your member groups (or filter them for the active ones using where mg.Any(group => group.Members.Status == "Active"). Then when you try to drill into the Members of that group, only the Active ones will be returned due to the LoadOptions.
See also http://msdn.microsoft.com/en-us/library/system.data.linq.dataloadoptions.associatewith.aspx .
One word of warning, once you set the LoadOptions on a context instance, you can not change it. You may want to use a customized context to use this option.
As an alternative, you could use LINQ to SQL's inheritance model to create an ActiveMember type using the Status column as your discriminator and then create an association between the MemberGroups and ActiveMembers types. This would be the approach you would need to use to model this with the Entity Framework if you though about going that route as well as EF doesn't support the concept of the LoadOptions.
Make sure you are including the child objects you are trying to filter on, inside the query.
E.g.
var query = db.MembershipGroups
.Include("Members")
.Where(m => m.Members.Status == "Active");

Help with LINQ query

I currently a list of a Supplier class, within that supplier class is a list of orders.
Each order has a userID and an empty string variable for username.
I then have a list of users which contains userID and username.
The way I am doing this now is:
foreach(supplier s in SupplierList)
{
foreach (order o in s.childorders)
{
user u = _users.First(p => p.userid == o.userid);
o.username = u.username;
}
}
I feel this might be a little inefficient and I was wondering if it is possible to compact it down into one linq query?
The logic should be
set supplierslist.childorders.username to the value in _users where supplierslist.childorders.userid == _users.userid.
Im fairly new to Linq so any advice for this would be apreciated, or also if its a bad idea and to leave it as it is / reasons why would be good too.
Thanks
What you want to do here is iterate over a collection (many collections, really, but it doesn't make a difference) and mutate its members. LINQ is not really targeted at performing mutating operations but rather at querying. You can do it with LINQ, but it's against the spirit of the tool.
If you are constructing the SupplierList yourself, it might be possible to fetch the data appropriately with LINQ so that it comes pre-populated as you want it to be.
Otherwise, I 'd leave the foreach as it is. You can make a dictionary that maps ids to users to make the inner loop faster, but that's your call and it depends on your data size.
var orderUserPairs = SupplierList
.SelectMany(s => s.ChildOrders)
.Join(_users, o => o.UserId, u => u.userId, (Order, User) => new {Order, User});
foreach (var orderUserPair in orderUserPairs)
orderUserPair.Order.username = orderUserPair.User.username;
Though having both username and userId as part of order looks suspicious.
First a question...
It looks like you are operating on every order. Why do you need to cycle through the supplierlist first since you don't seem to be using it inside the loop? Unless there are orders that don't belong to any supplierlist, you might be able to skip that step.
If that isn't the case, then I think you can use a join. If you aren't familiar with the syntax for joins in linq, this is one (simplified) way to approach it:
var x = from S in SupplierList
join C in childorders on C.supplierlistID equals S.ID
where [whatever you need here if anything]
select new { field1, field2};
foreach var y in x
{
}
Note I assumed a foreign key in childorders to supplierlist. If that isn't the case you will have to modify accordingly.
Hope that helps.
You need to use SelectMany or join depending on weather you are using linq-to-sql or linq with local collections. If you are using local collections the better way is to use join, else use SelectMany.
Like this...join:
var selection = (from s in SupplierList
join o in s.childholders on s.userid equals o.userid
select new { username = o.username);
or, in case of linq-to-sql:
var selection = (from s in SupplierList
from o in s.childholders
select { username = o.username);
You can then use the anonymous type you projected the way you want.
I agree with Jon, but you could say:
var orders = (from s in supplier
from o in s.childorders
select new
{
Order = o,
User = _users.First(p => p.userid == o.userid)
}).ToList();
foreach(var order in orders) {
order.Order.username = order.User.username;
}
Untested of course :)
If users list contains many elements, it can be really slow so I'd use a temporary dictionary:
var userById = users.GroupBy(x => x.userid)
.ToDictionary(x => x.Key, x => x.First());
foreach(var order in supplier.SelectMany(x => x.childorders))
{
order.username = userById[order.userid].username;
}

Categories

Resources