Is looping through the entityreference the correct way? - c#

I want to get all users that have a specific role to a list of usernames.
Is using .Include to include all the users, and going through the UsersReference
the best way to loop through all the users that are associated with the role?
I noticed I could not do a foreach(User user in role.Users) but UsersReference seem to work, but is that how it's supposed to be done? Going through the reference?
using (var context = new MyEntities())
{
List<string> users = new List<string>();
Role role = (from r in context.Roles.Include("Users")
where r.RoleName == roleName
select r).FirstOrDefault();
foreach (User user in role.UsersReference)
users.Add(user.UserName);
return users.ToArray();
}

Is it possible that your Role table has a Users property? I would think that it would name the navigation property Users, not UsersReference. I don't use EF, but all the examples I've seen name the property after the table. AFAIK it always implements IEnumerable so you should be able use it in a foreach.
If you have it set up right I think you only need:
using (var context = new MyEntities())
{
return context.Roles
.Where( r => r.RoleName == roleName )
.SelectMany( r => r.Users )
.Select( u => u.UserName )
.ToArray();
}

Try using your original foreach loop with ToList()
foreach(var user in role.Users.ToList()) {...}

Use the .ToArray() helper instead
using (var context = new MyEntities())
{
return (from role in context.Roles
where role.RoleName == roleName
from user in r.Users
select user.UserName).ToArray();
}
Note that there's no need for .Include("Users") if you do it this way. Using r.Users in the query causes it to be in one query without the need for including it, because it's used in an active ObjectContext.
One a side note, I'm not sure what the method signature of this method is, but in this case IEnumerable<string> is probably a better fit than string[], because you can adjust the implementation later without creating arrays from other types of collections.

Related

LINQ statment looks unreasonable to me: what's my error?

There has to be a better way:
public IList<ApplicationUser> GetProspects()
{
var UsrNames = Roles.GetUsersInRole("prospect");
IList<ApplicationUser> Users = new List<ApplicationUser>();
foreach ( var u in UsrNames)
{
// In theory should return at most one element.
var UserListFromQuery = from usr in (new ApplicationDbContext()).Users where usr.UserName == u select usr;
Users.Add(UserListFromQuery.ElementAtOrDefault(0));
}
return Users;
}
Can you please show me the error of my ways?
This should do what you want:
using (var context = new ApplicationDbContext())
{
var result = Roles.GetUsersInRole("prospect")
.Select(name => context.Users.FirstOrDefault(user => user.UserName == name))
.Where(user => user != null)
.ToList();
}
I've modified your code to utilize a using statement for the context, so as to ensure it is disposed of properly even if there is an exception. Then I have a linq query which does the following:
Get the usernames
For every username, select the first user in Users with a matching username
Remove all nulls from the resulting enumeration. This is necessary because FirstOrDefault returns null if no matching user is found
Turn our final enumeration into a List
I guess you could join it, then group it, then cull the grouping. I'm not sure whether there is an overall advantage of the front-loading that you'd be doing by joining and grouping, so you might want to put a stopwatch to this (and to your original code) and figure that one out.
My suggestion is:
// force a prefetch, rather than potentially slamming the server again and again
var allUsers = (new ApplicationDbContext()).Users.ToList();
// use the prefetched list to join & filter on
var result = from entitled in UsrNames
join user in allUsers
on entitled equals user.UserName
group user by user.UserName into grp
select grp.First();
return result.ToList();
Couple of thoughts:
This is clearly a user-related table. So I'm guessing that you're not going to have 100k records or something along that scale. At most, maybe thousands. So safe to cache in local memory, especially if the data will not change many times throughout the day. If this is true, you might even want to preload the collection earlier on, and store into a singular instance of the data, to be reused later on. But this observation would only hold true if the data changes very infrequently.

Using query results with joined parameters without anonymous type

Adapting from this C# MVC tutorial I'm using the following statement to return a specific user to the view
User user = db.Users.Find(id);
if (user == null){return HttpNotFound();}
return View(user);
The Users model has a foreign key to Auctions so the view can iterate over a list of auctions associated with this user.
Auctions has a field called buyerID which corresponds with User.ID so I want to display the buyers name by linking back to User.userName by way of the Auctions.buyerID.
I can do this in SQL statements and LINQ as well but I think there is a simpler way.
How can I use, User user = db.Users.Find(id); and join it to User from the foreign key of Auctions.buyerID?
This is kind of what I thought would work but I can't create a new parameter 'buyerName' when User doesn't have this.
IEnumerable<User> thisUser = from usr in db.Users
join auct in db.Auctions on usr.ID equals auct.UserID
join ur in db.Users on auct.buyerID equals ur.ID
where usr.ID == id
select new User
{
buyerName = ur.userName
};
From other posts like this one I read about setting up a new class but that doesn't seem to stick to the DRY & KISS principles since now I need to have two classes that vary by one parameter located in different locations.
Is there a simpler way to join the table and get the strong typing without creating a new class?
So the result should display the buyers name instead of the buyers ID#
Since I wanted to avoid using anonymous type and I didn't want to create a new class I found a work around by passing a dictionary of user names to the view.
Controller
User user = db.Users.Find(id);
var dict = db.Users.Select(t => new { t.ID, t.userName })
.ToDictionary(t => t.ID, t => t.userName);
ViewData["userDict"] = dict;
Now I just looked up the username based on the buyer ID
View
#{Dictionary<int, String> userList = ViewData["userDict"] as Dictionary<int, String>;}
#{
if (item.buyerID.HasValue)
{
#Html.DisplayFor(modelItem => userList[item.buyerID.Value])
}
}

Linq Conversion From ICollection<T> to List<T>

