Can somebody explain why Entity Framework will not create extra columns CurVal and NewVal in join table? It is creating a join table with DeviceFeatureID and UserDeviceID.
public class DeviceFeature
{
[Display(Name = "ID")]
public int DeviceFeatureID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<DeviceType> DeviceTypes { get; set; }
public virtual ICollection<UserDevice> UserDevices { get; set; }
}
public class UserDevice
{
public int UserDeviceID { get; set; }
public int DeviceTypeID { get; set; }
public string UserID { get; set; }
public string DeviceName { get; set;}
public virtual ICollection<DeviceFeature> DeviceFeatures { get; set; }
}
public class UserDeviceFeatureStatus
{
public int UserDeviceID { get; set; }
public int DeviceFeatureID { get; set; }
public virtual UserDevice UserDevice { get; set; }
public virtual DeviceFeature DeviceFeature { get; set; }
public string CurVal { get; set; }
public string NewVal { get; set; }
}
Your many-to-many relationship table is being generated automatically by your Virtual Collections an not by your entity.
You can either include the join entity in the DbContext (but it will probably duplicate the table unless you remove the virtual collections) or you can use the Fluent API to configure the relationship.
To do the later you will need to change your Virtual Collections to the ReleationShip type and add this to your OnModelCreating:
modelBuilder.Entity<UserDeviceFeatureStatus>()
.HasKey(t => new { t.UserDeviceID, t.DeviceFeatureID });
modelBuilder.Entity<UserDeviceFeatureStatus>()
.HasOne(pt => pt.UserDevice)
.WithMany(p => p.DeviceFeatures) // from UserDevice
.HasForeignKey(pt => pt.UserDeviceID);
modelBuilder.Entity<UserDeviceFeatureStatus>()
.HasOne(pt => pt.DeviceFeature)
.WithMany(t => t.UserDevices) //from DeviceFeature
.HasForeignKey(pt => pt.DeviceFeatureID);
In DeviceFeature
public virtual ICollection<UserDeviceFeatureStatus> UserDevices { get; set; }
In UserDevice
public virtual ICollection<UserDeviceFeatureStatus> DeviceFeatures { get; set; }
For more information about EF relationships you can see the docs here.
If you want to make a many-to-many relationship between UserDevice and DeviceFeature through UserDeviceFeatureStatus than you need to have a navigation property from the 2 tables to your "join" table. I understand that you try to navigate directly from UserDevice to DeviceFeature directly and vice-versa. But you won't be able to do that in EF if your relationship table contains other fields than the primary keys of the tables you're trying to link.
You can try this:
public class DeviceFeature
{
[Display(Name = "ID")]
public int DeviceFeatureID { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<DeviceType> DeviceTypes { get; set; }
public virtual ICollection<UserDeviceFeatureStatus> UserDeviceFeatureStatuses { get; set; }
}
public class UserDevice
{
public int UserDeviceID { get; set; }
public int DeviceTypeID { get; set; }
public string UserID { get; set; }
public string DeviceName { get; set;}
public virtual ICollection<UserDeviceFeatureStatus> UserDeviceFeatureStatuses { get; set; }
}
public class UserDeviceFeatureStatus
{
public int UserDeviceID { get; set; }
public int DeviceFeatureID { get; set; }
public virtual UserDevice UserDevice { get; set; }
public virtual DeviceFeature DeviceFeature { get; set; }
public string CurVal { get; set; }
public string NewVal { get; set; }
}
So to navigate from UserDevice to DeviceFeature for example, you'll need to navigate to UserDeviceFeatureStatus first then from there to DeviceFeature.
Related
I am a bit confused about this, I am implementing a Wish List in which multiple users can add the same entity (Product) to their list.
I extended the identity user class and added an ICollection<Product>
but i cant seem to figure out how can multiple users reference the same product in terms of foreign keys, because whenever a user add a product to their wish list it gets deleted from the previous user as the foreign key now references the new user. obviously my logic is flawed and i misunderstood how the relationship should be defined can you please point me in the right direction?
here is the Product entity
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ProductType ProductType { get; set; }
[ForeignKey("Category")]
public int SubcategoryFK { get; set; }
public virtual SubCategory Category { get; set; }
public decimal Price { get; set; }
public int NumberOfItems { get; set; }
public string ImageUrl { get; set; }
public string IsInStock { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
[ForeignKey("SalesUser")]
public string SalesUserFK { get; set; }
public virtual ApplicationUser SalesUser { get; set; }
and the relationship is configured like this
modelBuilder.Entity<ApplicationUser>()
.HasMany(a => a.Products)
.WithOne(p => p.SalesUser).HasForeignKey(z => z.SalesUserFK).OnDelete(DeleteBehavior.ClientSetNull);
As Kei commented , the relationship of Product and User is many-to-many. In EF Core ,Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ProductType ProductType { get; set; }
[ForeignKey("Category")]
public int SubcategoryFK { get; set; }
public virtual SubCategory Category { get; set; }
public decimal Price { get; set; }
public int NumberOfItems { get; set; }
public string ImageUrl { get; set; }
public string IsInStock { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
public List<UserProduct> UserProducts { get; set; }
}
public class ApplicationUser:IdentityUser
{
public List<UserProduct> UserProducts { get; set; }
}
public class UserProduct
{
public int ProductId { get; set; }
public Product Product { get; set; }
public Guid ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
DbContext :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProduct>()
.HasKey(up => new { up.ProductId, up.ApplicationUserId });
modelBuilder.Entity<UserProduct>()
.HasOne(up => up.Product)
.WithMany(p => p.UserProducts)
.HasForeignKey(up => up.ProductId);
modelBuilder.Entity<UserProduct>()
.HasOne(up => up.ApplicationUser)
.WithMany(au => au.UserProducts)
.HasForeignKey(up => up.ApplicationUserId);
}
Reference : Many-to-many relationship and Cascade Delete
I have User table and I'd like to add connection called UserFriend between 2 users. I've searched a lot and basicly tried many different solutions and none of them worked. Everytime I get same error:
Introducing FOREIGN KEY constraint 'FK_UserFriends_Users_Friend2Id' on table 'UserFriends' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Here are my models:
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public string EmailAddress { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public virtual ICollection<UserFriend> Friends { get; set; }
public virtual ICollection<UserFriend> FriendOf { get; set; }
}
public class UserFriend
{
public User Friend1 { get; set; }
public Guid Friend1Id { get; set; }
public User Friend2 { get; set; }
public Guid Friend2Id { get; set; }
public bool Confirmed { get; set; }
public DateTime Added { get; set; }
}
And here's code in DataContext:
modelBuilder.Entity<UserFriend>().HasKey(sc => new { sc.Friend1Id, sc.Friend2Id });
modelBuilder.Entity<UserFriend>()
.HasOne(c => c.Friend1)
.WithMany(c => c.FriendOf)
.HasForeignKey(f => f.Friend1Id);
modelBuilder.Entity<UserFriend>()
.HasOne(c => c.Friend2)
.WithMany(c => c.Friends)
.HasForeignKey(f => f.Friend2Id)
.OnDelete(DeleteBehavior.Restrict);
Change your code to below and remove the other lines you have posted.
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public string EmailAddress { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public virtual ICollection<UserFriend> Friends { get; set; }
public virtual ICollection<UserFriend> FriendOf { get; set; }
}
public class UserFriend
{
public User Friend1 { get; set; }
[ForeignKey("Friend1")]
public Guid? Friend1Id { get; set; }
public User Friend2 { get; set; }
[ForeignKey("Friend2")]
public Guid? Friend2Id { get; set; }
public bool Confirmed { get; set; }
public DateTime Added { get; set; }
}
modelBuilder.Entity<User>();
modelBuilder.Entity<UserFriend>();
I have two classes:
One is User
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<Subscription> Subscriptions { get; set; }
}
Other is Subscription:
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
As you can see that User has a list of Subscriptions.
Now when using the entity framework code first approach I am getting a table for User which doesn't contain Subscriptions but a new column for User Id is being added to Subscription table. I was expecting to have a third table which contains two columns one with User ID and the other with subscription ID.
How can I achieve this?
From documentation:
Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
So this answer is correct.
I just corrected code a little bit:
class MyContext : DbContext
{
public DbSet<Use> Users { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserSubscription>()
.HasKey(t => new { t.UserId, t.SubscriptionId });
modelBuilder.Entity<UserSubscription>()
.HasOne(pt => pt.User)
.WithMany(p => p.UserSubscription)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserSubscription>()
.HasOne(pt => pt.Subscription)
.WithMany(t => t.UserSubscription)
.HasForeignKey(pt => pt.SubscriptionId);
}
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public List<UserSubscription> UserSubscriptions{ get; set; }
}
public class Subscription
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<UserSubscription> UserSubscriptions{ get; set; }
}
public class UserSubscription
{
public int UserId { get; set; }
public User User { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
}
PS. You don't need use virtual in navigation property, because lazy loading still not available in EF Core.
Create a third middle table named: UserSubscriptions for example.
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public virtual ICollection<UserSubscription> Subscriptions { get; set; }
}
public class Subscription
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class UserSubscription
{
public int ID { get; set; }
public int SubscriptionID { get; set; }
public decimal Price { get; set; }
public int UserID { get; set; }
public virtual User { get; set; }
public DateTime BeginDate { get; set; }
public DateTime EndDate { get; set; }
}
Second Solution:
Add reference for Subscription to User and name it CurrentSubscription for example.
public class User
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int CurrentSubscriptionID { get; set; }
public virtual Subscription Subscription { get; set; }
}
public class Subscription
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
I've been for a while trying to find out why the Include clause is not loading the related collection: I have two classes with a one-to-many relationship:
public class AgencyNote : IAutId
{
[Key]
public int aut_id { get; set; }
public string Comment { get; set; }
[DisplayName("Note Created Date")]
public DateTime NoteDate { get; set; }
[DisplayName("Contact Date")]
public DateTime ContactDate { get; set; }
[ForeignKey("tbl_agency")]
public int AgencyId { get; set; }
[DisplayName("User")]
public string RipsUser { get; set; }
public virtual ICollection<AgencyNoteAttachment> AgencyNoteAttachments { get; set; }
public virtual tbl_agency tbl_agency { get; set; }
}
and
public class AgencyNoteAttachment
{
[Key]
public int aut_id { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
public int AgencyNoteId { get; set; }
[NotMapped]
[ForeignKey("AgencyNoteId")]
public virtual AgencyNote AgencyNote { get; set; }
}
Context class:
public DbSet<AgencyNote> AgencyNotes { get; set; }
public DbSet<AgencyNoteAttachment> AgencyNoteAttachments { get; set; }
This is the action where I'm using the Include clause:
private IQueryable<AgencyNote> GetNotes(int agencyId)
{
return _ctx.AgencyNotes
.Include(a => a.tbl_agency)
.Include(a => a.AgencyNoteAttachments)
.OrderByDescending(f => f.NoteDate)
.Where(x => x.AgencyId == agencyId);
}
I'm getting AgencyNotesAttachments always null from this action even if I know it's not null, what's going on? Any question let me know...
If you add just the navigation properties between the related entities, then EF will create the FK column for you in the AgencyNoteAttachment table. Now, EF by convention can interpret AgencyNoteId is the FK of that relationship, but is good idea do that explicitly as you already have in your model or using ForeignKey attribute on FK property:
public class AgencyNoteAttachment
{
[Key]
public int aut_id { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
[ForeignKey("AgencyNote")]
public int AgencyNoteId { get; set; }
public virtual AgencyNote AgencyNote { get; set; }
}
If you want to learn more about conventions, take a look this link
I've posted a question in Programmers: https://softwareengineering.stackexchange.com/questions/315857/entity-framework-code-first-c-class-separation-and-eav
One solution to the problem is Table Splitting in Entity Framework. So far, I've seen how to do this with 2 entities, but not with 3 or more.
Here are the models I want to share a same table with:
[Table("Tournament")]
public partial class PGTournament : IImageable
{
public int Id { get; set; }
public string Name { get; set; }
public GameGenre GameGenre { get; set; }
public TournamentFormat TournamentFormat { get; set; }
public TournamentStatus Status { get; set; }
public string Description { get; set; }
public virtual List<PrizePool> Prizes { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public virtual List<Participants.Participant> Participants { get; set; }
public decimal Cost { get; set; }
public string Streaming { get; set; }
public int? ChallongeTournamentId { get; set; }
public string Bracket { get; set; }
public virtual List<TournamentMatch> Matches { get; set; }
public int MainImageId { get; set; }
public virtual Media MainImage { get; set; }
public bool IsFollowUp { get; set; }
public int? FollowUpTournamentId { get; set; }
[ForeignKey("FollowUpTournamentId")]
public virtual PGTournament FollowUptournament { get; set; }
public int MediaID { get; set; }
public int MainImageID { get; set; }
//Properties that share same table:
public virtual TournamentOrganizer Organizer { get; set; } //Change to Organizer
public virtual TournamentOrganizerSetting OrganizerSetting { get; set; }
public virtual TournamentSettings TournamentSettings { get; set; }
public virtual TournamentRules Rules { get; set; }
}
All the properties you see that are virtual and don't have a List<> as their type, I want them to share a same table (If it is possible).
[Table("Tournament")]
public partial class TournamentOrganizer
{
public int Id { get; set; }
public string Name { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public AppUser User { get; set; }
public int LogoId { get; set; }
[ForeignKey("LogoId")]
public Media Logo { get; set; }
public virtual TournamentOrganizerSetting Settings { get; set; }
public virtual TournamentRules Rules { get; set; }
public virtual TournamentSettings TournamentSettings { get; set; }
public virtual PGTournament Tournament { get; set; }
}
[Table("Tournament")]
public partial class TournamentSettings
{
public int Id { get; set; }
public string Address { get; set; }
public string LocationGoogleMaps { get; set; }
public bool isOnline { get; set; }
public int MaxPlayers { get; set; }
public List<TournamentAssistant> TournamentAssistants { get; set; }
public virtual TournamentOrganizer Organizer { get; set; } //Change to Organizer
public virtual TournamentRules Rules { get; set; }
public virtual TournamentOrganizerSetting OrganizerSettings { get; set; }
public virtual PGTournament Tournament { get; set; }
}
[Table("Tournament")]
public partial class TournamentOrganizerSetting
{
public int Id { get; set; }
public string Location { get; set; }
//Properties that share same table:
public virtual TournamentOrganizer Organizer { get; set; } //Change to Organizer
public virtual TournamentRules Rules { get; set; }
public virtual TournamentSettings TournamentSettings { get; set; }
public virtual PGTournament Tournament { get; set; }
}
[Table("Tournament")]
public partial class TournamentRules
{
public int Id { get; set; }
public string Bans { get; set; }
public string Allowed { get; set; }
public string Description { get; set; }
public string FileName { get; set; }
public string FilePath { get; set; }
//Properties that share same table:
public virtual TournamentOrganizer Organizer { get; set; } //Change to Organizer
public virtual TournamentOrganizerSetting OrganizerSetting { get; set; }
public virtual TournamentSettings TournamentSettings { get; set; }
public virtual PGTournament Tournament { get; set; }
}
I don't know why the classes are partial. I've been following several tutorials over the Internet, such as this: http://www.c-sharpcorner.com/UploadFile/ff2f08/table-splitting-in-entity-framework-6-code-first-approach/
I can't get them to work.
I have even tried this in the DbModelBuilder:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PGTournament>().ToTable("Tournament");
modelBuilder.Entity<PGTournament>()
.HasKey(e => e.Id)
.HasOptional(e => e.FollowUptournament)
.WithMany();
modelBuilder.Entity<PGTournament>()
.HasKey(e => e.Id)
.HasRequired(e => e.Organizer)
.WithRequiredDependent(e => e.Organizer)
modelBuilder.Entity<TournamentOrganizer>()
.HasKey(e => e.Id)
.HasRequired(e => e.Settings)
.WithRequiredDependent(e => e.Organizer);
modelBuilder.Entity<TournamentViewModel>()
.HasKey(e => e.Id)
.HasRequired(e => e.Settings)
.WithRequiredDependent(e => e.Organizer);
modelBuilder.Entity<TournamentOrganizer>().Map(m => m.ToTable("Tournament"));
modelBuilder.Entity<TournamentOrganizerSetting>().Map(m => m.ToTable("Tournament"));
base.OnModelCreating(modelBuilder);
}
There doesn't seem to be a StackOverflow post with Mapping to 3 or more entities.
When I try to run it, this is the error I get:
One or more validation errors were detected during model generation:
Pro_Gaming.Infrastructure.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
Pro_Gaming.Infrastructure.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is based on type 'IdentityUserLogin' that has no keys defined.
IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.
This answer comes from Cole Wu from ASP.NET forums:
http://forums.asp.net/p/2093110/6043922.aspx?p=True&t=635968548324560382
The answer is the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Do not delete this:
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Tournament>()
.HasKey(e => e.TournamentId)
.HasRequired(e => e.Rules)
.WithRequiredPrincipal();
modelBuilder.Entity<Tournament>()
.HasKey(e => e.TournamentId)
.HasRequired(e => e.TournamentSettings)
.WithRequiredDependent();
modelBuilder.Entity<TournamentOrganizer>()
.HasKey(e => e.Id)
.HasRequired(e => e.Settings)
.WithRequiredDependent();
modelBuilder.Entity<Tournament>().ToTable("Tournament");
modelBuilder.Entity<TournamentRules>().ToTable("Tournament");
modelBuilder.Entity<TournamentSettings>().ToTable("Tournament");
modelBuilder.Entity<TournamentOrganizer>().ToTable("TournamentOrganizer");
modelBuilder.Entity<TournamentOrganizerSetting>().ToTable("TournamentOrganizer");
}
Explaining a bit:
There is no need for partial classes (I say this because there is an
example that states that you need partial classes, this is not
true):
I haven't tested this, but I used the same key for all the classes that I wanted to share the same table.
modelBuilder.Entity <= TheEntity will be the main class you want everything mapped to.
If you are using ASP.NET Identity and you are extending from IdentityDbContext (which is my case), It is very important
to include base.OnModelCreating(modelBuilder) in the OnModelCreating method, otherwise you'll be hit with Identityissues that it doesn't find the primary key for IdenittyUser.
You would then use:
modelBuilder.Entity.ToTable("MyTable")
modelBuilder.Entity.ToTable("MyTable")
modelBuilder.Entity.ToTable("MyTable")
This will map Entity1, Entity2, Entity3, etc to MyTable.