EF Core Cannot Save Related Data one to many - c#

Im trying to save a new user into the users table with a new role. Here's the model and configuration.
When I insert the below entity, The role table gets populated with the new role, but the user entity is not being inserted.
`var userEntity = new User
{
Id = 0,
Active = true,
Firstname = "Fname",
Lastname = "Lname",
Password = "test",
Phonenumber = "2223334444",
Test = false,
Username = "test#test.com",
RoleId = 0
// Role = roleEntity
};
var roleEntity = new Role
{
Id = 0,
Name = "test role",
Active = true,
Users = new List<User>
{
userEntity
}
};
context.Role.Add(roleEntity);
await context.SaveChangesAsync();
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Firstname { get; set; }
public string Middlename { get; set; }
public string Lastname { get; set; }
public string Phonenumber { get; set; }
public bool Active { get; set; }
public bool Test { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
}
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public virtual ICollection<User> Users { get; set; }
public Role()
{
Users = new HashSet<User>();
}
}`
Here are the entity configurations.
`
public class RoleConfiguration : IEntityTypeConfiguration<Role>
{
public void Configure(EntityTypeBuilder<Role> builder)
{
builder.ToTable("Role");
builder.HasKey(r => r.Id);
builder.Property(r => r.Id).IsRequired().ValueGeneratedOnAdd();
builder.Property(r => r.Name).IsRequired().HasMaxLength(255);
builder.Property(r => r.Active).IsRequired();
builder.HasMany(r => r.Users)
.WithOne(u => u.Role)
.HasForeignKey(u => u.RoleId)
.OnDelete(DeleteBehavior.Restrict);
}
}
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("User");
builder.HasKey(u => u.Id);
builder.Property(u => u.Id).IsRequired().ValueGeneratedOnAdd();
builder.Property(u => u.Username).IsRequired().HasMaxLength(255);
builder.Property(u => u.Password).IsRequired();
builder.Property(u => u.Firstname).IsRequired().HasMaxLength(255);
builder.Property(u => u.Lastname).IsRequired().HasMaxLength(255);
builder.Property(u => u.Phonenumber).HasMaxLength(255);
builder.Property(u => u.Active).IsRequired();
builder.Property(u => u.Test).IsRequired();
builder.Property(u => u.RoleId).IsRequired();
builder.HasOne(u => u.Role)
.WithMany(r => r.Users)
.HasForeignKey(u => u.RoleId)
.OnDelete(DeleteBehavior.Restrict);
}
}`
Is there anything I'm doing wrong.
Thanks for your help.

I made a mistake by using
context.Entry(entity).State = EntityState.Added;
instead of
context.Add(entity);
that's why the related entities weren't being added.

Related

Getting all users with roles in ASP.NET Core

