EFCore 6 many to many relationships - entity-framework

I know many to many relationships are not automatically done in previous versions of EF, but am I forgetting something because I can not retrieve a list.
I have the following data
public class User
{
public int Id { get; set; }
public List<UserGroup> Groups { get; set; }
}
public class UserGroup
{
public int Id { get; set; }
public void AddUser(User user)
{
Users.Add(user);
user.Groups.Add(this);
}
}
When checking the data based I find I have the groups point to the users.
When retrieving the group from the database it is empty (no users)
[HttpGet]
public async Task<IActionResult> GetUsers(int id)
{
var group = (await context.Groups.Include(userGroup => userGroup.Users).SingleAsync(group => group.Id == id));
var users = group.Users;
Console.WriteLine(users.ToArray()); //empty
Console.WriteLine(group.name); // prints group name correctly
return Ok(users.ToArray());
}
I'm a little stumped, did I forget something?

Related

EF Core One to one relathionship with AspNetUsers table and cascade delete

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);
});

Return dropdown string from second table

I have two tables I need to join for my Razor Page view. The first table called 'Account' contains an Account record with a int Status. The second table called 'AccountStatuses' contains possible statuses for the Account. Scaffolding created the following code in Account\Index.cshtml.cs
public IList<Account> Account { get;set; }
public async Task OnGetAsync()
{
Account = await _context.Account.ToListAsync();
}
The Account table contains a column "Status" that corresponds to the column "Value" in the AccountStatus table. I want to join on these and return the column "StatusString" from the AccountStatus table to the view.
You do not have to join the two tables to get the values. If you setup properly your models you can let Entity Framework do the work for you. I will give you an example of how I would create the models. First of all, we have the two models:
public class Account
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AccountID { get; set; }
public string AccountName { get; set; }
public int AccountStatusID { get; set; }
[ForeignKey("AccountStatusID")]
public virtual AccountStatus AccountStatus { get; set; }
}
public class AccountStatus
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AccountStatusID { get; set; }
public string AccountStatusName { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
The Account model has the property AccountStatusID which will contain the id for the status. We also define a virtual property for the AccountStatus model. This will be automatically loaded by EntityFramework for us when we ask it from Entity Framework.
We do something similar for the AccountStatus model but in this model we will have a virtual collection of Account models.
Now we will have to define our ApplicationDbContext class which could be the following:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
public DbSet<Account> Accounts { get; set; }
public DbSet<AccountStatus> AccountStatuses { get; set; }
}
Now we can execute the following queries:
// Get the account by id
Account account1 = await _context.Accounts.SingleOrDefaultAsync(m => m.AccountID == id);
// Get the account by id including the Account status
Account account2 = await _context.Accounts.Include(m => m.AccountStatus).SingleOrDefaultAsync(m => m.AccountID == id);
// account2.AccountStatus contains the AccountStatus
string AccountStatusName = account2.AccountStatus.AccountStatusName;
// Get teh account status by id
AccountStatus AccountStatus1 = await _context.AccountStatuses.SingleOrDefaultAsync(m => m.AccountStatusID == id);
// Get the account status by id include the accounts
AccountStatus AccountStatus2 = await _context.AccountStatuses.Include(m => m.Accounts).SingleOrDefaultAsync(m => m.AccountStatusID == id);
// AccountStatus2.Accounts contain all the accounts which has be set to be equal to the current account status
foreach (var account in AccountStatus2.Accounts)
{
string AccountName = account.AccountName;
}
I hope it helps you.

Entity Framework Code First: One-to-Many relation (empty list)

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

Updating entity framework entity with many to many relationship

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);

Entity Framework Many to Many issues while fetching data

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.