I'm having a bit of a problem with configuring composite keys in Entity framework between 3 tables, code first approach. I have a base class that has the Id, which all of my classes inherit from. The first table has a collection of the second table items, while it has a collection of the third table. I need composite keys for cascade on delete when removing an element from either table. I am also using the aggregate root pattern.
public abstract class BaseClass
{
[Key, Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
}
public class Table1 : BaseClass
{
public virtual ICollection<Table2> Table2Collection { get; set; }
public string Name { get; set; }
}
public class Table2 : BaseClass
{
public Table1 Table1 {get; set;}
[Key, ForeignKey("Table1"), Column(Order=1)]
public long Table1ID { get; set; }
public virtual ICollection<Table3> Table3Collection { get; set; }
public string Name { get; set; }
}
public class Table3 : BaseClass
{
[Key, ForeignKey("Table2Id,Table1Id"), Column(Order = 1)]
public Table2 Table2 { get; set; }
public long Table2Id{ get; set; }
public long Table1Id{ get; set; }
public string Name { get; set; }
}
The code above works fine for when I delete an element of type either Table 1 or Table2, but it won't allow me to delete an element from Table3 giving me the following exception:
"The relationship could not be changed because one or more of the foreign-key properties is non-nullable.When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted."
Bellow is my model builder:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Table2>()
.HasRequired(x=>x.Table1)
.WithMany(x =>x.Table2Collection)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Table3>()
.HasRequired(x=>x.Table2)
.WithMany(x =>x.Table3Collection)
.WillCascadeOnDelete(true);
}
I suspect that I may have not configured the model builder properly, but I can't seem to figure out how to configure it to allow for deleting an element of type Table3. Any help would be appreciated.
Figured out what I was missing. I'm putting this answer here for anyone that might bump into the same problem as I have. I needed to make all FK into PK FK (since they don't allow null). It's a bit annoying since if you have an even more complex tree, the number of keys you'd have to be manage of would grow the deeper you go.
modelBuilder.Entity<Table3>().HasKey(m => new {m.Id, m.Table2Id, m.Table1Id});
If anyone has an idea on how to shorten the number of keys to manage please leave an answer. Since this might not be the best solution.
Related
I am writing the Entity Framework models Code-First with no existing database. On my objects, I have declared my one-to-many foreign relationships both explicitly and as lazy-loading navigation properties like this:
UserRecord class
[Key]
public long ID { get; set; }
public virtual List<WorkItemRecord> WorkItemsAuthored { get; set; } // authored work items
WorkItemRecord class:
[Key]
public long ID { get; set; }
public long AuthorID { get; set; } // user ID of the author
public virtual UserRecord Author { get; set; } // navigation lazy-loaded property
The idea behind maintaining the foreign key both as an ID as well as a navigation property in the WorkItemRecord class is that in cases where I just need the actual underlying author's user ID, I can reference that directly without having to call the property and incur another DB lookup.
The problem is, when EF creates the database schema, it doesn't tie them together. It creates separate columns: AuthorIDand UserRecord_ID
I thought at first maybe this was because my property was Author and not User. However, even when I explicitly specified it using the property attribute...
[ForeignKey("Author")]
public long AuthorID { get; set; }
public virtual UserRecord Author { get; set; }
... I still end up with those same two columns in the generated schema.
Also tried putting the decorator on the other property...
public long AuthorID { get; set; }
[ForeignKey("AuthorID")]
public virtual UserRecord Author { get; set; }
... and still get the same result.
I'd like to avoid the Fluent API if possible, but I'm open to it if I can't get any other solutions.
Any ideas?
Thanks!
UPDATE
Thanks for all the help! Fluent API worked fine, but fixing the data annotation with an inverse property decorator proved much easier. Final solution:
UserRecord class:
public long ID { get; set; }
public virtual List<WorkItemRecord> WorkItemsAuthored { get; set; }
WorkItemRecord class:
public long ID { get; set; }
[ForeignKey("Author")]
public long AuthorUserID { get; set; }
[InverseProperty("WorkItemsAuthored")]
public virtual UserRecord Author { get; set; }
Microsoft also has an article talking about this specific issue:
https://msdn.microsoft.com/en-us/data/jj591583.aspx#Relationships
In you your DbContext class add this to your OnModelCreating method.
modelBuilder.Entity<Book>() //Guessing at your class name
.HasRequired(e => e.Author)
.WithMany(e => e.Books)
.HasForeignKey(e => e.AuthorID);
This will enforce the constraint.
The reference property should be enough for EF to recognize the one-to-many relationship but do you have a navigation property in the UserRecord class as well?
public class UserRecord
{
/* other properties */
public virtual List<WorkItemRecord> WorkItemsAuthored { get; set; }
}
Maybe there is a minor detail which causes EF not to recognize the foreign key.
UPDATE:
Try InverseProperty in the UserRecord class as below:
[InverseProperty("WorkItemsAuthored")]
public virtual UserRecord Author { get; set; } // navigation lazy-loaded property
From the "Programming Entity Framework: Code First" written by Julia Lerman and Rowan Miller:
... you may run into a scenario where there are multiple relationships between entities. In these cases, Code First won’t be able to work out which navigation properties match up.
I assume that the UserRecord class has more than one List<WorkItemRecord>.
I have entities:
[Table("Currency")]
public class Currency
{
[Key]
public int CurrencyId { get; set; }
public string Name { get; set; }
public char Symbol { get; set; }
}
[Table("Invoice")]
public class Invoice
{
[Key]
public int InvoiceId { get; set; }
public int CurrencyId { get; set; }
public int Amount { get; set; }
// public virtual ICollection<Currency> CurrencyList { get; set; }
}
I need every invoice to contain all possible currencies that are in the database. If I just set navigation property as in the commented line, then the system tries to implicitly create its own foreign key constraint that doesnt actually exists and then gives an error.
Is there a way to make that navigational prop with no FK?
I don't believe a navigation property is what you're looking for. this msdn article discusses navigation properties more in depth, http://msdn.microsoft.com/en-us/library/vstudio/bb738520(v=vs.100).aspx
Every object can have a navigation property for every relationship in which it participates.
this sentence specifically and the two sections Modifying Relationships and Navigating Relationships point more to the fact that these properties are specifically for EF relationships, if there is no relationship between the two objects there likely shouldn't be a navigation property.
#carlosfiguira has a good idea about a helper class, which i agree with. but hearing your issues with adding helper code to your CodeFirst models.
My suggestion would be to create a composite model that has an invoice property and the list of currencies. I've done this before especially in Web patterns, that type of complex object might be called a ViewModel depending on use case.
Time for a dumb question. I think the database design is screwy, but there isn't much I can do about that part of it. I have a table in the database "Table1" and then "Table2" which is essentially an extension of Table1 (I also have Table3, Table4, etc). My problem is that Table2 has it's own unique key, even though it's a one for one relationship. Then Table2Component uses Table2Id as it's foreign key. However, when I try to use that in my code I think it's pointing to Table1Id. I get the error message:
System.Data.Entity.Edm.EdmAssociationConstraint: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'Table2Id' on entity 'Table2Component' does not match the type of property 'Table1Id' on entity 'Table2' in the referential constraint 'Table2Component_Table2'.
Here is the code
[Table("Table1")]
public abstract class Table1
{
[Key]
[Column("table1_id")]
public string Table1Id { get; set; }
[Column("name")]
public string Name { get; set; }
[Column("type_cd")]
public string TypeCode { get; set; }
}
[Table("Table2")]
public class Table2 : Table1
{
[Key]
[Column("table2_id")]
public int Table2Id { get; set; }
[ForeignKey("Table1Id")]
public virtual Table1 Table1 { get; set; }
// this table also has a table1_id column
// but I guess I don't need it here, correct?
[Column("column1")]
public string Column1 { get; set; }
public virtual ICollection<Table2Component> Table2Components { get; set; }
}
[Table("Table2Component")]
public class Table2Component : ISubItem
{
[Key]
[Column("table2_component_id")]
public int Table2ComponentId { get; set; }
[Column("table2_id")]
public int Table2Id { get; set; }
[Column("description")]
public string Description { get; set; }
public bool Required { get { return true; } }
[ForeignKey("Table2Id")]
public virtual Table2 Table2 { get; set; }
}
Any suggestions? Should I be more forceful in trying to get the database changed?
Started as comment.... finish as simple answer, since no one else jumped in.
Search for Entity Framework 1:1 relationship eg https://stackoverflow.com/a/14997417/1347784 the restriction is both tables must have the same foreign key when using 1:1
No not necessarily better database design. It is Just the why the EF team built the framework. Ive learnt to live with the restrictions. In code first scenario, no big deal. Try the powertool to reverse engineer the alternative approach when you start with the DB. EF will use 1:M even though you may see it as 1:1. Also OK in my view.
I asked this ages ago but worded the question badly.
I'm trying to specify a relationship between 2 classes that isn't a simple FK mapping - this is a pre-existing database, not something I will generate from EF.
So, a simplified view of the 2 objects:
public class WidgetDetails
{
[Key]
public int WidgetId { get; set; }
public int WidgetNumber {get; set;}
// Some other props here..
[ForeignKey("WidgetId,WidgetNumber")]
public virtual WidgetProps WidgetProps { get; set; }
}
public class WidgetProps
{
[Key]
public int WidgetPropId { get; set; }
[Key, Column(Order = 0)]
public int WidgetId { get; set; }
[Key, Column(Order = 1)]
public int WidgetNumber { get; set; }
// Some props here...
}
The key thing here is that WidgetProps already has it's own PK. BUT - because I want to be able to specify that WidgetProps are related to WidgetDetails using the composite WidgetId and WidgetNumber, I try to specify that in my ForeignKey attribute.
HOWEVER, that will only work if I remove the [KEY] attribute from the WidgetProps.WidgetPropId - because in EF the relationships are mapped using keys.
What I want to say to EF is "Hey, this is the PK column, but this relationship is not using it, it's based on these 2 columns".
Is this possible?
Hope that makes sense!
It is not possible. EF can create only relations which follow database rules. FK on dependent side must contain all parts of PK on principal side.
General rule: EF fluent API is not able to define any relationship which you cannot define in database by using PK, FK relationship.
I've been trying to get EF4 CTP5 to play nice with an existing database, but struggling with some basic mapping issues.
I have two model classes so far:
public class Job
{
[Key, Column(Order = 0)]
public int JobNumber { get; set; }
[Key, Column(Order = 1)]
public int VersionNumber { get; set; }
public virtual User OwnedBy { get; set; }
}
and
[Table("Usernames")]
public class User
{
[Key]
public string Username { get; set; }
public string EmailAddress { get; set; }
public bool IsAdministrator { get; set; }
}
And I have my DbContext class exposing those as IDbSet
I can query my users, but as soon as I added the OwnedBy field to the Job class I began getting this error in all my tests for the Jobs:
Invalid column name 'UserUsername'.
I want this to behave like NHibernate's many-to-one, whereas I think EF4 is treating it as a complex type. How should this be done?
UserUsername is the default name that EF Code First chooses for the foreign key in the Jobs table. Obviously, it's not the same name that your existing database has for the FK column in Jobs table. You need to override this conventions and change the FK's default name so that it matches your database. Here is how it's done with fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Job>()
.HasOptional(j => j.OwnedBy)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(u => u.Username, "TheFKNameInYourDB"));
}
Try let it create new database from your schema and look what columname it expect.
I think its foreing key for OwnedBy. It tries to name it according to its internal convention, that is incompatible with how you named your foreing key column. And no, its really treating it as many-to-one.