I am using Code First Entity Framework.
I am using the following simple classes:
public class Users
{
public ICollection<Projects> Projects{get;set;}
}
public class Projects
{
public ICollection<Users> Users{get;set;}
}
I am using linq for data retrieval. When performing the following query: (Note that lstProjects is a List<Project>)
var lstUsers = (from users in lstProjects
where users.ProjectId == pId
select users.Users).ToList();
I have a List<Users> object and want to populate this List with items. Like,
var lstUsersToDisplay = new List<Users>();
lstUsersToDisplay = (List<Users>)lstUsers; //This can't be cast.
What's the approach to convert ICollection<T> to List<T>?
Secondly, I have List<Users> and want to convert it into ICollection<Users> how achieve this?
Edited:
Scenario, more clearly is that
All Projects are loaded in lstProjects and we need to select the Users which were mapped to a specific project. These Users are also are contained inside Projects as collection. Every Project has its Users collection like if I decomposed the lstProjects it would be like:
lstProjects --> [0]-->//other Properties
ICollection[Users]-->[0]//Contains User class Properties
[1]....
[1] ... same procedure
Hope it clears the scenario
If your query is genuinely this:
var lstUsers = (from users in lstProjects
where users.ProjectId == pId
select users.Users).ToList();
then that's equivalent to:
List<ICollection<Users>> lstUsers = (from users in lstProjects
where users.ProjectId == pId
select users.Users).ToList();
If you're trying to get the list of uses from a single project, I'd write that as:
var lstUsers = lstProjects.Single(project => project.ProjectId == pId)
.Users
.ToList();
If there could be multiple projects with the same ProjectId, you want to flatten the users. For example:
var lstUsers = lstProjects.Where(project => project.ProjectId == pId)
.SelectMany(project => project.Users)
.ToList();
Or in query expression syntax:
var lstUsers = (from project in lstProjects
where project.ProjectId == pId
from user in project.Users
select user).ToList();
Note the fact that my range variable is called project, because it refers to a project, not a user. Naming is important - pay attention to it. I would also rename the Projects and Users types to just Project and User, assuming each is really only meant to represent a single entity.
lstUsers isn't a List<User>. It's a List<ICollection<User>>. You map each project to a sequence of users, not to a single user. To flatten a collection of collections into just a collection of the inner items you would use SelectMany, or, in query syntax, you'd write out your query like so:
var lstUsers = (from project in lstProjects
where project.ProjectId == pId
from user in project.Users
select user).ToList();
Now you have a List<User> for lstUsers. You can assign that as is to a List<User> or an ICollection<User>
using System.Linq; //include extension method OfType<> for ICollection
...
List<Projects> userList = lstUsers.OfType<Projects>().ToList();

Linq query with multiple objects in parent child relationship

I have a schema like this
Package -> Lists -> Users
All 'one to many' down the line...
So I want to run a query where I get all packages that a userID matches in users.
var pck = (from pk in context.Package
where pk.Lists[here's my problem]
I would assume the navigation properties here would be: pk.Lists. *Users.UserId* == MyUserId, however I'm not seeing the navigation properties at the Lists level.
I haven't gotten to more complex EF queries like this yet. I've looked around the web but haven't found anything to make it click. I turn to you stack. Somebody help me see the light!
EDIT: Thanks again stack, I will do my best to pay it forward!
Also, all of these answers enlightened me to the power of ef4!
I assume that a package contains multiple lists, and a list contains multiple users? You could try:
var pck = content.Package
// Outdented just for Stack Overflow's width
.Where(pk => pk.Lists.Any(list => list.Any(u => u.UserId == myUserId)));
Or use a cross-join:
var pck = from pk in content.Package
from list in pk.Lists
from user in list.Users
where user.UserId == myUserId
select ...; // Select whatever you're interested in
context.Packages.Where(p => p.Lists.Any(l => l.Users.Contains(MyUserId)))
or, if your user is something other then just a user id,
context.Packages.Where(p => p.Lists.Any(l => l.Users.Any(u => u.Id == MyUserId)))
var packages =
context.Package.Where(p =>
p.Lists.Any(l =>
l.Users.Any(u => u.UserId == MyUserId
)
);
If the links are non-nullable and direction is package has many lists and list has many users, then the query is pretty easy.
var pck = from user in context.Users
where user.UserId == userId
select user.List.Package;
try this:
pk.Lists.Any(l => l.Users.Any(u => u.UserId == MyUserId))

With entity framework's self tracking entities, can I perform an explicit load?

I wish to return a graph for a "Business" entity. The Buiness entity has a collection of "Contacts".
So I basically want this:
ctx.Business.Include("Contacts").Where(b => b.BusinessID == id).Single();
The problem is I don't want ALL the contacts for the business. Just those that have a ContactTypeID = x. How can I accomplish this? I figured I may need to break the query into 2 steps. 1 to get the business and a 2nd to grab the contacts and then attach them to the business somehow.
But I'm using using the STE t4 template and I can't figure out how to do it using that.
I'd greatly appreciate any help.
One way for doing so is:
var biz = from b in ctx.Business where b.BusinessID == id
select new
{
Business = b,
Contacts = b.Contacts.Where(c => c.ContactTypeID == x)
}.ToList();
I think I found how to do it:
var biz = ctx.Business.Where(b => b.BusinessID == id).Single();
var contacts = ctx.Contacts.Where(c => c.BusinessID==id && c.ContactTypeID==6);
foreach (var contact in contacts)
{
biz.Contacts.Add(contact);
}
I was worried by adding the contacts this way they would be treated as "new" items but change tracking is off so they are treated as unchanged. I think change tracking only turns on once they are deserialized.

Categories