I am configuring identity user, I have seeded users with roles
by many to many relationship as follows:
public class AppUser : IdentityUser
{
public string DisplayName { get; set;}
public ICollection<AppUserRole> UserRoles { get; set;}
}
public class AppRole : IdentityRole
{
public ICollection<AppUserRole> UserRoles { get; set; }
}
public class AppUserRole : IdentityUserRole<string>
{
public AppUser User { get; set; }
public AppRole Role { get; set; }
}
builder.Entity<AppUser>()
.HasMany(ur => ur.UserRoles)
.WithOne(u => u.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
builder.Entity<AppRole>()
.HasMany(ur => ur.UserRoles)
.WithOne(u => u.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
In the screenshot - why am I getting the shadow id UserId1, RoleId1?
I am trying to get all user with their respective roles or roles
public UsersController(UserManager<AppUser> userManager)
{
_userManager = userManager;
}
public async Task<ActionResult> GetUsersWithRoles()
{
var users = await _userManager.Users
.Include(r => r.UserRoles)
.ThenInclude(r => r.Role)
// .OrderBy(u => u.UserName)
.Select(u => new
{
// u.Id,
Username = u.UserName,
DisplayName = u.DisplayName,
Role = u.UserRoles.Select(r => r.Role.Name).ToList()
})
.ToListAsync();
return Ok(users);
}
I am getting the users with empty role array... but I think this code should work
i think you should add it in AppUserRole
builder.HasKey(a => new { a.UserId, a.RoleId });
then this is ideal dto
public class DtoSelectedUsersAdmin
{
public string UserName { get; set; }
public string Password { get; set; }
public string Gender { get; set; }
public int Age { get; set; }
public int AppRoleId { get; set; }
public string AppRoleName { get; set; }
public virtual ICollection<AppUserRole> AppRole { get; set; }
public override void CustomMappings(IMappingExpression<AppUser, DtoSelectedUsersAdmin> mapping)
{
.ForMember(a => a.AppRoleName, s => s.MapFrom(q => q.Roles.FirstOrDefault().AppRole.Name))
.ForMember(a => a.AppRoleId, s => s.MapFrom(q => q.Roles.FirstOrDefault().AppRole.Id));
}
}
and finally service class to get all users with their role
public async Task<List<DtoSelectedUsersAdmin>> GetAllUsersWithRolesAsync()
{
var result = await Users.Select(user => new DtoSelectedUsersAdmin
{
Id = user.Id,
AppRole = user.Roles,
UserName = user.UserName
}).ToListAsync();
return result;
}

EF Core not loading related entities

My original code:
public static User GetUser(int userID)
{
_context.Users.Where(x => x.UserId == userID)
.FirstOrDefault();
}
Here user.usergovernments is null.
New code:
public static User GetUser(int userID)
{
using (var _context = new SafetyContext())
{
// find lga for this user
var user_govs = from o in _context.Users
join i in _context.UserGovernments
on o.UserId equals i.UserId
where o.UserId == userID
select new { o, i };
var user = _context.Users
.Where(x => x.UserId == userID)
.FirstOrDefault();
foreach (var lga in user_govs)
{
user.UserGovernments.Add(new UserGovernment { UserId = userID, UserGovernmentId = lga.i.UserGovernmentId, LocalGovId = lga.i.LocalGovId, StateId = lga.i.StateId });
}
return user;
}
}
This time I get duplicate usergovernment records! One is loaded and the other is the one I added!
Model classes:
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public UserAccess AccessLevel { get; set; }
public ICollection<UserGovernment> UserGovernments { get; set; }
}
public class UserGovernment
{
public int UserGovernmentId { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int StateId { get; set; }
public State State { get; set; }
public int LocalGovId { get; set; }
public LocalGov LocalGov { get; set; }
}
public class LocalGov
{
public int LocalGovId { get; set; }
public string Name { get; set; }
public string LgaPid { get; set; }
public ICollection<UserGovernment> UserGovernments { get; set; }
}
Context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserGovernment>()
.HasKey(bc => new {bc.UserGovernmentId});
modelBuilder.Entity<UserGovernment>()
.HasOne(bc => bc.User)
.WithMany(b => b.UserGovernments)
.HasForeignKey(bc => bc.UserId);
modelBuilder.Entity<UserGovernment>()
.HasOne(bc => bc.LocalGov)
.WithMany(c => c.UserGovernments)
.HasForeignKey(bc => bc.LocalGovId);
}
What am I doing wrong?
LocalGov and User are individual entities while UserGovernments is the many-to-many joining table/entity
Write Query as like bellow.
Include method fill your UserGovermnet property automaticaly according to it's matched userId
_context.Users.Where(x => x.UserId == userID)
.Include(u=>u.UserGoverments)
.FirstOrDefault();

EF Core Identity - Applicationuser with one-to-one relationship with another entity returning null

I have the following one-to-one relationship with ApplicationUser:
public class Person
{
public Guid PersonId { get; set; }
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Company { get; set; }
public string Role { get; set; }
public string Country { get; set; }
public DateTime CreatedDate { get; set; }
public bool IsActive { get; set; }
public ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser
{
public Guid PersonId { get; set; }
public string Provider { get; set; } = "LOCAL";
public string ExternalUserId { get; set; }
public Person Person { get; set; }
}
DbContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> opts) : base(opts) { }
public DbSet<Person> Person { get; set; }
public DbSet<ApplicationUser> User { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new PersonConfiguration());
modelBuilder.Entity<ApplicationUser>(e => {
e.ToTable(name: "User");
e.HasOne(p => p.Person).WithOne(u => u.User);
//e.HasOne(p => p.Person).WithOne().HasForeignKey;
});
modelBuilder.Entity<IdentityRole>(e => e.ToTable(name: "Role"));
modelBuilder.Entity<IdentityUserRole<string>>(e => e.ToTable(name: "UserRole"));
modelBuilder.Entity<IdentityUserClaim<string>>(e => e.ToTable(name: "UserClaim"));
modelBuilder.Entity<IdentityUserLogin<string>>(e => e.ToTable(name: "UserLogin"));
modelBuilder.Entity<IdentityUserToken<string>>(e => e.ToTable(name: "UserToken"));
modelBuilder.Entity<IdentityRoleClaim<string>>(e => e.ToTable(name: "RoleClaim"));
}
}
Person entity configuration:
public class PersonConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.ToTable("Person");
builder.HasOne(u => u.User)
.WithOne(p => p.Person)
.HasForeignKey<Person>(p => p.UserId);
}
}
The problem is when I get the person data from db the related user returns null even using the Include extension.
FYI: I've tried to load the users from db using the dbcontext but it returns null too.
I tested your code,I think there is no problem with your configuration,so,the issue may caused by your data insert.You can try to add a new Person and try to find them like bellow:
var user = new ApplicationUser
{
Email = "www.example.com"
};
var p = new Person
{
FirstName = "AA",
//...
User = user,
};
_context.Persons.Add(p);
_context.SaveChanges();
var u = _context.User.ToList();
var pe = _context.Persons.Include(c => c.User).ToList();

