Here's the problem. I have table User which have quite a few fields. What I want to do is split this table into multiple entities like this:
User
-> GeneralDetails
-> CommunicationDetails
-> Address
etc.
All goes well when extracting some fields from User into GeneralDetails. However, when I try to do the same thing for CommunicationDetails EF blows up and require to establish one-to-one relationship between GeneralDetails and CommunicationDetails.
Sample entities definition:
public class User {
public int UserId { get; set; }
public string SomeField1 { get; set; }
public int SomeField2 { get; set; }
public virtual GeneralDetails GeneralDetails { get; set; }
public virtual CommunicationDetails CommunicationDetails { get; set; }
public virtual Address Address { get; set; }
}
public class GeneralDetails {
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public virtual User User { get;set; }
}
public class CommunicationDetails {
[Key]
public int UserId { get; set; }
public string Phone { get; set; }
public string DeviceToken { get; set; }
public virtual User User { get;set; }
}
public class Address {
[Key]
public int UserId { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Street { get; set; }
public virtual User User { get;set; }
}
Sample mapping:
modelBuilder.Entity<User>().
HasRequired(user => user.GeneralDetails).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().
HasRequired(user => user.CommunicationDetails).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().
HasRequired(user => user.Address).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<GeneralDetails>().ToTable("Users");
modelBuilder.Entity<Address>().ToTable("Users");
Why on earth EF want this relationship? Is there any way this could be solved?
The correct way to actually do this is by Complex Types rather than entities. Its actually a more common problem than you think.
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelbuilder.ComplexType<CommunicationDetails>();
modelbuilder.ComplexType<GeneralDetails>();
modelbuilder.ComplexType<Address>();
modelbuilder.Entity<User>().ToTable("Users");
}
}
Related
I am trying to set up audit properties for each of my Entities with an abstract Base class
public abstract class Base
{
public bool IsActive { get; set; }
public bool IsDeleted { get; set; }
public int CreatedByUserId { get; set; }
[ForeignKey("CreatedByUserId")]
public virtual User CreatedBy { get; set; }
public int ModifiedByUserId { get; set; }
[ForeignKey("ModifiedByUserId")]
public virtual User ModifiedBy { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
}
Somehow the Data Annotations doesn't work in EF Core but was working in my EF 6 Project
I am now receiving this error:
Unable to determine the relationship represented by navigation 'Address.CreatedBy' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
These are my models:
public class Address : Base
{
public int Id { get; set; }
public string StringAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User : Base
{
public int Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string SecondaryContactNumber { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
public HashSet<Address> Addresses { get; set; }
}
What's weird is when I remove the Base inheritance from my other entities apart from User, EF Core is able to set the FK without any errors.
How do I configure it manually with Fluent API?
I already have a BaseConfig class as starting point to be inherited by my other entity config classes:
public class BaseConfig<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : Base
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(x => x.DateCreated).HasDefaultValueSql("GETDATE()");
builder.Property(x => x.DateModified).HasDefaultValueSql("GETDATE()");
// Am I setting this correctly?
builder
.HasOne(b => b.CreatedBy)
.WithMany()
.HasForeignKey(p => p.CreatedByUserId);
}
}
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 designing a system and one of my entity has one to many relation as shown below.
public class Product
{
public int Id { get; set; }
}
public class CompetitorProduct
{
public int Id { get; set; }
public Product Product { get; set; }
}
competitorProduct indicates that product has a equivalent which is sold by different store. should i define one-to-many relation as shown above or below? which one is correct?
public class Product
{
public int Id { get; set; }
public virtual ICollection<CompetitorProduct> CompetitorProducts{ get; set; }
}
public class CompetitorProduct
{
public int Id { get; set; }
}
Assuming it is a one to many relationship (what would happen if a competitor product was competing with more than one of your products for example) you can do both and add in a foreign key as well.
public class Product
{
public int Id { get; set; }
public virtual ICollection<CompetitorProduct> CompetitorProducts { get; set; }
}
public class CompetitorProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
You can then set up your relationship using fluent API as so:
modelBuilder.Entity<CompetitorProduct>(entity =>
{
entity.HasOne(e => e.Product)
.WithMany(e => e.CompetitorProducts)
.HasForeignKey(e => e.ProductId)
.HasConstraintName("FK_ComptetitorProduct_Product");
});
This way you can access the competitor products from the product and the product from the competitor products.
Here is a quick example of a ecommerce site I have worked on and how we did table relations.
I removed a bunch of the fields so you can see what you really need. Once to make relations and run Add-Migration EF will handle the FK constraints for you as long as you identified them in models like how I have below.
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
Active = true;
CreateDateTimeUtc = DateTime.UtcNow;
ModifiedDateTimeUtc = DateTime.UtcNow;
}
[StringLength(500)]
public string FirstName { get; set; }
[StringLength(500)]
public string LastName { get; set; }
[StringLength(1000)]
public string Address { get; set; }
[StringLength(100)]
public string Unit { get; set; }
[StringLength(250)]
public string City { get; set; }
[StringLength(25)]
public string State { get; set; }
[StringLength(20)]
public string ZipCode { get; set; }
//This will give access to a list of child carts a user could have
[Index]
public bool Active { get; set; }
public virtual ICollection<Cart> Carts { get; set; }
// Account Profile Image
public byte[] ProfileImage { get; set; }
[StringLength(500)]
public string ProfileFilename { get; set; }
[StringLength(100)]
public string ProfileMimeType { get; set; }
}
[Table("Cart", Schema = "dbo")]
public class Cart : AbstractTable
{
public Cart()
{
IsComplete = false;
}
//This create relation to user table where I can get one unique user.
[StringLength(128)]
[ForeignKey("ApplicationUser")]
public string UserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
//These link us to child tables of Cart where we can get a LIST of the items below
public virtual ICollection<CartCategory> CartCategories { get; set; }
public virtual ICollection<CartItem> CartItems { get; set; }
// Marked when a payment/receipt is generated based off of this cart
public bool IsComplete { get; set; }
}
[Table("CartItem", Schema = "dbo")]
public class CartItem : AbstractTable
{
//This will return one unique cart id and let us access it as the parent record
[ForeignKey("Cart")]
public Guid CartId { get; set; }
public virtual Cart Cart { get; set; }
// Signifies if this was paid for in a receipt
public bool IsComplete { get; set; }
public virtual ICollection<CartItemCustomField> CustomFields { get; set; }
}
I want to create this database schema in ASP.NET Core:
I already tried it with this code without success.
User
public class User : IdentityUser
{
public override string Id { get; set; }
public virtual UserVisitors Visitors { get; set; }
}
UserVisitors
public class UserVisitors
{
public int Id { get; set; }
public string UserId { get; set; }
public string UserVisitorId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime VisitTime { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
[ForeignKey("UserVisitorId")]
public virtual User UserVisitor { get; set; }
}
I'm getting this error on the migrations:
Unable to determine the relationship represented by navigation property >'User.Visitors' of type 'UserVisitors'. Either manually configure the >relationship, or ignore this property using the '[NotMapped]' attribute or >by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I hope someone can help me. Thanks in advance.
Update:
Changed UserVisitors to:
public class UserVisitors
{
public int Id { get; set; }
[InverseProperty("User")]
public string UserId { get; set; }
[InverseProperty("User")]
public string UserVisitorId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime VisitTime { get; set; }
public virtual User User { get; set; }
}
I can compile it now, but on the database is it wrong.
Database image:
The column UserVisitorId doesn't have a relationship to User.UserID.
public class User : IdentityUser
{
public override string Id { get; set; }
public virtual ICollection<UserVisitor> UserVisitors { get; set; }
public virtual ICollection<UserVisitor> UserVisitors2 { get; set; }
}
public class UserVisitor
{
public int Id { get; set; }
[InverseProperty("User")]
public string UserId { get; set; }
public virtual User User {get; set;}
public virtual User UserVisitor {get; set;}
[InverseProperty("User")]
public string UserVisitorId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime VisitTime { get; set; }
public virtual User User { get; set; }
}
Then you need to make proper mappings using OnModelCreatig as follows:
modelBuilder.Entity<UserVisitor>()
.HasOne(x => x.UserVisitor)
.WithMany(x => x.UserVisitors)
modelBuilder.Entity<UserVisitor>()
.HasOne(x => x.User)
.WithMany(x => x.UserVisitors2)
Say I have two models - House and User
class User
{
[Key, Required]
public Guid UserId { get; set; }
public virtual House ILiveHere { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
}
And
class House
{
[Key, Required]
public Guid HouseId { get; set; }
[Required]
public virtual User HouseOwner { get; set; }
}
Many users can be associated with one house so it is a one-to-many relationship. However, there is also another relationship which is a one-to-one relationship between the owner of the house and the house. How would you define this with data annotations? Thanks
Give this a try... (this is EF Core)
public class MyDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<House> Houses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"...");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<House>().HasMany(x => x.Residents).WithOne(x => x.House);
}
}
public class User
{
[Key, Required]
public Guid UserId { get; set; }
public virtual House House { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
}
public class House
{
[Key, Required]
public Guid HouseId { get; set; }
public List<User> Residents { get; set; }
[Required]
public virtual User HouseOwner { get; set; }
}