I have a legacy database which I cannot change with two entities with a foreign key property in each entity:
EntityOne
ForeignKey1 (pointing to EntityTwo.ForeignKey2)
EntityTwo
ForeignKey2 (pointing to EntityOne.ForeignKey1)
These foreign keys point to eachother. How can I map this relation with EF6? If it isn't possible, is there any workaround?
I tested with a test database which actually has a foreign key relation from EntityOne to EntityTwo and another from EntityTwo to EntityOne and auto generating the model, with code first from database, did not create any relationships between the two tables:
public partial class EntityFrameworkTesting : DbContext
{
public EntityFrameworkTesting()
: base("name=EntityFrameworkTesting")
{
}
public virtual DbSet<EntityOne> EntityOne { get; set; }
public virtual DbSet<EntityTwo> EntityTwo { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
With some additional trying I've succeeded to build a relationship between these entities. However, I had to delete the foreign key properties from my POCO classes to allow usage of the MapKey function. I would still like to access the foreign key, but when adding the foreign key property again, I receive an error stating: "ForeignKey2: Name: Each property name in a type must be unique. Property name 'ForeignKey2' is already defined.".
Basically I'm looking for the relationship functionality I have now, but with added foreign key property on my POCO class.
modelBuilder.Entity<EntityTwo>()
.HasRequired(entity => entity.EntityOne)
.WithRequiredDependent(entity => entity.EntityTwo)
.Map(m => m.MapKey("ForeignKey2"));
In this situation you want to use a shared primary key, meaning the PK of the dependent entity is also the FK to the principal entity (and references the principal entity's PK), enforcing a 1..0 relationship. You can view this answer to see how it's configured.
If you can't change your database schema, you're out of luck in EF6. EF6 does not support FK's referencing any candidate key other than the primary key.
EFCore supports FK's referencing alternate candidate keys, but still won't create two mutually referencing FK's - only the FK in the dependent table will be created (or so it seems via my limited testing just now). You can do so via the following FluentAPI configuration (assuming EntityOne is the principal entity):
modelBuilder.Entity<EntityOne>()
.HasAlternateKey( eo => eo.ForeignKey1 );
modelBuilder.Entity<EntityTwo>()
.HasOne( et => et.EntityOne )
.WithOne( eo => eo.EntityTwo )
.IsRequired()
.HasForeignKey<EntityTwo>( et => et.ForeignKey2 )
.HasPrincipalKey<EntityOne>( eo => eo.ForeignKey1 );
EntityOne.ForeignKey1 should be nullable. When testing, I had to manually specify the type parameters for the Has*Key methods, not sure why.
Related
I'm Trying to rename the Default FK, but Code First Migration keep generating a second FK to the same table with a different name, messing up the Table schema.
The FK Model has a PK named Id, I just want to keep the convention required by my client, changing the name for Id(something).
1) Generated migration:
2) Mapping:
What should I do?
If your CorpoGestor entity exposes a property for the foreign key, use HasForeignKey instead of Mapand MapKey.
HasRequired(x => x.Conselho)
.WithMany()
.HasForeignKey(x => x.IdConselho);
Another solution: you can use the ForeignKey attribute in the property, instead of mapping the relation in the CorpoGestorMap class:
public class CorpoGestor
{
...
public int IdConselho { get; set; }
[ForeignKey("IdConselho")]
public virtual Conselho Conselho { get; set; }
}
A warning: using attributes in the entities is conceptually not so cool as implementing the code in the EntityTypeConfiguration mapping classes, because you are polluting your entities with data layer code that should be kept in the EF classes.
I have the following Schema:
User Table: (Primary Key)
UserId
CustomerId
Role Table: (Primary Key)
UserId
CustomerId
UserRole Table:
UserRoleId (UNIQUEIDENTIFIER (newsequentialid)) Primary Key
UserId
Customerid
RoleId
Those tables participate in many to many relationship (UserRole). I am using Entity Framework code first with mapping classes to define the database tables. So, In my mapping class for User Table, I have the following:
this.HasMany(u => u.Roles)
.WithMany()
.Map(m =>
{
m.MapLeftKey(new string[] { "CustomerID", "UserID" });
m.MapRightKey(new string[] {"CustomerID", "RoleID"});
m.ToTable("UserRoles");
}
);
Entity framework is failing with this message:
"One or more validation errors were detected during model generation:
CustomerID: Name: Each property name in a type must be unique. Property name 'CustomerID' is already defined.
UserRole: EntityType: EntitySet 'UserRole' is based on type 'UserRole' that has no keys defined.
is it possible to tell Code First that the Primary Key for my "UserRole" is UserRoleId?
The issue is when Entity Framework tries to create the UserRole Table, it would use all columns of MapLeftKey and MapRightKey to Create UserRole with PrimaryKey that has all those columns.
Any suggestions?
You need to model your classes similar to your DB, why don't you simply add the association tables? I mocked up your DB and there is no problem as long as you model all the tables.
Test it for yourself, create an EF project .edmx using code first from existing DB, I think the answer will be obvious.
I have a model like this:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Account Parent { get; set; }
}
and I add the following configuration:
this.HasOptional(item => item.Parent)
.WithMany()
.HasForeignKey(item => item.ParentId)
.WillCascadeOnDelete(true);
and then I got following error message:
Assembly Initialization method
UnitTest.Biz.Accounting.TestInitializer.Init threw exception.
System.Data.SqlClient.SqlException:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint
'FK_dbo.acct_Account_dbo.acct_Account_ParentId' on table
'acct_Account' may cause cycles or multiple cascade paths. Specify ON
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints. Could not create constraint.
I had read the documents of the EF, but I don't understand where is wrong in my code...
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null
http://msdn.microsoft.com/en-us/data/jj591620#CascadeDelete
Self Referencing Table
SQL server doesn't allow self referencing table to have cascading delete.
As you can see, SQL Server noticed that your cascade operation is
cyclic, and it does not allow this type of cascading. This is true not
only for a self-referencing table, but also for any chain of
relationships between tables in which the cascading operations have a
potential to be cyclical.
Source
That's why in EF you can't set cascading delete on self referencing entity.
.WillCascadeOnDelete(true); // Not allowed.
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship
is about the default convention of EF which you can override by fluent api. If you have following code,
public int? ParentId { get; set; } // <--- nullable
public Account Parent { get; set; }
EF will mark the cascading delete as false by default, even if you don't configure it with WillCascadeOnDelete(false), which actually means that the foreign key cascading delete in the database will be set as ON DELETE NO ACTION.
And currently EF doesn't support ON DELETE SET NULL unless you have custom query to drop and re-add the constraint, check this post.
and when the principal is deleted the foreign key will be set to null.
This has been explained in this post which means only if the children are loaded into the context.
My code:
Models.Resource r = new Models.Resource();
r.Name = txtName.Text;
r.ResourceType = resTypes.Find(rt => rt.Name == "Content");
r.ResourceContents.Add(_resourceContent.Find(rc => rc.ID == _resourceContentID));
ctx.Resource.Add(r);
ctx.SaveChanges();
ctx.SaveChanges() causes the error:
Cannot insert explicit value for identity column in table 'Resources' when IDENTITY_INSERT is set to OFF.
Looking at what's being sent to SQL:
ADO.NET:Execute NonQuery "INSERT [dbo].[Resources]([ID], [Name], [Description], [IsOnFile],
[ContentOwnerAlias], [ContentOwnerGroup], [ResourceTypes_ID])
VALUES (#0, #1, #2, #3, #4, #5, NULL)"
My POCO Resource has ID as a Key:
public partial class Resource
{
public Resource()
{
}
[Key]
public int ID { get; set; }
And my Map code:
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
// Primary Key
this.HasKey(t => t.ID);
How do I tell Entity to not send the Key ID field to the database?
If your PK is generated by the database (like an identity) you have to configure it in your Map.
public class ResourceMap : EntityTypeConfiguration<Resource>
{
public ResourceMap()
{
// Primary Key
this.HasKey(t => t.ID);
this.Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
You do not need the HasKey(t => t.ID) Fluent API mapping or the [Key] Data Attribute because by convention EF will assume that an integer field named ID is the key and is database generated.
As an aside, I'd recommend that when you are not following conventions you should choose one method or the other - otherwise you are repeating yourself and when you want to change something you need to change it in 2 places.
I'm not sure why the field in the database isn't already database generated - maybe when you define the field via the fluent api you have to specify that too. What I do know is that in order to make EF change a key field to be database generated you will need to drop the table.
So - rollback the migration or drop the table / database, then remove the data attribute, remove the fluent mapping and recreate.
This issue is currently on a "backlog" in the entity framework. If you want to vote for it you can do that here: Migrations: does not detect changes to DatabaseGeneratedOption
Other References:
Identity problem in EF
Switching Identity On/Off With A Custom Migration Operation
EF 4.3.1. I have defined User and Box entities. Each box may or may not be assigned to a user.
What I'd like to achieve is to have a OwnBox property in User class, and an Owner property in Box class.
in Database, I have defined OwnerId foreignkey in Boxes (Boxes.OwnerId has relation with Users.UserId).
To define the relationship with fluent api, I have defined the following classes:
public partial class User
{
public int UserId {get; set;}
public virtual Box OwnBox { get; set; }
}
public partial class Box
{
public int? OwnerId { get; set; }
public virtual User User { get; set; }
}
Then in my Mapping class for Box, I have defined the relations as follows:
this.HasOptional(t => t.User).WithOptionalDependent(d => d.OwnBox).
Map(m => m.MapKey("OwnerId")).WillCascadeOnDelete(true);
But by firing up the project, I got the error:
Schema specified is not valid. Errors: (56,6) : error 0019: Each
property name in a type must be unique. Property name 'OwnerId' was
already defined.
So I had to tell EF to forget about the OwnerId column first:
this.Ignore(t => t.OwnerId);
Now the project works fine. But I'm still doubtful if this is a good approach and will everything work fine on CRUD operations with foreign key associations.
First of all, this is not one-to-one relationship. In one-to-one relationship the foreign key must be a primary key.
I believe in your scenario the situation can happen:
User = { UserID = 2 }
Box1 = { UserID = 2 }
Box2 = { UserID = 2 }
Nothing stops you from doing that, but which box should be returned when you do that:
User.OwnBox, Box1 or Box2?
EF can deal with that using Independent Association. It will create foreign key, hidden from your POCO class. You can specify the name of the column using MapKey as you did. However, because you also created a property called OnwerID, just as the column used with MapKey, the EF has a problem as two properties are mapped to the same column.
When you use ignore, the POCO OwnerID property is ignored by EF so that fixes the problem of two properties, however, the OwnderID value never gets saved or read to the database. Because EF just ignores it.
Thanks for your question, I have learnt a lot thanks to this.