EF 4 Feature CTP 4 - Associate Many to Many - entity-framework

I am using EF 4 Feature CTP 4.
I have the following database schema:
[Users] 1-M [UserRoles] M-1 [Roles]
I have a User and Role class (both POCOs).
When I try to associate an existing role to a user, a new record is getting inserted in the Roles table, instead of only inserting a new record in UserRoles.
So say I have User 1 and want to Associate with Role 2. When I try to save, I end up with a new record in Roles named "Role 2" with a record in UserRoles to the newly created Role. From the code below I was expecting only a new record in UserRoles with the mapping between User and Role.
Here is my POCO classes, mapping and test.
POCO Classes:
public class User {
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public byte[] DataVersion { get; set; }
private List<Role> roles = new List<Role>();
public virtual IList<Role> Roles {
get {
return roles;
}
}
}
public class Role {
public int Id { get; set; }
public string Name { get; set; }
public List<User> Users { get; set; }
}
Mapping:
public class UserConfiguration : EntityConfiguration<Domain.User> {
public UserConfiguration() {
this.MapSingleType(user => new {
UserId = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
DataVersion = user.DataVersion
});
this.Property(u => u.DataVersion).IsConcurrencyToken().HasStoreType("timestamp").StoreGeneratedPattern = StoreGeneratedPattern.Computed;
//Users <--> Roles
this.HasMany(u => u.Roles).WithMany(r => r.Users)
.Map("UserRoles", (u, r) => new {
UserId = u.Id,
RoleId = r.Id
});
}
}
public class RoleConfiguration : EntityConfiguration<Domain.Role> {
public RoleConfiguration() {
this.MapSingleType(role => new {
RoleId = role.Id,
RoleName = role.Name
});
}
}
Test:
public void AssignRoleTest() {
var userId = 1;
User user;
var userRepo = new UsersRepository();
user = userRepo.GetUser(userId);
var roleRepo = new RolesRepository();
var roleId = 2;
var role = roleRepo.GetRole(roleId);
user.Roles.Add(role);
userRepo.SaveUser(user);
}
Code for Repository Save:
public void SaveUser(User user) {
if (user.Id > 0) {
dbContext.Users.Attach(user);
dbContext.MarkAsModified(user);
}
else {
dbContext.Users.Add(user);
}

Try to use context.DetectChanges() in your SaveUser method. It should work because you are using same context for both loading and saving entity graph.

Related

Facing issue with Entity framework Db approach

I have these classes
public class UserRole
{
public int RoleId { get; set; }
public int UserId { get; set: }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
[Table("User")]
public class User
{
public User()
{
UserRoles = new HashSet<UserRole>();
}
public int FirstName { get; set; }
public int UserId { get; set: }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
[Table("Role")]
public class Role
{
public Role()
{
UserRoles = new HashSet<UserRole>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
}
I am trying to do simple insert into the UserRole table
public class Run
{
DbContext context;
public Run()
{
context = new DbContext();
}
public void Validate(User user, int roleId)
{
InsertUserRole(user,roleId)
context.SaveChanges();
}
}
public void InsertUserRole(user targetUser, int roleId)
{
UserRole targetUserRole = targetUser.UserRoles
.Where(u => u.RoleId == roleId)
.FirstOrDefault();
if (targetUserRole == null)
{
targetUserRole = new UserRole();
targetUserRole.RoleId = roleId;
targetUserRole.UserId = targetUser.UserId;
context.UserRoles.Add(targetUserRole);
}
}
When I am trying to insert into UserRole table, I get an exception
Violation of Unique KEy 'UQ_Role_Name'.Cannot insert duplicate key in dbo.Role
I need to insert row into UserRole as the role does not exist for user, and the role is present in Role table.
Please let me know how I can insert into Userole table using Entity Framework context
I'm not sure if there is enough info about your entities/schema to narrow down the specific cause of your issue, but a few things do come to mind.
Firstly, how are you nominating the PK for the UserRole entity? This should either be done with attributes in the entity or via entity configuration:
public class UserRole
{
[Key, Column(Order=0), ForeignKey("User")]
public int UserId { get; set; }
[Key, Column(Order=1), ForeignKey("Role")]
public int RoleId { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
Next, to add a new UserRole it should be possible to do so by setting the IDs, though generally I recommend setting the navigation properties rather than the IDs. The reasons for this is that this validates the values you are passing, and the entity should be considered "complete" (navigation properties are available) after saving.
For your InsertUserRole method, there are a couple of adjustments there. You don't need to load the existing entity, a simple exists check would be sufficient and faster, then loading the relevant references. This can either be inserted to a UserRole DBSet, or added to the requested user:
public void InsertUserRole(int userId, int roleId)
{
var userRoleExists = context.UserRoles.Any(x => x.UserId == userId && x.RoleId == roleId);
if(userRoleExists) return;
var user = context.Users.Single(x => x.UserId == userId);
var role = context.Roles.Single(x => x.RoleId == roleId);
UserRole userRole = new UserRole
{
User = user,
Role = role
};
context.UserRoles.Add(userRole);
context.SaveChanges();
}
or alternatively:
public void InsertUserRole(int userId, int roleId)
{
var user = context.Users
.Include(x => x.UserRoles)
.Single(x => x.UserId == userId);
if(user.UserRoles.Any(x => x.RoleId == roleId) return;
var role = context.Roles.Single(x => x.RoleId == roleId);
UserRole userRole = new UserRole
{
User = user,
Role = role
};
user.UserRoles.Add(userRole);
context.SaveChanges();
}

Entity Framework Id Object Name Collision Problem

So I'm running into an annoying problem with EF Core 6.0 where I am getting a naming conflict when mapping using strongly typed Value Objects as Id's.
So the following, using straight Guid's as Id's, works:
public class User {
public Guid Id { get; set; }
public List<UserRole> Roles { get; set; }
}
public class UserRole {
public Guid UserId { get; set; }
public Guid RoleId { get; set; }
}
internal sealed class UserEntityTypeConfiguration : IEntityTypeConfiguration<User> {
public void Configure(EntityTypeBuilder<User> builder) {
builder.ToTable("User", SchemaNames.Application);
builder.HasKey(p => p.Id).HasName("Id");
builder.HasMany(d => d.Roles);
}
}
internal sealed class UserRoleValueTypeConfiguration : IEntityTypeConfiguration<UserRole> {
public void Configure(EntityTypeBuilder<UserRole> builder) {
builder.ToTable("UserRoles", SchemaNames.Application);
builder.HasKey(userRole => new { userRole.UserId, userRole.RoleId });
builder.Property("UserId").HasColumnName("UserId");
builder.Property("RoleId").HasColumnName("RoleId");
}
}
User user = new User() { Id = Guid.NewGuid() };
user.Roles.Add(new UserRole() { UserId = user.Id.Value, RoleId = roleId });
context.Users.AddAsync(user);
However when trying to use strongly typed Id's and perform an Add:
public class UserId : TypedIdValue {
public UserId(Guid value) : base(value) { }
}
public class User {
public UserId Id { get; set; }
public List<UserRole> Roles { get; set; }
}
User user = new User() { Id = Guid.NewGuid() };
user.Roles.Add(new UserRole() { UserId = user.Id.Value, RoleId = roleId });
context.Users.AddAsync(user);
I get - SqlException: Invalid column name 'UserId1'.
I am using a custom ValueConverterSelector and ValueConverter registered with the DbContextOptionsBuilder to facilitate the strongly typed Id's.
Clearly it's tripping up over there being a UserId object for Id and UserId field in the UserRole object, but I'm at a loss as to how to resolve this issue.
Any help would be greatly appreciated.

How to select Entity Framework many-to-many query?

I have a many-to-many relation in my Entity Framework context.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public IList<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int UserId { get; set; }
public int RoleId { get; set; }
}
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public IList<UserRole> UserRoles { get; set; }
}
And my context is:
public class MyContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserRole>().HasKey(sc => new { sc.UserId, sc.RoleId });
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
}
So when a user is assigned to a role, the record is in UserRole table. My roles table has 4 rows of data:
[
{ roleId = 1, name = "Administrator" },
{ roleId = 2, name = "Editor"},
{ roleId = 3, name = "x"},
{ roleId = 4, name = "y"}
]
But I want to select all roles for a user. But user assigned data property should be true like the following. For example I want to select roles for userid = 1. Because 2 role assigned.
roles = [
{ roleId = 1, name = "Administrator", isAddigned = true },
{ roleId = 2, name = "Editor", isAddigned = true },
{ roleId = 3, name = "x", isAddigned = false },
{ roleId = 4, name = "y", isAddigned = false }
]
Bot how can I select this query using Entity Framework?
From what it sounds like you have a user (ID #1) that has 2 roles currently assigned, Administrator and Editor. You want to list all available roles with a flag for "Is Assigned" set to True if the user has that role assignment, False if they do not.
To start, you likely do not need a UserRoles DbSet in your context. It's advisable to only create DbSets for entities that are considered top level entities (stuff you'll query) and rely on relationships to manage the rest.
If your goal is to return a list of Roles to associate to a user with an indicator of whether a user holds them or not (such as for a role assignment screen).
If you maintain UserRoles on the Role:
var roles = context.Roles
.Select(x => new RoleViewModel
{
RoleId = x.RoleId,
Name = x.Name,
IsAssigned = x.UserRoles.Any(ur => ur.UserId == userId)
}).ToList();
return roles;
When faced with a single-direction association (I.e. User contains UserRoles, but Roles does not) A simple first step would be to get the User's assigned role IDs, then use that as check against the complete list of roles. It requires 2 simple queries.
var usersRoleIds = context.Users
.Where(x => x.UserId == userId)
.SelectMany(x => x.UserRoles.Select(ur => RoleId))
.ToList();
var roles = context.Roles
.Select(x => new RoleViewModel
{
RoleId = x.RoleId,
Name = x.Name,
IsAssigned = userRoleIds.Contains(x.RoleId)
}).ToList();
return roles;

EF 6.1 Bidirectional mapping returning null results from ICollection member

I have a set of allowed Roles, and Users who can belong to a number of Roles.
I need to query both the number of Roles, and know from within the User entity to which Roles it belongs.
The first query works, the second query does not (no Roles are returned).
My code is below:
class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Index(IsUnique=true)]
public string Username { get; set; }
public ICollection<Role> Roles { get; set; }
public User()
{
this.Roles = new List<Role>();
}
}
class Role
{
[Key]
public int Id { get; set; }
[Index(IsUnique = true)]
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
class MemberContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
For the below example EF has created 3 databases:
**Roles {Id,Name}**
1, IdentityServerUsers
2, IdentityServerAdministrators
**UserRoles {User_Id,Role_Id}**
5179EF31-C7DD-E311-80BD-00155D458501, 2
**Users {Id,Name}**
5179EF31-C7DD-E311-80BD-00155D458501, admin
I want to be able to run the following queries:
using(var context = new MemberContext())
{
var roles = context.Roles.Where(x => x.Name == "IdentityServerAdministrators").FirstOrDefault();
// roles.Users is empty!!!
var user = context.Users.Where(x => x.Username == "admin").FirstOrDefault();
// user.Roles is empty!!
}
But the roles.Users and user.Roles both return empty, but looking at the above database tables they should have data.
Do I need to explicitly set mapping via EF Code First and what am I doing wrong please?
Update 1
I am now using virtual properties, but still null's are being returned:
class Role
{
[Key]
public int Id { get; set; }
[MaxLength(127)]
[Index(IsUnique = true)]
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
class User
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Index(IsUnique = true)]
[MaxLength(127)]
public string Name { get; set; }
}
using (var context = new MemberContext())
{
var unf1 = context.Roles.Where(x => x.Name == "IdentityServerAdministrators").FirstOrDefault();
var wow1 = unf1.Users;
// roles.Users is empty!!!
var unf2 = context.Users.Where(x => x.Username == "admin").FirstOrDefault();
var wow2 = unf2.Roles;
// user.Roles is empty!!
}
In your queries, you need to explicitly load the related entities since you're not using lazy loading.
using(var context = new MemberContext())
{
var roles = context.Roles.Include(x => x.Users).Where(x => x.Name == "IdentityServerAdministrators").FirstOrDefault();
// roles.Users is empty!!!
var user = context.Users.Include(x => x.Roles).Where(x => x.Username == "admin").FirstOrDefault();
// user.Roles is empty!!
}
If you want the related entities to be lazy loaded, the navigation properties must be virtual.

Update related data when AutoDetectChangesEnabled = false

I'm trying to update a simple model:
public class DayOff
{
public int Id { get; set; }
public List<User> Users { get; set; }
public DayOff()
{
Users = new List<User>();
}
}
my service layer is:
public void Update(DayOff dayOff, IEnumerable<int> users)
{
var dayOffToUpdate = _db.DayOff.Include("Users").Single(d => d.Id == dayOff.Id);
dayOffToUpdate.Users.Clear();
_db.Entry(dayOffToUpdate).CurrentValues.SetValues(dayOff);
dayOffToUpdate.Users = _db.User.Where(u => users.Contains(u.Id)).ToList();
foreach (var user in dayOffToUpdate.Users)
{
_db.Entry(user).State = EntityState.Modified;
}
_db.SaveChanges();
}
but it is NOT updating the users, so how can I?