I have (2) entities, they are as follows:
[Table("User")]
public class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Required]
[MaxLength(20)]
public string Name { get; set; }
public ICollection<Role> Roles { get; set; }
public User()
{
this.Roles = new Collection<Role>();
}
}
[Table("User")]
public class Role
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int RoleId{ get; set; }
[Required]
[MaxLength(20)]
public string Name { get; set; }
public ICollection<User> Users { get; set; }
public Role()
{
this.Users = new Collection<User>();
}
}
This creates three tables in my database, User, Role and UserRole.
I am leveraging a generic repository, my Add and Update look like the following:
public virtual void Add(TEntity entity)
{
_dbSet.Add(entity);
}
public virtual void Update(TEntity entity)
{
_dbSet.Attach(entity);
_dbContext.Entry(entity).State = EntityState.Modified;
}
If I want to add a new User with Roles, I have to do the following in my UserRepository, which inherits from my generic repository.
public override void Add(User entity)
{
foreach (var role in entity.Roles)
_dbContext.Roles.Attach(role);
base.Add(entity);
}
This seems clunky, but it works.
My trouble is now when I want to update a User, say add a new Role. I thought I could do something similar.
public override void Update(User entity)
{
foreach (var role in entity.Roles)
_dbContext.Roles.Attach(role);
base.Update(entity);
}
But this does not work ... any ideas on what I am doing wrong would be appreciated!
Update
My use case is I have an existing User with X Roles, I add Y number of Roles, I want to update the User with the Y number of new Roles.
You shouldn't need to do that. If the role does not yet exist, you would do something like this:
var user = new User { Name="Fred" }
user.Roles.Add(new Role { Name="Accounting" });
context.Add(user);
If you are adding an existing role, then you need to get that role first
var role = context.Roles.Single(x => x.Name="Accounting");
var user = new User { Name="Fred" }
user.Roles.Add(role);
context.Add(user);
Related
I am struggling to setup a scenario (using C# classes and OnModelCreating() method) where DB models would act as follows:
First assumption, the mandatory one:
I want to have the ability to create User (AspNetUsers table) without a reference to a Guest. It will be necessary while seeding a DB with an admin user - it will not belong to any of the Event-s.
In summary - at least that's my understanding - User will be PRINCIPAL, Guest will be DEPENDENT (?)
Cascading deletion: I want to delete Users from AspNetUsers table when I delete a given Event (cascade delete).
This functionality already exists for Guests. When I delete an Event, all related Guests are being deleted correctly.
Two questions:
1. How do I actually create Guests that are related to AspNetUsers table?
When it comes to Guests list and its assignement to a ceratin Event, I just do something like:
eventDbObject.Guests = GetGuestsList();
_dbContext.Events.Add(evenDbObject); //Event is created in Events table, Guests table is correctly populated as well
With users it's tricky - I have to Register them first, get their ID, and then assign that ID to a Guest object. Is that way correct?
foreach (var guest in weddingDbObject.Guests)
{
var userCreationResult = await _identityService.RegisterAsync("userName","password"); // my RegisterAsync() method returns actual User
guest.AppUser = userCreationResult.User;
}
2. How to set up cascade deletion in such a scenario?
builder
.Entity<Guest>()
.HasOne(e => e.AppUser)
.WithOne(e => e.Guest)
.OnDelete(DeleteBehavior.Cascade);
Something like this does not seem to work
My classes:
public class Event
{
// PK
public Guid Id {get;set;}
public List<Guest> Guests { get; set; }
}
public class Guest
{
// PK
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Guid AppUserId { get; set; }
public AppUser AppUser { get; set; }
}
public class AppUser : IdentityUser<Guid>
{
public WeddingGuest Guest { get; set; }
}
Ok, this is what I ended up with, assumptions are met, everything works as expected.
public async Task<string> AddEventAsync(Event event)
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
try
{
// Add an event to [Events] table and corresponding guests to [Guests] table
_dbContext.Events.Add(event);
// Add users to [AspNetUsers] table
foreach (var guest in event.Guests)
{
var userCreationResult = await _identityService.RegisterAsync(guest.Id, $"{event.Name}-{guest.FirstName}-{guest.LastName}", guest.GeneratedPassword);
if (!userCreationResult.Success)
throw new Exception();
guest.AppUser = userCreationResult.User;
}
transaction.Commit();
_dbContext.SaveChanges();
}
catch
{
transaction.Rollback();
}
}
return event.Name;
}
Classes look like this:
public class Event
{
// PK
public Guid Id {get;set;}
public List<Guest> Guests { get; set; }
}
// PRINCIPAL
public class Guest
{
// PK
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[JsonIgnore]
public AppUser AppUser { get; set; }
}
// DEPENDENT
public class AppUser : IdentityUser<Guid>
{
public Guid? GuestId { get; set; } // '?' allows for 1:0 relationship
[JsonIgnore]
public Guest Guest { get; set; }
}
Fluent API DbContext configuration:
builder.Entity<AppUser>(entity =>
{
entity.HasOne(wg => wg.Guest)
.WithOne(a => a.AppUser)
.HasForeignKey<AppUser>(a => a.GuestId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
});
I have a WCF project and I generate the database with Entity framework v6 and code-first.
But I have a problem with the relation between the class User and FeedRss. I want several FeedRss for each User. My code work (no exception) but don't add in the ICollection feeds (in user), this list is empty after the recovery in the database.
public class User
{
[Key]
public int UserID {get; set;}
...
[InverseProperty("User")]
public ICollection<FeedRSS> feeds { get; set; }
public User()
{
feeds = new List<FeedRSS>();
}
}
one user->many feedRss
public class FeedRSS
{
[Key]
public int ID { get; set; }
...
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
public FeedRSS()
{
}
}
public class UsersContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<FeedRSS> Feeds { get; set; }
}
My function for test my code (but return a empty list) :
public User getUser(int Id)
{
using (UsersContext context = new UsersContext())
{
return context.Users.ToList().Find(
delegate(User u) {
return u.UserID == Id;
});
}
}
public List<FeedRSS> getFeedListTest(User u)
{
using (UsersContext ctx = new UsersContext())
{
User user = ctx.Users.First(i => i.UserID == u.UserID);
FeedRSS f = new FeedRSS() { name = "code", link = "uri" };
user.feeds.Add(f);
//the list user.feeds lenght is = 1
ctx.SaveChanges();
//update working
}
//get the same user in the database but the list uuu.feeds lenght is 0 :(
User uuu = this.getUser(u.UserID);
return uuu.feeds.ToList();
}
I tested other code very different (fluent API, force the UserId in FeedRss..) but I do not understand the principle of the relation in entity framework... I tried unsuccessfully several examples code...
*And sorry for my approximate English
You can either load feeds with Include(...) statement as CodeNotFound suggested or you can make the feeds collection virtual - that will enable lazy loading and EF will load feeds for you automatically on the fly.
public class User {
...
[InverseProperty("User")]
public virtual ICollection<FeedRSS> feeds { get; set; }
}
You can find a nice article about lazy loading and eager loading on the MSDN portal
I have two simple classes
public class User
{
public User()
{
Roles = new Collection<Role>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
public Role()
{
Users = new Collection<User>();
}
public int RoleId { get; set; }
public string RoleName { get; set; }
public ICollection<User> Users { get; set; }
}
I have seeded it with the following data by overriding the seed method
var firstUser = new User {UserName = "vivekr"};
var secondUser = new User {UserName = "vivekm"};
var firstRole = new Role {RoleName = "admin"};
var secondRole = new Role {RoleName = "user"};
firstUser.Roles.Add(firstRole);
firstUser.Roles.Add(secondRole);
secondUser.Roles.Add(firstRole);
context.Users.Add(firstUser);
context.Users.Add(secondUser);
Mapping is done by overriding OnModelCreating()
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany<Role>(r=> r.Roles)
.WithMany(u => u.Users)
.Map(c=>
{
c.MapLeftKey("UserId");
c.MapRightKey("RoleId");
c.ToTable("UserRoles");
});
}
All tables are created correctly and I can see that the values are correct(including mappings)
But I there are issues when fetching the data
If I do(assuming that db is an instance of my Context class)
var selectedRoles = db.Users.Find(1).Roles;
I get the count of selectedRoles to be 0. It is supposed to be 2. I have no idea why this is happening
you need to use Include(). When you use include you can't use find anymore. Try something like this:
db.Users.Include("Roles ").FirstOrDefault(x => x.UserId == 1).Roles;
Entity framework will not automatically load related objects for you. You have to tell it to do so by eagerly fetching like above or by using lazy load.
Here is a good article that explains different techniques for pulling in related data.
Confusing Situation
I have a situation where I have 2 entities where 1 inherits from the other, that need to map to 2 separate tables, but code use should be around the base of the 2 entities.
Details
public class Team
{
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
We have an existing database schema where Employee and EmployeeInfo are separate tables with a FK between EmployeeInfo_Id and Employee_Id.
In our system "managers" will be adding Employee's to the system, with a set of private information (more properties than listed above) like pay, and add them to a Team. Other areas of the system will be using the Team or Employee objects for various other things. We would like to have to code super simple if the mapping can be done.
When a manager creates a new employee we would like the code to look something like this:
public void Foo(string name, decimal pay)
{
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Pay = pay;
// add him/her to the team
_team.Employees.Add(employee); // the idea being that consumers of the Team entity would not get the separate employee info properties
// save the context
_context.SaveChanges();
}
The end result would be that the EmployeeInfo properties entered into the EmployeeInfo table and the base Employee data is entered into the Employee table and added to the Team via the association table TeamEmployees.
So far I'm trying the current mappings, and I get an invalid column named "Discriminator." When just adding an employee to a team.
public class TeamConfiguration : EntityTypeConfiguration<Team>
{
public TeamConfiguration()
{
ToTable("Team");
HasKey(t => t.Id);
HasMany(t => t.Members).WithMany(m => m.Teams)
.Map(m =>
{
m.MapLeftKey("Team_Id");
m.MapRightKey("Employee_Id");
m.ToTable("TeamEmployees");
});
}
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
ToTable("Employee");
ToTable("EmployeeInfo");
HasKey(t => t.Id);
Property(p => p.Name);
HasMany(m => m.Teams)
.WithMany(t => t.Members)
.Map(m =>
{
m.MapLeftKey("Employee_Id");
m.MapRightKey("Team_Id");
m.ToTable("TeamEmployees");
});
}
}
Also, if I take the many-to-many between team and employee out of the mix I get a FK exception on Employee_Id to EmployeeInfo_Id.
Thanks, JR.
Discriminator is a column that's being added to your table when you use Table Per Hierarchy approach.
I think what you're looking for is "Table per Type (TPT)". Decorate your EmployeeInfo class as follows:
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
Or add below to your OnModelCreating event:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<EmployeeInfo>().ToTable("EmployeeInfo");
...
}
Or, create the following class and use it like modelBuilder.Configurations.Add(new EmployeeInfoConfiguration()); in OnModelCreating method:
public class EmployeeInfoConfiguration : EntityTypeConfiguration<EmployeeInfo>
{
public EmployeeInfoConfiguration()
{
ToTable("EmployeeInfo");
}
}
This will cause EF to create EmployeeInfo table with necessary constraints.
Also, it's good to initialize your collections in your objects' constructors to prevent null exception. For example in Team class:
public Team()
{
this.Employees = new HashSet<Employee>();
}
I copied your code exactly, and changed the following parts:
public class Team
{
public Team()
{
this.Members = new HashSet<Employee>();
}
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public Employee()
{
this.Teams = new HashSet<Team>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
In the DbContext, no changes:
public partial class TestEntities : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<EmployeeInfo> Employee_Info { get; set; }
public DbSet<Team> Teams { get; set; }
}
and your working Foo method:
public static void Foo(string name, decimal pay)
{
var _team = new Team();
var context = new TestEntities();
context.Teams.Add(_team);
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Amount = pay;
context.Employees.Add(employee);
context.SaveChanges();
// add him/her to the team
_team.Members.Add(employee);
// save the context
context.SaveChanges();
}
Finally, remove ToTable("EmployeeInfo"); part from EmployeeConfiguration since you have mentioned this correctly in your mode creating event.
For more info about Table Per Type approach, check out this great article.
I have an existing database. At the moment I am trying to map my new Entity objects to that DB with entity framework code first. Below is the User class which has a friends collection. As you can see this is a many-to-many relationship to the same table. How can I map this relation to table "user_friend" which has columns "user_id" and "friend_id".
public class User
{
private ICollection<User> _friends = new List<User>();
public ICollection<User> Friends { get{return _firends;} }
}
moduleBuilder.Entity<User>().HasMany????.ToTable("user_friend");
You need to drop down to fluent API for this:
public class User
{
public int UserId { get; set; }
public ICollection<User> Friends { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(u => u.Friends).WithMany().Map(c =>
{
c.MapLeftKey(u=>u.UserID, "user_id");
c.MapRightKey(f=>f.FriendID, "friend_id");
c.ToTable("user_friend");
});
}
}