The filter expression cannot be specified for entity type. A filter may only be applied to the root entity type in a hierarchy

I am having trouble with this error when Adding new migration.
The filter expression 'e => Not(e.IsDeleted)' cannot be specified for entity type 'Babysitter'. A filter may only be applied to the root entity type in a hierarchy.
What I am doing is that I have 2 classes Babysitter and Parent that both need to be ApplicationUsers, because they have different properties. So I made them inherit the ApplicationUser class and extended them.
This is the ApplicationUser class.
public class ApplicationUser : IdentityUser, IAuditInfo, IDeletableEntity
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
this.Roles = new HashSet<IdentityUserRole<string>>();
this.Claims = new HashSet<IdentityUserClaim<string>>();
this.Logins = new HashSet<IdentityUserLogin<string>>();
}
// Audit info
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
// Deletable entity
public bool IsDeleted { get; set; }
public DateTime? DeletedOn { get; set; }
public virtual ICollection<IdentityUserRole<string>> Roles { get; set; }
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
}
These are the Babysitter and Parent classes.
public class Babysitter : ApplicationUser
{
public Babysitter()
{
this.Appointments = new HashSet<Appointment>();
this.Comments = new HashSet<Comment>();
}
public string Name { get; set; }
public int Age { get; set; }
public Gender Gender { get; set; }
public DateTime DateOfBirth { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public decimal WageRate { get; set; }
public string Address { get; set; }
public decimal Rating { get; set; }
public ICollection<Comment> Comments { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
public class Parent : ApplicationUser
{
public Parent()
{
this.Comments = new HashSet<Comment>();
this.Kids = new HashSet<Kid>();
this.Appointments = new HashSet<Appointment>();
}
public string Name { get; set; }
public string ImageUrl { get; set; }
public decimal Rating { get; set; }
public string Address { get; set; }
public ICollection<Comment> Comments { get; set; }
public ICollection<Kid> Kids { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
And so when I try to Add-Migration Initial I get this error: The filter expression 'e => Not(e.IsDeleted)' cannot be specified for entity type 'Babysitter'. A filter may only be applied to the root entity type in a hierarchy.
This is the ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
private static readonly MethodInfo SetIsDeletedQueryFilterMethod =
typeof(ApplicationDbContext).GetMethod(
nameof(SetIsDeletedQueryFilter),
BindingFlags.NonPublic | BindingFlags.Static);
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Babysitter> Babysitters{ get; set; }
public DbSet<Parent> Parents { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Kid> Kids{ get; set; }
public DbSet<Appointment> Appointments { get; set; }
public DbSet<Setting> Settings { get; set; }
public override int SaveChanges() => this.SaveChanges(true);
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
this.ApplyAuditInfoRules();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) =>
this.SaveChangesAsync(true, cancellationToken);
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default)
{
this.ApplyAuditInfoRules();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
protected override void OnModelCreating(ModelBuilder builder)
{
// Needed for Identity models configuration
base.OnModelCreating(builder);
ConfigureUserIdentityRelations(builder);
EntityIndexesConfiguration.Configure(builder);
var entityTypes = builder.Model.GetEntityTypes().ToList();
// Set global query filter for not deleted entities only
var deletableEntityTypes = entityTypes
.Where(et => et.ClrType != null && typeof(IDeletableEntity).IsAssignableFrom(et.ClrType));
foreach (var deletableEntityType in deletableEntityTypes)
{
var method = SetIsDeletedQueryFilterMethod.MakeGenericMethod(deletableEntityType.ClrType);
method.Invoke(null, new object[] { builder });
}
// Disable cascade delete
var foreignKeys = entityTypes
.SelectMany(e => e.GetForeignKeys().Where(f => f.DeleteBehavior == DeleteBehavior.Cascade));
foreach (var foreignKey in foreignKeys)
{
foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
}
}
private static void ConfigureUserIdentityRelations(ModelBuilder builder)
{
builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
private static void SetIsDeletedQueryFilter<T>(ModelBuilder builder)
where T : class, IDeletableEntity
{
builder.Entity<T>().HasQueryFilter(e => !e.IsDeleted);
}
private void ApplyAuditInfoRules()
{
var changedEntries = this.ChangeTracker
.Entries()
.Where(e =>
e.Entity is IAuditInfo &&
(e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entry in changedEntries)
{
var entity = (IAuditInfo)entry.Entity;
if (entry.State == EntityState.Added && entity.CreatedOn == default)
{
entity.CreatedOn = DateTime.UtcNow;
}
else
{
entity.ModifiedOn = DateTime.UtcNow;
}
}
}
}
So you're trying to add your filter by convention;
// Set global query filter for not deleted entities only
var deletableEntityTypes = entityTypes
.Where(et => et.ClrType != null && typeof(IDeletableEntity).IsAssignableFrom(et.ClrType));
foreach (var deletableEntityType in deletableEntityTypes)
{
var method = SetIsDeletedQueryFilterMethod.MakeGenericMethod(deletableEntityType.ClrType);
method.Invoke(null, new object[] { builder });
}
But that is matching against all three types; Babysitter, Parent, ApplicationUser. The error message is telling you that in a table hierarchy, only apply filters to base types;
.Where(et => et.ClrType != null
&& typeof(IDeletableEntity).IsAssignableFrom(et.ClrType)
&& et.BaseType == null)

Unique Constraint Not Working with Entity Framework Core

I'm using Entity Framework Core 2.2.6 and I have two entities Profile and Category. Each Category will have ProfileId to identify to which Profile it belongs. I'm trying to enforce uniqueness for Name and ProfileId in Category. But however my unique constraint fails.
Here is my Entities,
BaseEntity:
public class BaseEntity<TKey>
{
public TKey Id { get; set; }
public bool Active { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public string CreatedBy { get; set; }
public DateTimeOffset? ModifiedAt { get; set; }
public string ModifiedBy { get; set; }
}
Profile:
public class Profile : BaseEntity<Guid>, IAggregateRoot
{
private Profile()
{
// required by EF
}
public Profile(string brandName)
{
Guard.Against.NullOrEmpty(brandName, nameof(brandName));
BrandName = brandName;
}
public string BrandName { get; set; }
public string Caption { get; set; }
}
Category:
public class Category : BaseEntity<Guid>, IAggregateRoot
{
private Category()
{
// required by EF
}
public Category(string name)
{
Guard.Against.NullOrEmpty(name, nameof(name));
Name = name;
}
public Category(string name, string code) : this(name)
{
Guard.Against.NullOrEmpty(code, nameof(code));
Code = code;
}
public string Name { get; set; }
public string Code { get; set; }
public Guid ProfileId { get; set; }
}
Category Entity Configuration:
public class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.HasAlternateKey(c => new { c.ProfileId, c.Name });
// builder.HasIndex(c => new { c.ProfileId, c.Name }).IsUnique();
builder.Property(c => c.Name)
.IsRequired()
.HasMaxLength(50);
builder.Property(c => c.Code)
.HasMaxLength(10);
builder.HasOne<Profile>()
.WithMany()
.HasForeignKey(p => p.ProfileId)
.IsRequired();
}
}
I tried builder.HasAlternateKey(c => new { c.ProfileId, c.Name }); and builder.HasIndex(c => new { c.ProfileId, c.Name }).IsUnique();. But both doesn't seem to work. Please can you assist on where I go wrong?

Categories

Resources