I have some doubt on entity framwork. I have two classes User and UserClaim. When I run migration there are 3 tables created, User, UserClaim and UserUserClaim.
Is the new table UserUserClaim created by EF (columns UserId, ClaimId) bcos to maintain the relationship I guess. Am I correct?
When inserting User with UserClaims the values will be automatically added to the table UserUserClaim but when I add UserClaims only the values are not added, why it is so?
Is it possible not to create table UserUserClaim ?
public class User{
[Key]
public Guid UserId { get; set; }
[MaxLength(200)]
public string Username { get; set; }
[Required]
public Guid Subject { get; set; }
public ICollection<UserClaim> Claims { get; set; } = new List<UserClaim>();
}
public class UserClaim
{
[Key]
public Guid UserClaimId { get; set; }
[MaxLength(250)]
[Required]
public string Type{ get; set; }
[Required]
public Guid Subject { get; set; }
[MaxLength(250)]
[Required]
public string Value { get; set; }
public ICollection<User> Users { get; set; }
}
migration code
migrationBuilder.CreateTable(
name: "UserUserClaim",
columns: table => new
{
ClaimsUserClaimId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UsersUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserUserClaim", x => new { x.ClaimsUserClaimId, x.UsersUserId });
table.ForeignKey(
name: "FK_UserUserClaim_UserClaims_ClaimsUserClaimId",
column: x => x.ClaimsUserClaimId,
principalTable: "UserClaims",
principalColumn: "UserClaimId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserUserClaim_Users_UsersUserId",
column: x => x.UsersUserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
});
1 - Yes, EF will automatically create said table for many-to-many relationships - users can have many claims, claim can be assigned to many users.
2 - You are adding a user and claims that are in relationship with said user - therefore this is reflected in the UserUserClaim table. Adding new claim does not create new relationship. If You'll add this new claim later to a user, entry will be made.
3 - No - said table always will be created when relation is many-to-many. You can only reconfigure its naming / keys etc. like this (more or less, example from my app):
builder.HasMany(x => x.Seasons)
.WithMany(x => x.DeparturePlans)
.UsingEntity<Dictionary<string, object>>(
"DeparturePlanSeason",
x => x.HasOne<Season>().WithMany(),
x => x.HasOne<DeparturePlan>().WithMany(),
x => x.ToTable("DeparturePlanSeason", "hub"));
This configures this third table for many-to-many relationship for entities called Season and DeparturePlan to be named "DeparturePlanSeason" in custom schema "hub".
Related
I have the following two tables:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
...
public ApplicationUser RegisteredPerson { get; set; }
}
public class ApplicationUser : IdentityUser
{
public DateTime DateJoined { get; set; }
public int PersonId { get; set; }
public string DisplayName { get; set; }
...
public Person Person { get; set; }
}
When creating a new migration to add the navigation properties, the following migration code is produced:
migrationBuilder.CreateIndex(
name: "IX_AspNetUsers_PersonId",
table: "AspNetUsers",
column: "PersonId",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_AspNetUsers_Persons_PersonId",
table: "AspNetUsers",
column: "PersonId",
principalTable: "Persons",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
I get the following error when trying to update the database with the new migration
CREATE UNIQUE INDEX [IX_AspNetUsers_PersonId] ON [AspNetUsers] ([PersonId]);
The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.AspNetUsers' and the index name 'IX_AspNetUsers_PersonId'. The duplicate key value is (0).
Suggestions for fix appreciated.
Thanks
I have tow tables with one-to-many relationship.
Flights Table
Destination Table
I have to use AirportCode from a Destination table for two columns in Flights table.
DepartureAirportCode - column_1
ArrivalAirportCode - c0lumn_2
How to do that by using EF code First?
I have tried with below codes. But That is not correct. I can add foreign key for only one column.
public class Flight
{
public int Id { get; set; }
[Required]
public string DepartureAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
[Required]
public string ArrivalAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
public Destination Airport { get; set; }
}
public class Destinations
{
public int Id { get; private set; }
public string AirportCode { get; private set; }
public IEnumerable<Flight> Flights { get; set; }
}
DbContext
builder.Entity<Flight>()
.HasOne(x => x.Airport)
.WithMany(x => x.Flights)
.HasForeignKey(x => x.ArrivalAirportCode)
.HasPrincipalKey(x => x.AirportCode);
builder.Entity<Flight>()
.HasOne(x => x.Airport)
.WithMany(x => x.Flights)
.HasForeignKey(x => x.DepartureAirportCode)
.HasPrincipalKey(x => x.AirportCode);
Edit: Added Migration file
I already have did migration and DB update for add ArrivalAirportCode as a foreignKey.
When I try to add a migration for DepartureAirportCode as a foreign key, I got this
migrationBuilder.DropForeignKey(
name: "FK_Flight_Destinations_ArrivalAirportCode",
table: "Flight");
// here dropped FK_Flight_Destinations_ArrivalAirportCode . But not added later.
migrationBuilder.DropForeignKey(
name: "FK_Flight_Departures_DepartureAirportCode",
table: "Flight");
// please ignore above line. Because already I used FK_Flight_Departures_DepartureAirportCode - foreign key from a different table named as Departures. Now I want to use FK_Flight_Destinations_DepartureAirportCode and FK_Flight_Destinations_ArrivalAirportCode from Destinations table
migrationBuilder.AddForeignKey(
name: "FK_Flight_Destinations_DepartureAirportCode",
table: "Flight",
column: "DepartureAirportCode",
principalTable: "Destinations",
principalColumn: "AirportCode",
onDelete: ReferentialAction.Cascade);
// added only FK_Flight_Destinations_DepartureAirportCode. I want two column with foreign key FK_Flight_Destinations_DepartureAirportCode and FK_Flight_Destinations_ArrivalAirportCode
Your problem is that you are trying to use the same properties for two different relationships between Flight and Destination. Both the Airport property on Flight and the Flights property on Destination are used for the "Arrival" relationship as well as for the "Destination" relationshio - and this is not possible with EF.
So in your Flight class you are missing a property for the second relationship to Destination as you cannot use the same property for Arrival and Depature (because they are different airports):
public class Flight
{
public int Id { get; set; }
[Required]
public string DepartureAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
[Required]
public string ArrivalAirportCode { get; set; } // sholud be foreign key of Destinations_AirportCode
public Destination DepartureAirport { get; set; }
public Destination ArrivalAirport { get; set; }
}
And same for Destination:
public class Destination
{
public int Id { get; private set; }
public string AirportCode { get; private set; }
public IEnumerable<Flight> ArrivalFlights { get; set; }
public IEnumerable<Flight> DepartureFlights { get; set; }
}
once you add it and change your configuration to:
builder
.Entity<Flight>()
.HasOne(x => x.ArrivalAirport)
.WithMany(x => x.ArrivalFlights)
.HasForeignKey(x => x.ArrivalAirportCode)
.HasPrincipalKey(x => x.AirportCode);
builder.Entity<Flight>()
.HasOne(x => x.DepartureAirport)
.WithMany(x => x.DepartureFlights)
.HasForeignKey(x => x.DepartureAirportCode)
.HasPrincipalKey(x => x.AirportCode);
it should work.
I working on building out a model that would represent a typical product that could be created in an e-commerce platform written using EF Core 2.0. See the model structure below
public class GSProduct : BaseEntity
{
[Required]
public string Name { get; set; }
public ICollection<GSProduct> BaseProducts { get; set; }
public ICollection<GSRelatedProduct> ParentProducts { get; set; }
public ICollection<GSRelatedProduct> ChildProducts { get; set; }
public ICollection<GSVendorProductInfo> VendorProductInfo { get; } = new List<GSVendorProductInfo>();
}
public class GSRelatedProduct
{
public virtual GSProduct ParentProduct { get; set; }
public Guid ParentProductId { get; set; }
public virtual GSProduct ChildProduct { get; set; }
public Guid ChildProductId { get; set; }
}
public class GSVendorProductInfo : BaseEntity
{
public GSContact Vendor { get; set; }
public Guid VendorId { get; set; }
public GSProduct Product { get; set; }
public Guid ProductId { get; set; }
public string VendorPartNumber { get; set; }
public int BaseUnits { get; set; }
public double Cost { get; set; }
public int MinOrderQty { get; set; }
public int OrderedInMultiples { get; set; }
}
This is what I have set up for the Fluent API.
modelBuilder.Entity<GSVendorProductInfo>()
.HasOne(pt => pt.Product)
.WithMany(p => p.VendorProductInfo)
.HasForeignKey(pt => pt.ProductId);
modelBuilder.Entity<GSVendorProductInfo>()
.HasOne(pt => pt.Vendor)
.WithMany(t => t.VendorProductInfo)
.HasForeignKey(pt => pt.VendorId);
modelBuilder.Entity<GSRelatedProduct>().HasKey(x => new { x.ParentProductId, x.ChildProductId });
modelBuilder.Entity<GSRelatedProduct>()
.HasOne(pt => pt.ParentProduct)
.WithMany(t => t.ParentProducts)
.HasForeignKey(pt => pt.ParentProductId);
modelBuilder.Entity<GSRelatedProduct>()
.HasOne(pt => pt.ChildProduct)
.WithMany(t => t.ChildProducts)
.HasForeignKey(pt => pt.ChildProductId);
Also including the migration
migrationBuilder.CreateTable(
name: "GSRelatedProducts",
columns: table => new
{
ParentProductId = table.Column<Guid>(nullable: false),
ChildProductId = table.Column<Guid>(nullable: false),
Optional = table.Column<bool>(nullable: false),
Quantity = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GSRelatedProducts", x => new { x.ParentProductId, x.ChildProductId });
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ChildProductId",
column: x => x.ChildProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ParentProductId",
column: x => x.ParentProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
The scaffolding / migration is working fine and I can actually create products without a problem that include all of the relationships. The issue arises when I try to add a 'RelatedProduct' to the Product model.
I set the ParentProductId and the ChildProductId accordingly and when I create or update the entity it sets both the ParentProductId and the ChildProductId value to the ParentProductId.
I've followed the code through my debugger and it is correct up until the point where I call _context.Update(entity). After that both of the Ids in the RelatedProduct model are set to the same value.
I've got no idea why this is happening any suggestions would be very helpful.
I think there is a problem with the way your migration is generated. From the above code I am not sure it is clear what is the purpose of the child property in GSRelatedProduct. If you look at the migration you will see that the same constraint is set for both parent and child:
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ChildProductId",
column: x => x.ChildProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_GSRelatedProducts_GSProducts_ParentProductId",
column: x => x.ParentProductId,
principalTable: "GSProducts",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
Both Parent and Child have the same principal table and principal column. They will both get the same value from GSProducts table. What your logic or business process is I cannot figure out, but you are actually getting the desired effect. Is the child supposed to point to something else? In your code you are probably assigning different values to product and child, but this seems to be somehow overwritten. Basically, your code first is thinking that both and parent should have the same value. In other words, Child seems to be redundant.
Is your GSProduct table a self-referencing table where you keep products and subproducts together? If so, then you need an additional column for this purpose like ParentId that points to the Id in order to get the desired relationship.
I have models in a many-to-many relationship:
User
public class User
{
public int Id { get; set; }
public int CompanyId { get; set; }
[Required]
[StringLength(100)]
public string Username { get; set; }
[Required]
public string PasswordHash { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
public ICollection<UserRole> UserRoles { get; set; }
}
Role
public class Role
{
public int Id { get; set; }
public int CompanyId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[StringLength(500)]
public string Description { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
public ICollection<RolePrivilege> RolePrivileges { get; set; }
}
UserRole
public class UserRole
{
public int Id { get; set; }
public int UserId { get; set; }
public int RoleId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("RoleId")]
public Role Role { get; set; }
}
When I created migrations and then tried update-database, it threw an error of multiple cascade paths. The solution to this was to make On Delete, No Action so I added this in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserRole>()
.HasIndex(e => new { e.UserId, e.RoleId })
.IsUnique();
modelBuilder.Entity<UserRole>()
.HasOne(e => e.User)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<UserRole>().ToTable("UserRoles");
}
Now the tables are created but one thing that I wasn't expecting is its making an extra column. The migration code looks like this after generating:
migrationBuilder.CreateTable(
name: "UserRoles",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<int>(type: "int", nullable: false),
UserId1 = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserRoles", x => x.Id);
table.ForeignKey(
name: "FK_UserRoles_Roles_RoleId",
column: x => x.RoleId,
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserRoles_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_UserRoles_Users_UserId1",
column: x => x.UserId1,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
As you can see, it added an extra column, UserId1.
What am I doing wrong or how do I prevent this from happening?
This is a result of a typical relationship fluent configuration mistake - using a parameterless overload of Has / With (effectively telling EF that there is no corresponding navigation property) while actually a navigation property exists. In that case EF will map the missing navigation property to another relationship with no navigation property at the other end and default by convention FK property/column name.
To fix the issue, make sure to use the correct overloads which represent the presence/absence of a navigation property (and update them according in case you add/remove navigation property). In your case, replace
.WithMany()
with
.WithMany(e => e.UserRoles)
I have following classes
public class Employer
{
[Key]
public Int64 EmployerID { get; set; }
public String CompanyName { get; set; }
public virtual List<Employee> Employees { get; set; }
}
public class Employee
{
[Key]
public Int64 EmployeeID { get; set; }
public String EmployeeName { get; set; }
public virtual Employer EmployerInfo { get; set; }
}
In the Database context I have set the relation as
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired()
.Map(x => x.MapKey("EmployerID"));
After executing some actions, database gets created with Employee table having EmployerID as foreign key and one extra key EmployerInfo_EmployerID.
Now when I fetch employer data, I am getting employee details with it.
But when I tried to fetch employee data I am getting EmployerInfo as null. This is because I need relationship from Employee to EmployerInfo.
How do I set the bi-directional relationship in this context?
You need to update your fluent so your relationship mapping contains both ends:
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired(e => e.EmployerInfo)
.Map(x => x.MapKey("EmployerID"));