I want to create a one-to-many relationship using EF 6 using a code-first approach.
Let's take simple and classical example. I have two entities Invoice and UserApplication which have a one-to-many relationship:
I also want to have an UpdatedById relationship with the same ApplicationUser table, to be able to show the names in the UI of who added the record and who modified it.
public partial class ApplicationUser : IdentityUser
{
public string FirstName { get; set; };
public string LastName { get; set; };
}
public virtual List<Invoice> Invoices { get; set; }
public class Invoice
{
public int Id { get; set; }
public string Details { get; set; }
public string CreatedById { get; set; }
public string UpdatedById { get; set; }
}
public virtual ApplicationUser CreatedBy { get; set; }
builder.Entity<Invoice>()
.HasOne(f => f.CreatedBy)
.WithMany(mu => mu.Invoices)
.HasForeignKey(f => f.CreatedById);
If you want Navigation Properties on Application user for these relationships, you would need to create and configure seperate ones.
eg
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System;
using System.Collections.Generic;
namespace EfCore6Test
{
public partial class ApplicationUser //: IdentityUser
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Invoice> InvoicesCreated { get; } = new HashSet<Invoice>();
public virtual ICollection<Invoice> InvoicesLastUpdated { get; } = new HashSet<Invoice>();
}
public class Invoice
{
public int Id { get; set; }
public string Details { get; set; }
public int CreatedById { get; set; }
public int UpdatedById { get; set; }
public virtual ApplicationUser CreatedBy { get; set; }
public virtual ApplicationUser LastUpdatdBy { get; set; }
}
public class Db: DbContext
{
public DbSet<Invoice> Invoices{ get; set; }
public DbSet<ApplicationUser> Users{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>()
.HasOne(f => f.CreatedBy)
.WithMany(mu => mu.InvoicesCreated)
.HasForeignKey(f => f.CreatedById)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Invoice>()
.HasOne(f => f.LastUpdatdBy)
.WithMany(mu => mu.InvoicesLastUpdated)
.HasForeignKey(f => f.UpdatedById)
.OnDelete(DeleteBehavior.Restrict);
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;database=efCore6Test;Integrated Security=true;TrustServerCertificate=true", o => o.UseRelationalNulls(true))
.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
{
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
}
}
}
}
Or simply omit the Navigation Properties on Application User.
Related
In project I can have one User that can have many UserActivites. In my models I've set up their relationship as follows:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
//relationship mapping example
// delete these attributes and you'll cause a self referenceing loop error
[JsonIgnore]
[IgnoreDataMember]
public List<UserActivity> Activities { get; set; }
}
public class UserActivity
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Project { get; set; }
public DateTime EntryDate { get; set; }
// relationship mapping
public User User { get; set; }
}
And in my repository class, I'm getting all my user activities this way:
public async Task<IEnumerable<UserActivity>> GetAll()
{
var result = await _context.UserActivities.Include(activity => activity.User).OrderByDescending(x => x.EntryDate).ToListAsync();
return result;
}
However, when I run my project, the User property of UserActivities is null. So I checked the Microsoft docs on EF Core relationships and updated my OnModelCreating method inside of my context to also do the mapping as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserActivity>().ToTable("UserActivities").Property(x => x.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<UserActivity>().ToTable("UserActivities").HasOne(x => x.User).WithMany();
modelBuilder.Entity<User>().ToTable("Users").Property(x => x.Id).ValueGeneratedOnAdd();
base.OnModelCreating(modelBuilder);
}
However, when I run the project again, my User property still isn't populated. I know this isn't a data issue as I have data inside of my User table and display that on a separate page.
I'm not sure what I'm doing wrong/missing with this. So any help would be appreciated
Try this code:
Your tables:
public partial class User
{
public User()
{
UserActivities = new HashSet<UserActivity>();
}
[Key]
public int Id { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string Email { get; set; }
[InverseProperty(nameof(UserActivity.User))]
public virtual ICollection<UserActivity> UserActivities { get; set; }
}
public partial class UserActivity
{
[Key]
public int Id { get; set; }
public int UserId { get; set; }
public string Project { get; set; }
public DateTime EntryDate { get; set; }
[ForeignKey(nameof(UserId))]
[InverseProperty("UserActivity")]
public virtual User User { get; set; }
}
dbcontext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserActivity>(entity =>
{
entity.HasOne(d => d.User)
.WithMany(p => p.UserActivities)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_UserActivity_User");
});
OnModelCreatingPartial(modelBuilder);
}
I am using EF Core 5 and have extended EF Core Identity to manage user auth. But when I migrate my Entity objects to the database it creates extra columns in the database for example:
AspNetUserRole table creates 4 columns in the database UserId UserId1 RoleId RoleId1 in the database.
Here are my extended classes to give you more insights:
ASP.NET USER ENTITY:
public class AspNetUser : IdentityUser
{
public AspNetUser()
{
UserRoles = new List<AspNetUserRoles>();
AspNetUserShippingInfo = new List<AspNetUserShippingInfo>();
AspNetUserPaymentInfo = new List<AspNetUserPaymentInfo>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExternalUserId { get; set; }
public string ExternalProviderName { get; set; }
public bool VerifiedEmail { get; set; }
[ForeignKey("Vendor")]
public int? VendorId { get; set; }
public virtual Vendor Vendor { get; set; }
//TODO: remove from here. doesn't belong here maybe
[ForeignKey("DeliveryCompany")]
public int? DeliveryCompanyId { get; set; }
public virtual DeliveryCompanies DeliveryCompany { get; set; }
public virtual IList<AspNetUserShippingInfo> AspNetUserShippingInfo { get; set; }
public virtual IList<AspNetUserPaymentInfo> AspNetUserPaymentInfo { get; set; }
public virtual IList<AspNetUserRoles> UserRoles { get; set; }
public int? PreferredZipCode { get; set; }
public string PreferredLanuage { get; set; }
}
ASP.NET ROLE ENTITY:
public class AspNetRoles : IdentityRole<string>
{
public AspNetRoles()
{
UserRoles = new List<AspNetUserRoles>();
}
public virtual IList<AspNetUserRoles> UserRoles { get; set; }
}
ASP.NET USER ROLE ENTITY:
public class AspNetUserRoles : IdentityUserRole<string>
{
public AspNetUserRoles() : base()
{
}
public override string UserId { get; set; }
[ForeignKey("UserId")]
public virtual AspNetUser User { get; set; }
public override string RoleId { get; set; }
[ForeignKey("RoleId")]
public virtual AspNetRoles Role { get; set; }
}
I'm not sure what I'm doing wrong that generates these extra columns in the database. any help will be highly appreciated. Thanks
You can do the following step to solve your issue.
1:change your AspNetUserRoles
public class AspNetUserRoles : IdentityUserRole<string>
{
public AspNetUserRoles() : base()
{
}
public override string UserId { get; set; }
public virtual AspNetUser User { get; set; }
public override string RoleId { get; set; }
public virtual AspNetRoles Role { get; set; }
}
2:In your context:
public class ApplicationDbContext : IdentityDbContext<AspNetUser, AspNetRoles, string,
IdentityUserClaim<string>, AspNetUserRoles, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//add this code,do not set a composite primary key.
modelBuilder.Entity<AspNetUser>(b =>
{
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<AspNetRoles>(b =>
{
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
});
}
3:Remigration and update database.
If You Don't Include This Code Then Your Code Works Fine Your AspNetUserRoles Class Look Like This
public class AspNetUserRoles : IdentityUserRole<string>
{
public AspNetUserRoles() : base()
{
}
[Key]
public virtual AspNetUser User { get; set; }
[Key]
public virtual AspNetRoles Role { get; set; }
}
You Can Also Do Something Like This
public class AspNetUser : IdentityUser
{
public AspNetUser()
{
this.Role=new HashSet<AspNetRoles>();
}
public virtual ICollection<AspNetRoles> Role { get; set; }
}
public class AspNetRoles : IdentityRole<string>
{
public AspNetRoles()
{
this.User=new Hashest<AspNetUser>();
}
public virtual ICollection<AspNetUser> User { get; set; }
}
If you Go With Second Option Then You don't Need To add Extra Class AspNetUserRoles for Many To Many Relationship
Just Try This Last code it working Fine in my computer
public class AspNetRoles
{
public string RoleId { get; set; }
public string USerId { get; set; }
}
I'm using entity core 3. I have 2 classes User and Ticket. User may have many Ticket's to me and many Ticket's from me. Ticket should have User-sender and User-receiver. I did so:
public class User
{
public int Id { get; set; }
public string PasswordHash { get; set; }
public string Email { get;
public ICollection<Ticket> TicketsToMe { get; set; }
public ICollection<Ticket> TicketsFromMe { get; set; }
}
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public int UserToId { get; set; }
public int UserFromId { get; set; }
public User UserTo { get; set; }
public User UserFrom { get; set; }
}
And I got the error: Unable to determine the relationship represented by navigation property 'Ticket.UserTo' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Do you have any ideas?:)
You can use one of these
1 - Metadata. you can use InverseProperty to define the relationships.
if you use the metadata you should set UserToId and UserFromId to Nullable
public class User
{
public int Id { get; set; }
public string PasswordHash { get; set; }
public string Email { get; set; }
[InverseProperty("UserTo")]
public ICollection<Ticket> TicketsToMe { get; set; }
[InverseProperty("UserFrom")]
public ICollection<Ticket> TicketsFromMe { get; set; }
}
2 - FluentApi
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(a => a.TicketsFromMe)
.WithOne(a => a.UserFrom)
.HasForeignKey(a => a.UserFromId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<User>()
.HasMany(a => a.TicketsToMe)
.WithOne(a => a.UserTo)
.HasForeignKey(a => a.UserToId).OnDelete(DeleteBehavior.Restrict);
}
I want to implement one-to-many relationship with EF6. Table User can handle many Friends, i can implement that with map table:
--TABLE USERS:
Id
--TABLE USER_MAP:
UserOwnerId
UserFriendId
But how to implement that with EF6?
Here is my entity User:
public class User
{
...
public virtual List<User> Friends { get; set; }
...
}
You can use something like this
// Relationships
HasRequired(t => t.User)
.WithMany(t => t.Friends)
.HasForeignKey(d => d.UserId);
https://msdn.microsoft.com/en-us/data/hh134698.aspx
One-to-Many relationship using DataAnnotations:
public class User
{
public User() { }
public int UserId { get; set; }
public string Name { get; set; }
public virtual Friends friends { get; set; }
}
public class Friends
{
public Friends()
{
Users = new List<User>();
}
public int FriendId { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
you can defined in Code first like that:
1) Fluent API:
public class Student
{
public Student() { }
public int StudentId { get; set; }
public string StudentName { get; set; }
public virtual Standard Standard { get; set; }
}
public class Standard
{
public Standard()
{
Students = new List<Student>();
}
public int StandardId { get; set; }
public string Description { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Fleut Api:
in your DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//one-to-many
modelBuilder.Entity<Student>()
.HasRequired<Standard>(s => s.Standard)
.WithMany(s => s.Students);
}
virtual keyword is only for Lazy loading you can remove it if you do not need it
2) Code first:
public class Student
{
public Student()
{
Students= new List<Student>();
}
public int StundendId{ get; set; }
public string StudentName { get; set; }
public int? SharedStudentId{ get; set; }
[ForeignKey("SharedStudentId")]
public Student SharedStudent{ get; set; }
public virtual ICollection<Student> SharedStudents{ get; set; }
}
I'm using CodeFirst for my devemopment. For all model classes in my Entity I have a base class named CommonFields
public class CommonFields
{
public int Status { get; set; }
public DateTime CreatedOn { get; set; }
public int CreaedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public int ModifiedBy { get; set; }
}
And, for eg. I have two classes like
public class Employee : CommonFields
{
public int Id { get; set; }
public string Name { get; set; }
//Other properties
}
public class User : CommonFields
{
public int Id { get; set; }
public string Name { get; set; }
//Other properties
}
How can I set relation from CreatedBy & ModifiedBy to User table. I just need only one directional mapping.
I need to get User information when I write objEmployee.CreatedUser
Thanks.
public class CommonFields
{
public int Status { get; set; }
public DateTime CreatedOn { get; set; }
public int? CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public virtual User ModifiedBy { get; set; }
public int? ModifiedById { get; set; }
}
and you have to define relations for all derived entities in your DbContext
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<User>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<User>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
mb.Entity<Employee>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<Employee>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
}
EDIT:
Or you could use TPH. Then your model creating look like this
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<CommonFields>().
.Map(x => x.ToTable("Users"))
.Map<User>(x => x.Requires("__type").HasValue(1)
.Map<Employee>(x => x.Requires("__type").HasValue(2);
mb.Entity<CommonFields>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<CommonFields>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
}