Entity Framework: Migration falsely adds foreign key column twice - c#

I'm using EntityFramework 6.1.3 with CodeFirst for an Asp.Net application. I have an existing table ("Users") on which I'm trying to add a foreign key "GroupId".
This is the class (Everything with //new are changes made after the last migration)
[Table("Users")]
public class User
{
[Key]
[Column("PK_USER")]
public int Id { get; set; }
[Column("Username")]
public string Username { get; set; }
[Column("Name")]
public string Name { get; set; }
[Column("Password")]
public string Password { get; set; }
[Column("Firstname")]
public string Firstname { get; set; }
[Column("Lastname")]
public string Lastname { get; set; }
[Column("LastLogin")]
public DateTime? LastLogin { get; set; }
[Column("Department")]
public string Department { get; set; }
[Column("EMail")]
public string EMail { get; set; }
[Column("IsWindowsUser")]
public bool? IsWindowsUser { get; set; }
[Column("Signature")]
public string Signature { get; set; }
[Column("FK_ROLE")]
public int? RoleId { get; set; }
[ForeignKey("RoleId")]
public virtual Role Role { get; set; }
// new
[Column("GroupId")]
public int? GroupId { get; set; }
//new
[ForeignKey("GroupId")]
public virtual Group Group { get; set; }
//new
public virtual ICollection<GroupResponsibility> Responsibilites { get; set; }
public virtual ICollection<UserField> UserFields { get; set; }
public override string ToString()
{
return Username;
}
}
After I run add-migration the following code is generated (omitted other changes)
AddColumn("dbo.Users", "GroupId", c => c.Int());
AddColumn("dbo.Users", "Group_Id", c => c.Int());
CreateIndex("dbo.Users", "GroupId");
CreateIndex("dbo.Users", "Group_Id");
AddForeignKey("dbo.Users", "Group_Id", "dbo.Groups", "Id");
AddForeignKey("dbo.Users", "GroupId", "dbo.Groups", "Id");
As you can see EntityFramework recognized the foreign key but still added a default one "Group_Id". If I go through with it, and update the database, the navigational property "Group" will releate to "Group_Id" instead of the desired "GroupId".
Any ideas what might cause this?
Update 1
I commented out the navigational property and got the same result. Looks like the ICollection Users on the other end of the relation is the culprit. Here is the class "Group"
[Table("Groups")]
public class Group
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? GroupLeaderId { get; set; }
[ForeignKey("GroupLeaderId")]
public virtual User GroupLeader { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<GroupResponsibility> Responsibilites { get; set; }
public virtual ICollection<ApplicationForm> Applications { get; set; }
public override string ToString()
{
return Name;
}
}
If I comment out the ICollection Users, I get the following exception when adding the migration:
Unable to determine the principal end of an association between the types 'SparePartsDb.Entities.Group' and 'SparePartsDb.Entities.User'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Update 2
After some googling and changing the Key Attribute on the Group class...
[Key, ForeignKey("GroupLeader")]
public int Id { get; set; }
...I'm able to comment out the ICollection and run the migration. However, GroupId is not not longer recognized as a foreign key even though the navigiational property is present in the class User.
AddColumn("dbo.Users", "GroupId", c => c.Int());

OK, so I've searched for "migration adding duplicate foreign key" in multiple permutations for hours, but I finally figured out what was causing it for me: don't assume Fluent API knows which field you're referring to with WithOne() or WithMany()-- specify the field. See below for details.
I had two entity models in a one-to-many relationship, Address and Invitation, with public virtual ICollection<Invitation> Invitations on Address and public virtual Address Address on Invitation. I chose Fluent API over convention/attributes, so my Invitation builder looked like this:
builder
.HasOne(x => x.Address)
.WithMany()
.HasForeignKey(x => x.AddressId);
Unfortunately, EF Core 2.2 doesn't like having that empty WithMany() in there. Running dotnet ef migrations add InitialCreate resulted in this nonsense, much like OP:
migrationBuilder.CreateTable(
name: "Invitation",
columns: table => new
{
AddressId = table.Column<Guid>(nullable: false),
AddressId1 = table.Column<Guid>(nullable: true),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Invitation", x => x.Id);
table.ForeignKey(
name: "FK_Invitation_Address_AddressId",
column: x => x.AddressId,
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Invitation_Address_AddressId1",
column: x => x.AddressId1,
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
Switching my builder to read:
builder
.HasOne(x => x.Address)
.WithMany(x => x.Invitations)
.HasForeignKey(x => x.AddressId);
fixed the problem for me.
Running dotnet ef migrations remove followed by dotnet ef migrations add InitialCreate again gave me a much nicer migration:
migrationBuilder.CreateTable(
name: "Invitation",
columns: table => new
{
AddressId = table.Column<Guid>(nullable: false),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Invitation", x => x.Id);
table.ForeignKey(
name: "FK_Invitation_Address_AddressId",
column: x => x.AddressId,
principalTable: "Address",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Hope this helps some other poor soul searching this down.

You shouldn't need to put a
[Column("GroupId")]
on top of the
public int? GroupId { get; set; }
Entity Framework should be able to recognize your mapping by itself.
As stated in msdn:
When generating the database, code first sees the BlogId property in the Post class and recognizes it, by the convention that it matches a class name plus “Id”, as a foreign key to the Blog class. But there is no BlogId property in the blog class. The solution for this is to create a navigation property in the Post and use the Foreign DataAnnotation to help code first understand how to build the relationship between the two classes —using the Post.BlogId property — as well as how to specify constraints in the database.
And the code to this explanation would be:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
[ForeignKey("BlogId")]
public Blog Blog { get; set; }
public ICollection<Comment> Comments { get; set; }
}
As you can see, only the complex object has the mapping defining the fk, EF should do the rest for you.
source

Related

Entity framework core code first : multiple foreign key on itself

We got this exception message when we try to create mutliple foreign key to the owner table (with only one foreign key, there is no problem) :
The dependent side could not be determined for the one-to-one relationship between 'Person.Alternative1Person' and 'Person.Alternative2Person'. To identify the dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship, configure them independently via separate method chains in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.
We founded that hading the [ForeignKey] solve the problem. But we don't understand why without this data-annotion it doesn't work, because finally the migration generated code is the same if these foreign key were on another table ?
internal class DatabaseContext : DbContext
{
public DatabaseContext() { }
public DbSet<Country> Country { get; set; }
public DbSet<Resident> Resident { get; set; }
public DbSet<Person> Person { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
string connectionStringSqlServer = #"Server=myserver;Database=TestEf;User Id=x;Password=x;";
optionsBuilder.UseSqlServer(connectionStringSqlServer);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.EnableDetailedErrors();
}
}
}
public class Resident
{
public int Id { get; set; }
public string Name { get; set; }
// Data-annotation ForeignKey not required
public int? HomeCountryId { get; set; }
public virtual Country HomeCountry { get; set; }
// Data-annotation ForeignKey not required
public int? BirthCountryId { get; set; }
public virtual Country BirthCountry { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Alternative1Person")] // Error without this data-annotation if more than one foreign key on itseft
public int? Alternative1PersonId { get; set; }
public virtual Person Alternative1Person { get; set; } constraint)
[ForeignKey("Alternative2Person")] // Error without this data-annotation if more than one foreign key on itseft
public int? Alternative2PersonId { get; set; }
public virtual Person Alternative2Person { get; set; } constraint)
}
Migration generated code are identical, so why the data-annotion is required to avoid the error ?
constraints: table =>
{
table.PrimaryKey("PK_Resident", x => x.Id);
table.ForeignKey(
name: "FK_Resident_Country_BirthCountryId",
column: x => x.BirthCountryId,
principalTable: "Country",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Resident_Country_HomeCountryId",
column: x => x.HomeCountryId,
principalTable: "Country",
principalColumn: "Id");
});
constraints: table =>
{
table.PrimaryKey("PK_Person", x => x.Id);
table.ForeignKey(
name: "FK_Person_Person_Alternative1PersonId",
column: x => x.Alternative1PersonId,
principalTable: "Person",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Person_Person_Alternative2PersonId",
column: x => x.Alternative2PersonId,
principalTable: "Person",
principalColumn: "Id");
});

EF Core - create relationship without primary/foreign keys

I'm trying to configure EF to include documents when retriving a user or product. The entity Document has a ReferenceId property which should store either UserId or ProductId. This way, when I save a document for a user or product, the UserId or ProductId is saved to Document.ReferenceId.
Entities:
public class User
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class Product
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class Document
{
public string Id { get; set; }
public string ReferenceId { get; set; }
}
Configuring:
builder.Entity<User>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.HasMany(e => e.Documents)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Product>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.HasMany(e => e.Documents)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Document>(e =>
{
e.HasKey(e => e.Id);
e.Property(p => p.Id).ValueGeneratedOnAdd();
e.ToTable("Documents");
});
Saving:
var user = new User { };
var userDocument = new Document { ReferenceId = user.Id };
var product = new Product { };
var productDocument = new Document { ReferenceId = product.Id };
_context.Users.Add(user);
_context.Products.Add(product);
_context.Add(userDocument);
_context.Add(productDocument);
_context.SaveChanges();
Migrations:
migrationBuilder.CreateTable(
name: "Documents",
columns: table => new
{
Id = table.Column<string>(nullable: false),
ReferenceId = table.Column<string>(nullable: true),
ProductId = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Documents", x => x.Id);
table.ForeignKey(
name: "FK_Documents_Products_ProductId",
column: x => x.ProductId,
principalTable: "Products",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Documents_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
I don't want 2 foreign keys (ProductId and UserId) to be created on Documents table. Is there a way to make EF automatically link UserId and ProductId to ReferenceId?
The proper way to solve it would be to have User and Product inherit a base class and move the Id and Documents properties to that class.
public class BaseObject
{
public string Id { get; set; }
public ICollection<Document> Documents { get; set; }
}
public class User : BaseObject
{
}
public class Product : BaseObject
{
}
public class Document
{
public string BaseObjectId { get; set; }
}
The only way I see, is to use TPH inheritance (See here for more information).
I have quoted and edited the answer by Erik H.
public enum DocumentType
{
User = 0,
Product = 1
}
public class BaseObject
{
public string Id { get; set; }
public ObjectType DocumentType{ get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
public class User : BaseObject
{
}
public class Product : BaseObject
{
}
public class Document
{
public int Id { get; set; }
public string BaseObjectId { get; set; }
public virtual BaseObject DocumentObject { get; set; }
}
Via fluent-Api you can set a discriminator. This way ef core will only create one table for for both objects Product and User and distinguishes their type by the value of the discriminator column. But only as long as they have exactly the same properties which they share from the base class. As soon as you add properties to one of those subclasses a new table will be created (with all properties from the base- and subclass).
Here is the configuration for the discriminator:
modelBuilder.Entity<BaseObject>()
.HasDiscriminator<DocumentType>("DocumentType")
.HasValue<User>(DocumentType.User)
.HasValue<Product>(DocumentType.Product)
This may not be a clean approach (for me it seems like User and Product should not inherit from the same base class, because they do not share anything than the relations to documents). But it should work as you want it.
You can create a many to many table:
public class Product
{
public string Id { get; set; }
public ICollection<ProductDocument> ProductDocuments{ get; set; }
}
public class Document
{
public string ReferenceId { get; set; }
}
public class ProductDocument
{
public ICollection<Product> Products{ get; set; }
public ICollection<Document> Documents { get; set; }
}
You will have to create a separate table for your user table ie UserDocumentes using the same pattern.

EF Core creates multiple foreign key columns

I'm using EF Core with .NET Core 3.1
I have simple example of Client-Event relationship:
public class BaseEntity
{
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
}
public class Client : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
public class Event : BaseEntity
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public Client Client { get; set; }
}
In my context, I'm using Fluent API to specify relationships:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Event>()
.HasOne<Client>()
.WithMany()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
When I create migration, the client table looks fine, but the event table looks like this:
migrationBuilder.CreateTable(
name: "Events",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CreatedOn = table.Column<DateTime>(nullable: false),
ModifiedOn = table.Column<DateTime>(nullable: true),
Start = table.Column<DateTime>(nullable: false),
End = table.Column<DateTime>(nullable: false),
ClientId = table.Column<int>(nullable: false),
ClientId1 = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Events", x => x.Id);
table.ForeignKey(
name: "FK_Events_Clients_ClientId",
column: x => x.ClientId,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Events_Clients_ClientId1",
column: x => x.ClientId1,
principalTable: "Clients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Finally I end up having two columns: ClientId and ClientId1. Why is Entity Framework creating two columns for my relationship?
I wasn't using Fluent API so far and it worked perfectly with just shadow property ClientId auto-generated, but I needed to configure cascade delete form this entities, and since there is no other way to do it, I specified the relationship as pictured above. Since then, there is additional foreign key column for it.
I tried specifying a foreign key column:
modelBuilder.Entity<Event>()
.HasOne<Client>()
.WithMany()
.IsRequired()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
No effect so far. Is there any way to tell EF im using auto generated shadow properties?
Edit #1:
I also tried specyfing Foreign keys properties on my own like this:
public class Event : BaseEntity
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int ClientId { get; set; }
public Client Client { get; set; }
}
and then
modelBuilder.Entity<Event>()
.HasOne<Client>()
.WithMany()
.IsRequired()
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
but there is still no effect.
It turns out, relationships can be empty - leaving decision to the framework, but it doesn't really serve Your interest. I modified my code, so there is explicit pointing at the navigation property, and EF recognized the relationship and stopped creating shadow properties for the columns:
modelBuilder.Entity<Event>()
.HasOne<Client>(e => e.Client)
.WithMany()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
You can try this:
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public int? ClientId { get; set; }
[ForeignKey("ClientId ")]
public virtual Client Client { get; set; }
}

C# Entity Framework Foreign Key attribute ignored

I have "User" and "Product" entities, product has foreign key to user (UserId -> User). I am trying to add one more foreign key to user in product entity, but getting wrong migration
My updated product entity (example)
[ForeignKey("User")]
public int? UserId { get; set; }
[ForeignKey("AcceptedBy")]
public int? AcceptedById { get; set; }
[ForeignKey("AcceptedById")]
public User AcceptedBy { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
Automaticaly created migration:
DropForeignKey("dbo.Products", "UserId", "dbo.Users");
AddColumn("dbo.Products", "AcceptedById", c => c.Int());
AddColumn("dbo.Products", "User_Id", c => c.Int());
CreateIndex("dbo.Products", "AcceptedById");
CreateIndex("dbo.Products", "User_Id");
AddForeignKey("dbo.Products", "AcceptedById", "dbo.Users", "Id");
AddForeignKey("dbo.Products", "User_Id", "dbo.Users", "Id");
DropColumn("dbo.Products", "AcceptedByUserId");
So my UserId property is ignored
I think you have to many ForeignKey data annotations.
public int? UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
public int? AcceptedById { get; set; }
[ForeignKey("AcceptedById")]
public User AcceptedBy { get; set; }
You could use fluent API like:
modelBuilder.Entity<Product>()
.HasOptional(h => h.User)
.WithMany()
.HasForeignKey(fk => fk.UserId);

Entity Framework table per hierarchy and one to one relation

I am having troubles implementing a table-per-hierarchy architecture in combination with a one-to-one relation in EF 6 code-first and SQL-Server. Entity Framework doesn't use the right column as foreign key.
I have a class Version and two inheriting classes ProductVersion and FeatureVersion.
public class Version
{
[Key]
public Int32 ID { get; private set; }
public Int32 Major { get; private set; }
public Int32 Minor { get; private set; }
public Int32 Patch { get; private set; }
..
}
The two inheriting classes implement nothing but the navigation properties.
public class ProductVersion : Version
{
[Required]
public virtual Product.Product Product { get; set; }
public Int32 ProductId { get; set; }
}
public class FeatureVersion : Version
{
[Required]
public virtual Feature Feature { get; set; }
public Int32 FeatureId { get; set; }
}
Now in my product I use a one-to-many relation and everything works fine.
public class Product
{
public Int32 ID { get; set; }
public String Name { get; set; }
public List<Versioning.ProductVersion> ProductVersions { get; set; }
...
}
In my feature I use a one-to-one relation and things get wrong.
public class Feature : ICloneable
{
public Int32 Id { get; set; }
...
public int FeatureVersionId { get; set; }
public virtual Entities.Versioning.FeatureVersion FeatureVersion { get; set; }
...
}
The resulting migration looks like this
CreateTable(
"dbo.Versions",
c => new
{
ID = c.Int(nullable: false),
...,
FeatureId = c.Int(),
ProductId = c.Int(),
Discriminator = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.Features", t => t.ID)
.ForeignKey("dbo.Products", t => t.ProductId, cascadeDelete: true)
.Index(t => t.ID)
.Index(t => t.ProductId);
As you see the foreign key to Features is using the wrong column.
When I change t => t.ID manually to t => t.FeatureId, cascadeDelete: true the foreign key gets inserted correctly into the database, but EF seems to have a problem inserting new features with an initial FeatureVersion; the new FeatureVersion's FeatureId column is always set to 0 and therefore leading to an exception (in the code I simply assign a new FeatureVersion object to the Feature object and try to save the context changes).
Everything works fine for Product with its one-to-many relation.
Interestingly when I change the reference in Feature from
public List<Versioning.ProductVersion> ProductVersions { get; set; }
to
public List<Versioning.Version> Versions { get; set; }
e.g. the mother class, everything works fine; in the migration I see the right columns joined and the INSERT works as well. But definitely, this is not what I want.
Is the inheritance somehow irritating EF? Am I missing an important point?
I also tried to change to relation for FeatureVersion to a one-to-many relation same as for Product. It works fine but this isn't the relation I need to use.

Categories

Resources