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.
Related
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)
I am trying to learn the C# Fluent API, and Im running into issues (I think) with my model setup. I have three tables: OrderFile, Order, LineItem. The error:
Self referencing loop detected for property 'order' with type 'BaseService.WebApi.Order'. Path 'orders[0].lineItems[0]'.
My structure:
OrderFile contains List<Orders>
Order contains List<ListItems> and a Navigation property OrderFile
ListItem contains a Navigation property Order
They are tied together with ForeignKey constraints specified in a Fluent API. Is something wrong with the constraints? I was trying to follow this example for Foreign keys
modelBuilder.Entity<OrderFile>(e =>
{
//many orders within one order file
//the FK relates the OrderFile to the nav key of the Order
e.HasMany(of => of.Orders)
.WithOne(o => o.orderFile)
.HasForeignKey(o => o.FileGuid);
e.HasKey(o => o.FileGuid);
});
modelBuilder.Entity<Order>(e =>
{
//each order has an array of line items
//each line item has one order (navigation property)
//the foreign key of the line item ties it to the Parent (List<Order>)
e.HasMany(o => o.LineItems)
.WithOne(li => li.order)
.HasForeignKey(o => o.OrderGuid);
e.HasKey(o => o.OrderGuid);
});
Models
public class OrderFile
{
public Guid FileGuid { get; set; }
public virtual ICollection<Order> Orders { get; set; } //everything with same FileGuid
}
public class Order
{
....
[JsonIgnore]
public Guid FileGuid { get; set; }
[Key]
public Guid OrderGuid { get; set; }
[JsonIgnore]
public OrderFile orderFile { get; set; }
public virtual ICollection<LineItem> LineItems { get; set; } //everything with same OrderGuid
}
public class LineItem
{
....
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public Guid OrderGuid { get; set; }
public Order order { get; set; }
}
Your LineItem entity has a reference to Order, which doesn't have a JsonIgnore attribute.
Basically your problem stems from trying to serialize an object graph that has circular dependencies (loops), while your design problem is that you use database entity classes in your API. The client facing models should be different classes than the entities you persist in the database.
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.
I'm attempting to create a many-to-many mapping between User and Group models. Here are my classes:
public abstract class Entity
{
public Guid Id { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class User : Entity
{
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group : Entity
{
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
As you can see, I'm using the Entity abstract class to implement common properties in the classes that inherit from it. In this case, Id will be the key property for all of my EF classes.
Here is my configuration file where I map the many-to-many relationship:
public GroupConfiguration()
{
Property(x => x.Id).IsRequired().HasDatabaseGeneratedOption(databaseGeneratedOption: DatabaseGeneratedOption.Identity);
Property(x => x.Name).HasMaxLength(50).IsRequired();
HasMany(g => g.Users)
.WithMany(u => u.Groups)
.Map(m =>
{
m.MapLeftKey("Id");
m.MapRightKey("Id");
m.ToTable("UserGroups");
});
}
When I attempt to add a migration, I get the following error: Id: Name: Each property name in a type must be unique. Property name 'Id' is already defined. It seemingly doesn't like the fact that the property being mapped on both sides has the same identifier. When I don't inherit from Entity for one of the classes, eg. User, and make the mapping property UserId it is able to successfully create a migration.
Is there any way around this? It would be to be able to use an Id property for all of my entities defined in an abstract class.
Thanks in advance.
You can't have 2 keys with the same name, it will represent the columns for your relationship table (named "UserGroups"). When you call "MapLeftKey" or "MapRightKey", you define the columns name.
So I suggest you to rename your Ids (UserId and GroupId for example) and your mapping should be alright. I think you cannot have another solution on using fluent API.
From examples that I have seen online and in a Programming Entity Framework CodeFirst book, when you have a collection on both classes EF would create a mapping table such as MembersRecipes and the primary key from each class would link to this table.
However when I do the below, I instead get a new field in the Recipes table called Member_Id and a Recipe_Id in the Members table.
Which only creates two one-to-many relationships, but not a many-to-many so I could have Member 3 linked to Recipes (4,5,6) and Recipe 4 linked to Members (1,2,3) etc.
Is there a way to create this mapping table? and if so how do you name it something else such as "cookbooks" ?
Thanks
public abstract class Entity {
[Required]
public int Id { get; set; }
}
public class Member : Entity {
[Required]
public string Name { get; set; }
public virtual IList<Recipe> Recipes { get; set; }
}
public class Recipe : Entity {
[Required]
public string Name { get; set; }
[ForeignKey("Author")]
public int AuthorId { get; set; }
public virtual Member Author { get; set; }
....
public virtual IList<Member> Members { get; set; }
}
UPDATE:
Below is another approach I have tried which doesn't use the Fluent API and replaces the AuthorId & Author on Recipe with an owner flag, I have also renamed the below example from Cookbooks to MembersRecipes, this also fixes my issue similar to the answer but as mentioned has further implications.
public class MembersRecipes {
[Key, Column(Order = 0)]
[ForeignKey("Recipe")]
public int RecipeId { get; set; }
public virtual Recipe Recipe { get; set; }
[Key, Column(Order = 1)]
[ForeignKey("Member")]
public int MemberId { get; set; }
public virtual Member Member { get; set; }
public bool Owner { get; set; }
}
and in Recipe & Member classes I changed the collections to
public virtual IList<MembersRecipes> MembersRecipes { get; set; }
Do this on your DbContext OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Recipe>()
.HasMany(x => x.Members)
.WithMany(x => x.Recipes)
.Map(x =>
{
x.ToTable("Cookbooks"); // third table is named Cookbooks
x.MapLeftKey("RecipeId");
x.MapRightKey("MemberId");
});
}
You can do it the other way around too, it's the same, just another side of the same coin:
modelBuilder.Entity<Member>()
.HasMany(x => x.Recipes)
.WithMany(x => x.Members)
.Map(x =>
{
x.ToTable("Cookbooks"); // third table is named Cookbooks
x.MapLeftKey("MemberId");
x.MapRightKey("RecipeId");
});
Further examples:
http://www.ienablemuch.com/2011/07/using-checkbox-list-on-aspnet-mvc-with_16.html
http://www.ienablemuch.com/2011/07/nhibernate-equivalent-of-entity.html
UPDATE
To prevent cyclical reference on your Author property, aside from above, you need to add this:
modelBuilder.Entity<Recipe>()
.HasRequired(x => x.Author)
.WithMany()
.WillCascadeOnDelete(false);
Idea sourced here: EF Code First with many to many self referencing relationship
The core thing is, you need to inform EF that the Author property(which is a Member instance) has no Recipe collections(denoted by WithMany()); that way, cyclical reference could be stopped on Author property.
These are the created tables from the Code First mappings above:
CREATE TABLE Members(
Id int IDENTITY(1,1) NOT NULL primary key,
Name nvarchar(128) NOT NULL
);
CREATE TABLE Recipes(
Id int IDENTITY(1,1) NOT NULL primary key,
Name nvarchar(128) NOT NULL,
AuthorId int NOT NULL references Members(Id)
);
CREATE TABLE Cookbooks(
RecipeId int NOT NULL,
MemberId int NOT NULL,
constraint pk_Cookbooks primary key(RecipeId,MemberId)
);