I'm working on a database where tables have composite keys and part of that key is shared between tables. I don't know how to set the relationship properly in entity.
Imagine the following:
public class Sale
{
public long ID { get; set; } //Key
public long RetailerID { get; set; } //Key
public virtual Location Location { get; set; } //Foreign, Many-to-One
}
public class Location
{
public long ID { get; set; } //Key
public long RetailerID { get; set; } //Key
public virtual IEnumerable<Sale> Sales { get; set; } //Relationship, One-to-Many
}
Both are using the fluent API to define the composite keys OnModelCreating.
modelBuilder.Entity<Sale>().HasKey(x => new { x.RetailerID, x.ID });
modelBuilder.Entity<Location>().HasKey(x => new { x.RetailerID, x.ID });
However I am unsure how to finish this to set up the proper relationship as it sets itself up as having duplicate columns for RetailerID which is unnecessary. How is this supposed to be done properly (if at all?)
It's possible in several ways, all including additional LocationID FK property (either explicit or shadow).
With shadow FK property (without modifying the entity model):
Data Annotations:
[Required]
[ForeignKey("RetailerID, LocationID")]
public virtual Location Location { get; set; } //Foreign, Many-to-One
Fluent API:
modelBuilder.Entity<Sale>()
.HasOne(e => e.Location)
.WithMany(e => e.Sales)
.HasForeignKey("RetailerID", "LocationID")
.IsRequired();
with explicit FK property
Model:
public long LocationID { get; set; } // added
public virtual Location Location { get; set; } //Foreign, Many-to-One
Data annotations:
[ForeignKey("RetailerID, LocationID")]
public virtual Location Location { get; set; } //Foreign, Many-to-One
Fluent API:
modelBuilder.Entity<Sale>()
.HasOne(e => e.Location)
.WithMany(e => e.Sales)
.HasForeignKey(e => new { e.RetailerID, e.LocationID });
(note: use either data annotations or fluent API - no need for both)
Related
I have a program written with a database-first approach; I have a table ServicePlan and another ServicePlanDetails. They are not mapped to each other, but they have a common column PlanId; a servicePlan can contain multiple ServicePlanDetails like a list of it.
I don't want to make any change to the database, but I want to map them as well. How can I do this? Does doing this within the method of on model creating will do the work for me and will not change anything in the database? I have tried this but could get the result.
For simplicity, I have just added few columns and their mapping and not all of them:
public partial class ServicePlan
{
public ServicePlan()
{
ServicePlanDetails = new HashSet<ServicePlanDetail>();
}
public long PlanId { get; set; }
public decimal PhoneId { get; set; }
public byte? NLines { get; set; }
public DateTime? DateStart { get; set; }
public DateTime? DateEnd { get; set; }
public virtual ICollection<ServicePlanDetail> ServicePlanDetails { get; set; }
}
public partial class ServicePlanDetail
{
public long PlanId { get; set; }
public string? ServCode { get; set; }
public string? CountryCode { get; set; }
public bool? IsPlan { get; set; }
public decimal? Cost { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ServicePlan>(entity =>
{
entity.HasKey(e => e.PlanId).HasName("PK_UsersPlan");
entity.ToTable("ServicePlan");
entity.HasIndex(e => e.VideoTronId, "IDX_VTID").HasFillFactor(80);
entity.HasIndex(e => new { e.PhoneId, e.IsApproved }, "Ix_SrvcPlan").HasFillFactor(80);
entity.Property(e => e.Zone).HasMaxLength(50);
entity.HasMany(p => p.ServicePlanDetails)
.WithOne()
.HasPrincipalKey(p => p.PlanId)
.HasForeignKey(p => p.PlanId);
});
}
The error I get is :
Unable to determine the relationship represented by navigation 'ServicePlan.ServicePlanDetails' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.'
I want to get the serviceplandetails with the same planid as the serviceplan into a list in the serviceplan.
PlanId cannot be both foreign and principal key for one to many.
public partial class ServicePlanDetail
{
public long Id { get; set; }
public long PlanId { get; set; }
public string? ServCode { get; set; }
public string? CountryCode { get; set; }
public bool? IsPlan { get; set; }
public decimal? Cost { get; set; }
}
Configuration
entity.HasMany(p => p.ServicePlanDetails)
.WithOne()
.HasPrincipalKey(p => p.PlanId)
.HasForeignKey(p => p.PlanId);
If in the database a Plan can have many ServicePlanDetails, and you link them by Plan ID, how do you differentiate one ServicePlanDetail against that Plan from another? What makes two ServicePlanDetail records unique? That is the crux of your problem. Your FK mapping is correct, but it won't work if PlanId is the PK on ServicePlanDetail. PKs must uniquely identify a single record. For instance if your plan is associated to service plan details applying to various users where multiple users reference the same plan and there is a UserID on ServicePlanId, the PK should be a composite of PlanId + UserId.
As a DB-First approach the database should already have the PKs and constraints set up. You just set up EF keys and relationship types to match that.
Now if the ServicePlanDetail's PK is declared as just PlanId, then the answer is that the relationship between Plan and ServicePlanDetail is 1-to-1, not 1-to-many. This becomes a .HasOne(p => p.ServicePlanDetail).WithOne(sp => sp.Plan) and there's really nothing you can do about that without altering the data relationships. You cannot magically change the relationship that EF will use if the underlying database schema cannot support that relationship.
I am trying to do a database model for my social network school project. I am using Entity Framework 6. The problem is how should I model my Friendship and Chat entity.
I get this error:
Unhandled Exception:
System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
Friendship_Chat_Source: : Multiplicity is not valid in Role 'Friendship_Chat_Source' in relationship 'Friendship_Chat'. Because the dependent role properties are not the key properties, the upper bound of the multiplicity of the dependent role must be '*'.
It has probably something to do with this: EF Code-First One-to-one relationship: Multiplicity is not valid in Role * in relationship
I don't really get it how should I do it here. Even if I get rid off this error somehow (different db model), entity framework in Friendship table creates User_Id column as a foreign key, and I dont really know why it does it (I don't want it there). But I really feel that my model should look like this, not different. So I want to really figure out, how should I edit this model. However if you have different model idea I would also appreciate it.
User entity:
public class User
{
public int Id { get; set; }
[Required, MinLength(1), MaxLength(50)]
public string NickName { get; set; }
[Required, MinLength(6), MaxLength(50)]
public string PasswordHash { get; set; }
#region Settings
//true if anyone can see user posts
public Visibility PostVisibilityPreference { get; set; } = Visibility.Visible;
#endregion
#region Navigation properties
public virtual HashSet<Friendship> Friendships { get; set; }
public virtual HashSet<Post> Posts { get; set; }
public virtual HashSet<GroupUser> GroupUsers { get; set; }
public virtual HashSet<Comment> Comments { get; set; }
#endregion
}
Friendship entity:
public class Friendship
{
#region Primary keys
public int User1Id { get; set; }
public int User2Id { get; set; }
#endregion
[Required]
public DateTime FriendshipStart { get; set; }
#region Foreign keys
//defined using fluent api in MyDbContext:
//User1Id
//User2Id
//and
[ForeignKey("Chat")]
public int? ChatId { get; set; }
#endregion
#region Navigation properties
public virtual User User1 { get; set; }
public virtual User User2 { get; set; }
public virtual Chat Chat { get; set; }
#endregion
}
With this overrided function OnModelCreating in MyDbContext:
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Friendship>()
.HasKey(k => new { k.User1Id, k.User2Id });
builder.Entity<Friendship>()
.HasRequired(u => u.User1)
.WithMany()
.HasForeignKey(u => u.User1Id);
builder.Entity<Friendship>()
.HasRequired(u => u.User2)
.WithMany()
.HasForeignKey(u => u.User2Id)
.WillCascadeOnDelete(false);
}
Chat entity:
public class Chat
{
public int Id { get; set; }
#region Foreign keys
public int? FriendshipUser1Id { get; set; }
public int? FriendshipUser2Id { get; set; }
#endregion
#region Navigation properties
public virtual HashSet<Message> Messagges { get; set; }
[ForeignKey("FriendshipUser1Id, FriendshipUser2Id")]
public virtual Friendship Friendship { get; set; }
#endregion
}
If I understand your model right. 1 friendship has 1 user1 and 1 user2. Your settings are excluded.
builder.Entity<Friendship>()
.HasRequired(u => u.User1)
.WithMany()
.HasForeignKey(u => u.User1Id);
It means user1 has many... (many what?). Model doesn't have any list that could accept these settings. Your model has only single objects.
If you want it one to one/zero:
builder.Entity<Friendship>()
.HasRequired(u => u.User1)
.WithRequiredDependant(u => u.User2)
or
.WithRequiredPrincipal(u => u.User2)
or
.WithOptional(u => u.User2)
You can also try composite key for example: Creating Composite Key Entity Framework
Also I suggest you to use or Fluent API or Data Annotation convention. It will make your code more readable.
I have 2 models:
public class Text
{
public long Id { get; set; }
public string Text { get; set; }
}
public class User
{
public int Id { get; set; }
public ICollection<Text> Texts { get; set; }
}
My model build on user is that
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.Id).IsRequired();
When I try to run:
dotnet ef migrations add
I get this error:
with foreign key properties {'Id' : long} cannot target the primary
key {'Id' : int} because it is not compatible. Configure a principal
key or a set of compatible foreign key properties for this
relationship.
UPDATE:
It should be able for new models to have a collection of the table Texts like:
public class Customer
{
public int Id { get; set; }
public ICollection<Text> Texts { get; set; }
}
....
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.Id).IsRequired();
Had similar problem using EF Core but didn't want to include the (equivalent in my class) UserId on the dependent entity Text, just to make happy EF. Finally found that you can replace the primary key used in the relationship (UserId)
using HasPrincipalKey()
modelBuilder.Entity<User>()
.HasMany(t => t.Texts)
.WithOne()
.HasPrincipalKey(u => u.Text);
Firstly, change your Model naming please,
public class Text
{
public long Id { get; set; }
public int UserId { get; set; }// add a foreign key that could point to User.Id
public string Body { get; set; }//you cannot have a string property called "Text".
public virtual User Owner { get; set; }
}
public class User
{
public int Id { get; set; }
public virtual ICollection<Text> Texts { get; set; } = new HashSet<Text>();
}
builder.Entity<Text>(table =>
{
table.HasKey(x => x.Id);
table.HasOne(x => x.User)
.WithMany(x => x.Texts)
.HasForeignKey(x => x.UserId)
.HasPrincipalKey(x => x.Id)//<<== here is core code to let foreign key userId point to User.Id.
.OnDelete(DeleteBehavior.Cascade);
});
the reason we have to figure out which key is referred is because of multiple primary keys. I saw it once in MSDN, but cannot find it back.
You can use shadow properties for foreign keys, it looks popular now.
public class Text
{
public long Id { get; set; }
public string Body { get; set; }
public virtual User Owner { get; set; }
}
public class User
{
public int Id { get; set; }
public virtual ICollection<Text> Texts { get; set; } = new HashSet<Text>();
}
builder.Entity<Text>(table =>
{
table.HasKey(x => x.Id);
// Add the shadow property to the model
table.Property<int>("UserId");
table.HasOne(x => x.User)
.WithMany(x => x.Texts)
.HasForeignKey("UserId")//<<== Use shadow property
.HasPrincipalKey(x => x.Id)//<<==point to User.Id.
.OnDelete(DeleteBehavior.Cascade);
});
In the EF context configuration, specifically in the HasForeignKey() you are supposed to specify Which property on the Text model should be the foreign key that points to the User model?
Since User model's primary key is an int, the foreign key pointing from Text to User should naturally also be an int.
I think the mistake you've made is that you are configuring the PK of Textto also be the FK for the relationship Text -> User. Try to change your Text model to :
public class Text
{
public long Id { get; set; }
public string Text{ get; set; }
public int UserId { get; set; }
}
And your configuration to:
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.UserId).IsRequired();
You can simply drop all the migrations or the migration that made that Id, drop the database (if it is small or has no data) and add a clean migration
I was facing the same issue in one-to-one relationship. If you are facing the issue in one-one relationship. Then try this:
public partial class document
{
public document()
{
groups = new group();
}
public int? group_id { get; set; }
public virtual group groups { get; set; }
}
[Table("group")]
public class group
{
[Key]
[Column("group_id")]
public int group_id { get; set; }
[ForeignKey(nameof(group_id))]
public virtual document document { get; set; }
}
Each document has single group. So, we can consider these settings.
modelBuilder.Entity<group>().HasOne(a => a.document)
.WithOne(y => y.groups).HasForeignKey<document>(b => b.group_id);
I have a schema Definitions which I would like to be able to reference itself. As I need meta data about the reference, there's a coupling schema named Associations. I'm using Entity Framework's fluent API in conjunction with data annotation attributes.
Definitions:
public class Definition
{
[Key]
public int Id { get; set; }
// ...
public virtual ICollection<Association> Associations { get; set; }
}
Associations:
public class Association
{
[Key]
public int Id { get; set; }
public int TypeId { get; set; }
public int AssociatedDefinitionId { get; set; }
public int RootDefinitionId { get; set; }
public virtual AssociationType Type { get; set; }
public virtual Definition AssociatedDefinition { get; set; }
public virtual Definition RootDefinition { get; set; }
}
OnModelCreating:
modelBuilder.Entity<Association>()
.HasRequired(p => p.AssociatedDefinition)
.WithRequiredPrincipal();
modelBuilder.Entity<Association>()
.HasRequired(p => p.RootDefinition)
.WithRequiredPrincipal();
I use MySQL as the database engine.
When I try to save a definition entity with an empty association collection, I get a constraint violation:
Cannot add or update a child row: a foreign key constraint fails
("u0228621_8"."Definitions", CONSTRAINT
"FK_Definitions_Associations_Id" FOREIGN KEY ("Id") REFERENCES
"Associations" ("Id"))
What am I doing wrong?
You have defined your association class with all relationships being "required:required" because of the WithRequiredPrincipal which doesn't seem to be what you want. Since the Associations collection appears (from the comments) to be the relation from the Root definitions, the mapping should come from definition, like so:
// Foreign key mappings included.
modelBuilder.Entity<Definition>().HasMany(d => d.Assocations)
.WithRequired(a => a.RootDefinition).HasForeignKey(a => a.RootDefinitionId);
modelBuilder.Entity<Association>().HasRequired(a => a.AssociatedDefinition)
.HasForeignKey(a => a.AssociatedDefinitionId);
So the Associations collection may be empty, but every Association requires a RootDefinition and AssociatedDefinition.
I have an entity that excludes entities of the same type under certain conditions. In order to achieve this, I have an entity class like:
public class Entity
{
public int ID { get; set; }
public virtual ICollection<EntityExcludedEntity> ExcludedEntities { get; set; }
}
public class ExcludedEntity
{
public int ID { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public int EntityID { get; set; }
public virtual Entity Entity { get; set; }
public int ExcludedEntityID { get; set; }
public virtual Entity ExcludedEntity { get; set; }
}
//declared in the ExcludedEntity mapping class.
public ExcludedEntityMapping()
{
HasRequired(t => t.Entity).WithMany(t => t.ExcludedEntity).HasForeignKey(t => t.EntityID)
HasRequired(t => t.ExcludedEntity).WithMany(t => t.ExcludedEntity).HasForeignKey(t => t.ExcludedEntityID);
}
This causes in EF creating a third column and foreign key field called Entity_ID in my model. Seems like it thinks I have another relationship defined here but I don't understand why.
Here is the part related to foreign keys in the tables created:
.ForeignKey("dbo.Entities", t => t.EntityID)
.ForeignKey("dbo.Entities", t => t.ExcludedEntityID)
.ForeignKey("dbo.Entities", t => t.Entity_ID)
This post helped me find the answer.
Basically, EF cannot have two foreign keys to the same entity field. If you need to create two foreign key to the same entity you should bind them to different fields. So in this example:
public class Entity
{
public int ID { get; set; }
public virtual ICollection<EntityExcludedEntity> ExcludingEntities { get; set; }
public virtual ICollection<EntityExcludedEntity> ExcludedFromEntities { get; set; }
}
and this configuration:
public DBConceptAnswerExcludedAnswerMapping()
{
HasRequired(t => t.Entity).WithMany(t => t.ExcludingEntities).HasForeignKey(t => t.EntityID);
HasRequired(t => t.ExcludedEntity).WithMany(t => t.ExcludedFromEntities).HasForeignKey(t => t.ExcludedEntityID);
}
would solve the problem.