EF - Navigation property explanation - c#

I use EF 4 and Membership Provider Shipped with ASP.Net 4.0.
I need find all Users in a Specific Role and Populate a DropDownList using List<ListItem>.
DataBase Tables involved are:
aspnet_Roles
aspnet_Users
aspnet_UsersInRoles
Navigation Property from aspnet_Users to aspnet_Roles (using junction table aspnet_UsersInRoles) is:
myUser.aspnet_Roles
Matching RoleID (Guid) "CE44ED48-E9F9-49C6-9E15-E40EEFDC7479")
My Script:
using (CmsConnectionStringEntityDataModel context = new CmsConnectionStringEntityDataModel())
{
IQueryable<aspnet_Users> userQuery = from aspnet_Users in context.aspnet_Users select aspnet_Users;
IQueryable<aspnet_Roles> roleQuery = from aspnet_Roles in context.aspnet_Roles select aspnet_Roles;
List<ListItem> myListUsersInRoles = new List<ListItem>();
foreach (aspnet_Users myUser in userQuery)
{
// PROBLEM HERE
if (myUser.aspnet_Roles.ToString() == "CE44ED48-E9F9-49C6-9E15-E40EEFDC7479")
myListUsersInRoles.Add(new ListItem(myUser.UserName.ToString(), myUser.UserId.ToString()));
uxListUsers.DataSource = myListUsersInRoles;
uxListUsers.DataBind();
}`
Problems:
IF return FALSE always, so I am not able to populate List<>.
I suppose I am doing wrong in if an some Properties.
Do you have any ideas? Thanks for your time.

myUser.aspnet_Roles is an EntitySet<aspnet_Roles> (in other words, a collection of roles, so it's highly unlikely that it's string representation happens to match the string representation of the role you're after's ID.
There are several ways you could fix it. One is:
// ...
if (myUser.aspnet_Roles.Any(r => r.RoleId.ToString() == "CE44ED48-E9F9-49C6-9E15-E40EEFDC7479"))
// ...
Also note that from y in x select y is just the same as x, so you could write
foreach (aspnet_User myUser in context.aspnet_Users)
// ...
And not bother declaring userQuery or roleQuery. (You don't actually seem to be using roleQuery anywhere anyway...)
Also, you could look at using the membership/roles API to fetch users in a role:
using System.Web.Security; // at the top of your file
var usersInRole = Roles.GetUsersInRole("name of role here, NOT the guid");
Note that this returns an array of usernames, not user objects, so you'd have to do a little extra work to get the User ID, but it is something you could think about...

Related

How to update many to many in Entity Framework with AutoDetectChangesEnabled = false

Please, help me to handle this situation:
I meaningly switched off AutoDetectChangesEnabled and I load my
entities AsNoTracked() meaningly either.
And I can't update many-to-many relationship in this case:
Here is the code of Update method:
public void Update(User user)
{
var userRoleIds = user.Roles.Select(x => x.Id);
var updated = _users.Find(user.Id);
if (updated == null)
{
throw new InvalidOperationException("Can't update user that doesn't exists in database");
}
updated.Name = user.Name;
updated.LastName = user.LastName;
updated.Login = user.Login;
updated.Password = user.Password;
updated.State = user.State;
var newRoles = _roles.Where(r => userRoleIds.Contains(r.Id)).ToList();
updated.Roles.Clear();
foreach (var newRole in newRoles)
{
updated.Roles.Add(newRole);
}
_context.Entry(updated).State = EntityState.Modified;
}
All simple fields, like Name, LastName updated. But the set
of Roles for User doesn't get updated - it stays the same.
I tried loading Roles using
_context.Entry(updated).Collection("Roles").Load();
But I can't update this loaded set in any way.
I searched for similar items but failed to find the answer, thought it definitely already exists.
I'm really sorry for possible dublicate.
PS. I want to add that I don't want to delete or update child entities at all.
A lot of existing answers suggest manually delete / add child entities to database in whole, but it is not suitable for me.
Roles are independent entities, any other user can use them.
I just want to update User_Role table in database, but I can't.

Entity framework - how to remove data from junction table

my problem is following - I have 2 tables (users, roles) with relation many-to-many. When I want to add new role for user, everything is OK, but I am not able to remove role from user.
List<Role> roles = new List<Role>();
roles.Add(role1); roles.Add(role3);
User user = db.users.Find(1);
user.roles = roles;
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
As I mentioned above, this code inserts new roles (1,3), but it does not delete role 2 (if exists).
I have no clue how to delete record from this junction table, can you help me please?
I know, that when I delete object from one table, all data related with this object are deleted from junction table, but i do not want to delete -> add object since it is related with another tables.
Here is another solution, you firstly have to load the related roles of the user before replacing the whole roles with your new list, like this:
User user = db.users.Include<User, ICollection<Role>>(u=>u.roles)
.FirstOrDefault(u=>u.ID == 1);
if(user != null){
user.roles = roles;
db.SaveChanges();
}
I supposed your roles navigation property has type of ICollection<Role> and the user's key property is ID, you can read more about using Include extension method (declared in QueryableExtensions.
You have to remove role2 from user.
List<Role> roles = new List<Role>();
roles.Add(role1); roles.Add(role3);
User user = db.users.Find(1);
user.roles.Remove(role2);
user.roles = roles;
db.SaveChanges();
First you need to include roles when loading your user:
var user = db.users
.Include(x=>x.Roles)
.Single(x=>x.Id == 1);
Then you need to identify Role to remove:
var roleToRemove = user.roles.Single(x => x.Id == 1);
And Finally you remove the role and save changes
user.Roles.Remove(roleToRemove);
db.SaveChanges();
For DbContext to be able to track changes you have to let it load collections first then just by modifying that collection DbContext will know how to update DB.
PS. You can use lazy load option if collection is large and you don't want to load all linked elements.
The problem is EF doesn't know you want to delete the older roles. When you set the Roles navigation property with the new list, you are only telling to EF that you want to add those new two roles, but you are not changing the state of the older roles to Deleted. There are two options to achieve what you need:
Changing the state to Deleted:
User user = db.users.Find(1);
// change the state to Deleted.
foreach(var r in user.Roles)
db.Entry(r).State = System.Data.Entity.EntityState.Deleted;
// Add the new roles
user.Roles.AddRange(roles);
Using the Remove method:
User user = db.users.Find(1);
// delete older roles
foreach(var r in user.Roles.ToList())
user.Roles.Remove(r);
// Add the new roles
user.Roles.AddRange(roles);

Iterating through datacontext - most efficient way?

I have started using performance wizard in visual studio 2012 because there was a slow method which is basically used to get all users from the datacontext. I fixed the initial problem but I am now curious if I can make it faster.
Currently I am doing this:
public void GetUsers(UserManagerDashboard UserManagerDashboard)
{
try
{
using (GenesisOnlineEnties = new GenesisOnlineEntities())
{
var q = from u in GenesisOnlineEnties.vw_UserManager_Users
select u;
foreach (var user in q)
{
User u = new User();
u.UserID = user.UserId;
u.ApplicationID = user.ApplicationId;
u.UserName = user.UserName;
u.Salutation = user.Salutation;
u.EmailAddress = user.Email;
u.Password = user.Password;
u.FirstName = user.FirstName;
u.Surname = user.LastName;
u.CompanyID = user.CompanyId;
u.CompanyName = user.CompanyName;
u.GroupID = user.GroupId;
u.GroupName = user.GroupName;
u.IsActive = user.IsActive;
u.RowType = user.UserType;
u.MaximumConcurrentUsers = user.MaxConcurrentUsers;
u.ModuleID = user.ModuleId;
u.ModuleName = user.ModuleName;
UserManagerDashboard.GridUsers.users.Add(u);
}
}
}
catch (Exception ex)
{
}
}
It's a very straight forward method. Connect to the database using entity framework, get all users from the view "vw_usermanager_users" and populate the object which is part of a collection.
I was casting ?int to int and I changed the property to ?int so no cast is needed. I know that it is going to take longer because I am looping through records. But is it possible to speed this query up?
Ok, first things first, what does your vw_UserManager_Users object look like? If any of those properties you're referencing are navigational properties:-
public partial class UserManager_User
{
public string GroupName { get { return this.Group.Name; } }
// See how the getter traverses across the "Group" relationship
// to get the name?
}
then you're likely running face-first into this issue - basically you'll be querying your database once for the list of users, and then once (or more) for each user to load the relationships. Some people, when faced with a problem, think "I know, I'll use an O/RM". Now they have N+1 problems.
You're better to use query projection:-
var q = from u in GenesisOnlineEnties.vw_UserManager_Users
select new User()
{
UserID = u.UserId,
ApplicationId = u.ApplicationID,
GroupName = u.Group.Name, // Does the join on the database instead
...
};
That way, the data is already in the right shape, and you only send the columns you actually need across the wire.
If you want to get fancy, you can use AutoMapper to do the query projection for you; saves on some verbosity - especially if you're doing the projection in multiple places:-
var q = GenesisOnlineEnties.vw_UserManager_Users.Project().To<User>();
Next up, what grid are you using? Can you use databinding (or simply replace the Grid's collection) rather than populating it one-by-one with the results from your query?:-
UserManagerDashboard.GridUsers.users = q.ToList();
or:-
UserManagerDashboard.GridUsers.DataSource = q.ToList();
or maybe:-
UserManagerDashboard.GridUsers = new MyGrid(q.ToList());
The way you're adding the users to the grid right now is like moving sand from one bucket to another one grain at a time. If you're making a desktop app it's even worse because adding an item to the grid will probably trigger a redraw of the UI (i.e. one grain at a time and, describing every grain in the bucket to your buddy after each one). Either way you're doing unnecessary work, see what methods your grid gives you to avoid this.
How many users are in the table? If the number is very large, then you'll want to page your results. Make sure that the paging happens on the database rather than after you've got all the data - otherwise it kind of defeats the purpose:-
q = q.Skip(index).Take(pageSize);
though bear in mind that some grids interact with IQueryable to do paging out-of-the-box, in that case you'd just pass q to the grid directly.
Those are the obvious ones. If that doesn't fix your problem, post more code and I'll take a deeper look.
Yes, by turning off change tracking:
var q = from u in GenesisOnlineEnties.vw_UserManager_Users.AsNoTracking()
select u;
Unless you are using all the properties on the entity you can also select only the columns you want.
var q = from u in GenesisOnlineEnties.vw_UserManager_Users.AsNoTracking()
select new User
{
UserId = u.UserId,
...
}

How to join tables using include with entity SQL

I will try to describe my issue in details.I have the following scenario.
1.) I have 3 tables : business, customoffice(Custom Office) and cusdesc(custom office description)
The relationship is that a business has on customoffice and one customoffice has many cusdesc.
The table business has a field customofficeno which is a foreign key to the field cuscode of the customoffice table.The table cusdesc has a field cuscode which is a foreign key to the field cuscode of the customoffice table.
The objective is to select a business including the custom office and custom office description using entity framework.
2.) Code
I have a procedure FillData which fills a datagrid. My objective is to display fields from the 3 tables. I managed to display data from tables "Business" and "Customoffice" but i need to display the description of a custom office via table "cusdesc" and be more specific the field "CSNAME".
3.) My issue is that when I include the ("CUSTOMSOFFICE.CUSDESC") the results do not contain data from table "CUSDESC" but only how many records much the criteria so I cannot access the field "CSNAME"
Hereafter is the procedure:
using (var _context = new ReftabEntities())
{
try
{
SetGlobalValues();
ObjectQuery<BUSINESS> q_business = _context.BUSINESS.Where("it.BUSINESSNO=" + int.Parse(pv_businessno)).Where(string.Format("(it.BUSINESSSTART <= DATETIME'{0:yyyy-MM-dd HH:mm}') and (it.BUSINESSCLOSED >= DATETIME'{0:yyyy-MM-dd HH:mm}')", pv_date)).Include("CUSTOMSOFFICE").Include("CUSTOMSOFFICE.CUSDESC");
gvBusinessList.Caption = "Total records selected: " + q_business.Count();
gvBusinessList.DataSource = q_business;
gvBusinessList.DataBind();
}
catch (Exception e)
{
errorPopup.Text = e.Message;
errorPopup.ShowOnPageLoad = true;
}
finally
{
_context.Dispose();
}
}
}
Can you please give a hint what I do wrong.
Thanks in advance.
The Include operator just asks EF to load a related entity with the query. If you don't use "Include", EF will only extract the properties of BUSINESS and will not extract the properties of the CUSTOMSOFFICE. There is no need for the "Include("CONSOMSOFFICE.CUSDESC")" since you already loaded the entire CUSTOMSOFFICE entity in the first Include.
I see that you're binding the result to the grid view, and if I understand correctly, the issue is that the "CUSTOMSOFFICE.CUSDESC" is not being displayed in the gridview. I believe this is because the gridview tries to render its representation of the "CUSTOMSOFFICE" object itself, since that's the direct property of the items you are binding. To have more control over the "columns" of your gridview, I suggest using LINQ to transform the results of your query into what you explicitly want to display.
I am assuming that BUSINESSNO, BUSINESSSTART, and BUSINESSCLOSED are properties of your Business entity itself, and that BUSINESSNO is the Primary Key. Let me rewrite your query into this:
var q_business = _context.BUSINESS.Include(b=>b.CUSTOMSOFFICE)
.Where(p => p.BUSINESSNO == int.Parse(pv_businessno)
&& p.BUSINESSSTART <= DateTime.Parse(pv_date)
&& p.BUSINESSCLOSED >= DateTime.Parse(pv_date) )
.FirstOrDefault();
This query would extract the details of the Business (including the related CUSTOMSOFFICE details) that matches the given pv_businessno and falls within your date criteria. But you can't bind this to your gridview yet because you might encounter the same problem where the CUSTOMSOFFICE.DESC is not displayed. To ensure proper display, you must identify what properties you want to include. For example, if you only want to display the set of properties below:
BUSINESS.BUSINESSNO
BUSINESS.BUSINESSNAME
BUSINESS.CUSTOMSOFFICE.CUSCODE
BUSINESS.CUSTOMSOFFICE.CUSDESC
You should transform your output to explicitly and immediately include these properties.
var q_business = _context.BUSINESS.Include(b=>b.CUSTOMSOFFICE)
.Where(b => b.BUSINESSNO == int.Parse(pv_businessno)
&& b.BUSINESSSTART <= DateTime.Parse(pv_date)
&& b.BUSINESSCLOSED >= DateTime.Parse(pv_date) )
.Select(b => new {BusinessNo = b.BUSINESSNO,
BusinessName = b.BUSINESSNAME,
CustomsOfficeCode = b.CUSTOMSOFFICE.CUSCODE,
CustomsOfficeDesc = b.CUSTOMSOFFICE.CUSDESC } ) //This Select statement creates a new anonymous type that has Businessno, BusinessName, CustomsOfficeCode, and CustomsOfficeDesc properties
.FirstOrDefault();
When you bind this to your gridview, it should be able to display the value of the CUSDESC property.

Get logged in user's id

How can I get the logged in user's UserId? I'm using the standard system generated AccountModel. I can get the username using:
User.Identity.Name
but I don't see the UserId field. I want to use the UserId as a foreign key for another table.
Try this:
using Microsoft.AspNet.Identity;
User.Identity.GetUserId();
That's how its done in the partial views for current MVC (MVC5/EF6/VS2013) templates.
Correct me if I'm wrong, because I've seen Aviatrix's answers a lot, but what happens if more than one user has the same name in the database?
I think you're looking for ProviderUserKey - Gets the user identifier from the membership data source for the user.
object id = Membership.GetUser().ProviderUserKey
Membership.GetUser() - Gets the information from the data source and updates the last-activity date/time stamp for the current logged-on membership user.
The best way to do so is to use the WebSecurty class
var memberId = WebSecurity.GetUserId(User.Identity.Name);
and don't forget to add [InitializeSimpleMembership] on top of your controller :)
Their are already very good answer but what i understood from your question is that you want to fetch data from database using id.so here is what you can do.
public List<ForeignkeyTable> RV()
{
var email = User.Identity.Name;
Usertable m = db.Uusertables.Where(x => x.user_Email == email).SingleOrDefault();
var id = m.user_Id;
var p = db.ForeignkeyTable.Where(x => x.user_fk_id == id).ToList();
return p;
}
This way you can return all the data from database of that id in foreignkey.

Categories

Resources