C# Entity Framework Foreign Key attribute ignored - c#

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);

Related

Ignore deleting specific entity on cascade delete

I have the following entity:
public class ShoppingCartItem
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ShoppingCartItemId { get; set; }
public virtual Product Product { get; set; }
public int Quantity { get; set; }
public string ShoppingCartId { get; set; }
public DateTime ItemAddedToCart { get; set; }
}
There is a one to one foreign key relationship from Product to ShoppingCartItem.
My question is, how can I make entity framework core skip deleting the ShoppingCartItem entity on Product cascade delete?
You can use something like the following in your builder object
builder.Entity<ShoppingCartItem>()
.HasOne(s => s.Product)
.WithOne(p => p.ShoppingCartItem)
.Metadata.DeleteBehavior = DeleteBehavior.Restrict;

Entity Framework Core: Multiple Relationships between Users and Organizations

I have 2 model classes for Users and Organizations.
public class User : IdentityUser
{
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public int? OrganizationID { get; set; }
public virtual OrgList org { get; set; }
}
public class OrgList
{
public OrgList()
{
employees = new HashSet<User>();
}
public int id { get; set; }
public String name { get; set; }
public String ownerId { get; set; }
public virtual ICollection<User> employees { get; set; }
public virtual User ownerUser { get; set; }
}
User can be owner of some organization and also he is employee of the same organization (But other employees can't be owners of the organization).
First i've created a relationship for employees and it works OK
modelBuilder.Entity<OrgList>(entity =>
{
entity.HasMany(e => e.employees)
.WithOne(e => e.org)
.HasForeignKey(e => e.OrganizationID)
.OnDelete(DeleteBehavior.SetNull);
}
but when i try to add another relationship for owner
entity.HasOne(e => e.ownerUser)
.WithOne(e => e.org)
.HasForeignKey<OrgList>(e => e.ownerId)
.OnDelete(DeleteBehavior.Cascade);
i have an error on migration:
Cannot create a relationship between 'User.org' and
'OrgList.ownerUser', because there already is a relationship between
'OrgList.employees' and 'User.org'. Navigation properties can only
participate in a single relationship.
How can i fix it? I've found an answers for EF6 (not EF Core) with HasOptional() and WithOptionalPrincipal() methods that not exist in EF Core.
Can i do it without creating additional table for employees or without creating additional virtual OrgList on User class?
You're trying to create the owner relationship with the same property on the user that you are using for the employee relationship. Entity framework wouldn't know which relationship to assign the property. If you created another property on the user like
public int? OwnedOrganizationID { get; set; }
public virtual OrgList OwnedOrg { get; set; }
and change the statement to
entity.HasOne(e => e.ownerUser)
.WithOne(e => e.OwnedOrg)
.HasForeignKey<OrgList>(e => e.ownerId)
.OnDelete(DeleteBehavior.Cascade);
I imagine it should work.

Many to one migrations fails on foreign key long

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);

Entity Framework: Migration falsely adds foreign key column twice

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

Foreign Key with Fluent API - Code First

I am trying to establish foreign key to 2 classes using FluentAPI and bit confused on the way to implement it.
ApplicationUser uses ASP.NET Identity model and has UserId as string
public class ApplicationUser : IdentityUser
{
public virtual List<UserProduct> Orders { get; set; }
}
Product that has a composite key on columns ProductID and ProductCategoryID
public class Product
{
public int ProductID { get; set; }
public string ProductCategoryID { get; set; }
public virtual List<UserProduct> Orders { get; set; }
...
}
and another class UserProduct that will have many-to-many relationship between ApplicationUser and Product table
public partial class UserProduct
{
public string UserId { get; set; }
public int ProductID { get; set; }
public string ProductCategoryID { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual Product Product { get; set; }
}
The FluentAPI code looks like
modelBuilder.Entity<Product>().Property(t => t.ProductID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Product>().HasKey(x => new { x.ProductID, x.ProductCategoryID });
modelBuilder.Entity<UserProduct>().HasKey(x => new {x.UserId, x.ProductID, x.ProductCategoryID});
How do I establish the foreign key relationship of UserProduct with ApplicationUser and Product?
You can add the id-property (e.g. OrderId) to the class UserProduct and use this code to connect entities by foreign key.
modelBuilder.Entity<UserProduct>()
.HasRequired(x => x.User)
.WithMany(u => u.Orders)
.HasForeignKey(x => x.OrderId);

Categories

Resources