.NET Core creating non-nullable FK constraint with IdentityUser - c#

I am trying to create a relationship from my main IdentityUser to another class, using the EF migrations in .NET Core 2.2. There is an issue I am running into, and that is the migrations are being created with a null FK constraint. This is something I do not want.
I fear, that I should not be manually editing the migration file (to fix this issue), as when I change these models in the future, my manual edit will be overwritten (because my classes are somehow coded incorrectly).
These are the classes I have..
public class ApplicationUser : IdentityUser
{
public List<Purchase> Purchases { get; set; }
}
public class Purchase
{
public int Id { get; set; }
// others
}
The resulting migration file has a nullable: true property set on ApplicationUserId
migrationBuilder.CreateTable(
name: "Purchase",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ApplicationUserId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Purchase", x => x.Id);
table.ForeignKey(
name: "FK_Purchase_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
Not correct. So, I add properties in the Purchase class (like I have in other POCOs in my project)..
public class Purchase
{
public int Id { get; set; }
// others
public int ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
and re-run the migration regeneration..
migrationBuilder.CreateTable(
name: "Purchase",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ApplicationUserId = table.Column<int>(nullable: false),
ApplicationUserId1 = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Purchase", x => x.Id);
table.ForeignKey(
name: "FK_Purchase_AspNetUsers_ApplicationUserId1",
column: x => x.ApplicationUserId1,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
I now get the proper non-nullable constraint, but it is a duplicate. (There is a 1 tacked on). My questions are:
Is it safe for me to simply modify the migration file and call it a day?
How can I create a relationship between my ApplicationUser and Purchase classes so the migration file that is generated gives me a non-nullable FK constraint?

1) You can, but each consequent migration will remake this field an d cause you to have an issue. Also since the data type does not match it will not work as expected
2) The id type of the foreign key is wrong. Application user has a primary key of type string but you are using int so it is failing to recognize the correct foreign key.
After you correct that, put the Required attribute on the class. So the final class would look like
using System.ComponentModel.DataAnnotations;
public class Purchase
{
public int Id { get; set; }
// others
[Required]
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}

You can generate the migration script using the Add-Migration command, after that you can edit the migration source code and add at least 1 record right after the creation of the new table using:
migrationBuilder.InsertData(
table: "Organizations",
columns: new[] { "OrganizationName", "IsDeleted" },
values: new object[,]
{
{ "Test organization", false }
});
After that you should be able to manage the creation of the new foreign key field, giving 1 as default value.

Related

Why is EF in .NET randomly adding a column suffixed with 1?

I have a Group model, which has a has-one relation for GroupPermissionSet, which in turn contains permissions.
The Group model
public class Group
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int? PermissionSetId { get; set; }
public GroupPermissionSet? PermissionSet { get; set; }
}
However, the resulting migrations from .NET EF migrations adds the following
migrationBuilder.CreateTable(
name: "Groups",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
PermissionSetId = table.Column<int>(type: "int", nullable: true),
PermissionSetId1 = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Groups", x => x.Id);
table.ForeignKey(
name: "FK_Groups_GroupPermissionSets_PermissionSetId1",
column: x => x.PermissionSetId1,
principalTable: "GroupPermissionSets",
principalColumn: "Id");
})
.Annotation("MySql:CharSet", "utf8mb4");
Which contains a PermissionSetId1 for some weird random reason. I think this is a bug, unless it's me who misunderstood how one-to-one relations are made in .NET.
I have tried removing the PermissionSetId from the model, this did not work either.
The PermissionSetId1 column that you see in the migration code is most likely a result of an incomplete migration rollback or previous incomplete migration. It could also be due to an error in the initial migration code.
To fix this issue, you can try the following steps:
Delete the existing migration files from your project.
Update the Group model by removing the PermissionSetId property and using fluent API to define the one-to-one relationship between Group and GroupPermissionSet as follows:
public class Group
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public GroupPermissionSet? PermissionSet { get; set; }
}
public class GroupPermissionSet
{
public int Id { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
// ... other properties
}
// In your DbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasOne(g => g.PermissionSet)
.WithOne(ps => ps.Group)
.HasForeignKey<GroupPermissionSet>(ps => ps.GroupId);
}
Create a new migration by running the Add-Migration command in the Package Manager Console or the dotnet ef migrations add command in the terminal.
Apply the migration by running the Update-Database command or the dotnet ef database update command.
This should generate a new migration that defines the one-to-one relationship without the extra PermissionSetId1 column.
If the above steps don't work, you may need to manually delete the PermissionSetId1 column from the database using a database management tool before creating and applying the new migration.

C# cannot update database with Entity Framework

I have a problem, unfortunately I can not fix it and can not find a proper solution to this.
Does anyone know what the problem is?
I am trying to update my Tours object with a new TravelCountry.
Migration Code:
migrationBuilder.DropColumn(
name: "TravelCountry",
table: "Tours");
migrationBuilder.AddColumn<int>(
name: "TravelCountryId",
table: "Tours",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "TravelCountries",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_TravelCountries", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Tours_TravelCountryId",
table: "Tours",
column: "TravelCountryId");
migrationBuilder.AddForeignKey(
name: "FK_Tours_TravelCountries_TravelCountryId",
table: "Tours",
column: "TravelCountryId",
principalTable: "TravelCountries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
It fails at "migraitonBuilder.AddForeignKey" with the error:
Cannot add or update a child row: a foreign key constraint fails (gam_db.#sql-1e1c_fe, CONSTRAINT FK_Tours_TravelCountries_TravelCountryId FOREIGN KEY (TravelCountryId) REFERENCES travelcountries (Id) ON DELETE CASCADE)
Heres my Tour object:
public class Tour
{
[Key]
public int Id { get; set; }
[Required]
public TravelCountry? TravelCountry { get; set; }
}
And here is my TravelCountry object:
public class TravelCountry
{
[Key]
public int Id { get; set; }
[Required]
public string? Name { get; set; }
}
you need to add a foreign key to your tour model in order to create a relationship between this twp tables
for example:
public int? CategoryId { get; set; }
[ForeignKey("TravelCountryId")]
also make sure you have all the nugets and dont forget the command update-database in console package manger after adding the migrations
Just one question: why is the entity column [required] whilst the variable in the code can be null? Maybe delete the question marks in the declarations because [required] means that this value cannot be null

EF Core: duplicated foreign key in migration

It's my first project with using Entity Framework Core and sometimes the relationships are a bit confusing for me. The problem is when I'm creating a database migration, it always add foreign keys I haven't defined. So I can't use the database because inserting new values throws a duplicated entry exception.
But first some code of my config:
public class User : IdentityUser
{
public virtual List<SignatureToUser> Signatures { get; set; }
}
public class SignatureToUser
{
[Key]
[Required]
public int SignatureToUserID { get; set; }
public int SignatureID { get; set; }
[Required]
[ForeignKey("SignatureID")]
public virtual Signature Signature { get; set; }
public string SignatureUserID { get; set; }
[Required]
[ForeignKey("SignatureUserID")]
public virtual User User { get; set; }
}
And that is the builder options I use
modelBuilder.Entity<SignatureToUser>().HasOne(l => l.User).WithOne().OnDelete(DeleteBehavior.ClientNoAction);
modelBuilder.Entity<SignatureToUser>().HasOne(l => l.Signature).WithOne().OnDelete(DeleteBehavior.ClientNoAction);
modelBuilder.Entity<SignatureToUser>().HasIndex(l => l.SignatureUserID).IsUnique(false);
modelBuilder.Entity<SignatureToUser>().HasIndex(l => l.SignatureID).IsUnique(false);
And now the problem: this is the generated migration:
migrationBuilder.CreateTable(
name: "SignatureToUser",
columns: table => new
{
SignatureToUserID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SignatureID = table.Column<int>(type: "int", nullable: false),
SignatureUserID = table.Column<string>(type: "nvarchar(450)", nullable: false),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SignatureToUser", x => x.SignatureToUserID);
table.ForeignKey(
name: "FK_SignatureToUser_AspNetUsers_SignatureUserID",
column: x => x.SignatureUserID,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_SignatureToUser_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
As you can see there is a foreign key UserId added as a duplicate to my already existing key SignatureUserID.
How can I disable the creation of this additional foreign key?
In my research, I also heard about base classes could be a problem. Is this still a problem? I also tried something like that to trick the base class, but there were no changes.
public class User : IdentityUser
{
[Key]
public string Id { get { return base.Id; } set { base.Id = value; } }
}
Thank you for reading :) I hope anyone has an idea

EF Core 5 - Updating an entity in many-to-many relation throws "Violation of PRIMARY KEY" error

I am working with .NET 5 and using EF Core and MSSQL for database.
I have 2 classes that have many to many relationship between them. Those were migrated from models to DB.
example of DB tables
Migration:
migrationBuilder.CreateTable(
name: "TournamentUser",
columns: table => new
{
TournamentsId = table.Column<int>(type: "int", nullable: false),
UsersId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TournamentUser", x => new { x.TournamentsId, x.UsersId });
table.ForeignKey(
name: "FK_TournamentUser_Tournaments_TournamentsId",
column: x => x.TournamentsId,
principalTable: "Tournaments",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_TournamentUser_Users_UsersId",
column: x => x.UsersId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
Models:
public class Tournament
{
[Key]
public int Id { get; set; }
[Required, MaxLength(20)]
public string Name { get; set; }
[Required]
public int MaxPlayers { get; set; }
public int PlayersCount { get; set; }
[Required]
public DateTime Start { get; set; }
public bool IsStarted { get; set; }
public bool IsEnded { get; set; }
public List<User> Users { get; set; }
}
public class User
{
[Key]
public int Id { get; set; }
[Required, MaxLength(15)]
public string Username { get; set; }
[Required]
public string Password { get; set; }
[Required, MaxLength(20)]
public string Name { get; set; }
[Required, MaxLength(20)]
public string Surname { get; set; }
[JsonIgnore]
public List<Tournament> Tournaments { get; set; }
}
So as I try to update Tournaments model
[HttpPut]
public async Task<ActionResult<Tournament>> Put([FromBody] Tournament tournament)
{
_context.Tournaments.Update(tournament);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTournament), new { id = tournament.Id }, tournament);
}
I get this error
Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of
PRIMARY KEY constraint 'PK_TournamentUser'. Cannot insert duplicate
key in object 'dbo.TournamentUser'. The duplicate key value is (1, 1).
What could be possible solutions for this? I would like to keep my many to many table udpated as Tournament's User can be removed or added each time I update Tournament.
EDIT:
Following shows the tournament object data -
The Solution:
To update the Tournament entity only, use the following approach -
_context.Entry(tournament).State = EntityState.Modified;
await _context.SaveChangesAsync();
The Explanation:
The Update() method marks the entity as Modified if it has an Id (auto generated primary key) value, and as Added if it doesn't have an Id value. Same is true for any related entities if you call the method with an entity graph (entity with related entities). Then on the next SaveChanges() call, EF generates update and/or insert command for the entity (or entities) based on whether they are marked as Modified or Added. The point is - the Update() method does not only update an entity, it can insert new entities too.
In your case, looking at the models, EF can tell Tournament and User are in a many-to-many relationship, and there is a joining entity TournamentUser at database level. But since you are receiving the entity graph from outside, they are not tracked by EF and it cannot tell whether the TournamentUser entities for this tournament and the related users already exist in database or not.
As you are calling the Update() method with the entity graph, the tournament, the related users, and the joining entity relating them (TournamentUser) all becomes subject for update operation. And EF tries to create the joining entity just like it would create/insert a new entity without an Id value.
EF generates insert command for a TournamentUser entity and update commands for Tournament and User entities. But at database level, since the TournamentUser link already exists you get the Violation of PRIMARY KEY error.
The -
_context.Entry(tournament).State = EntityState.Modified;
suggested above explicitly marks only the tournament as Modified. Therefore, EF doesn't bother to look at any related entities, or to infer any relationship, and only an update command is generated for the tournament.
I have found a solution from this post
I have modified the code a bit, since originally it was removing users from table and I got the result I wanted
public async Task<ActionResult<Tournament>> Put([FromBody] Tournament tournament)
{
var _tournament = _context.Tournaments.Include(t => t.Users).FirstOrDefault(t => t.Id == tournament.Id);
_context.Entry(_tournament).CurrentValues.SetValues(tournament);
var _users = _tournament.Users.ToList();
// Adds new Users
foreach (var tournamentUser in tournament.Users)
{
if (_users.All(i => i.Id != tournamentUser.Id))
{
_tournament.Users.Add(tournamentUser);
}
}
// Removes old Users
foreach (var tournamentUser in _users)
{
if (tournament.Users.FirstOrDefault(tu => tu.Id == tournamentUser.Id) == null)
{
_tournament.Users.Remove(tournamentUser);
}
}
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTournament), new { id = tournament.Id }, tournament);
}
Not sure if it's not over complex but it works
Try to remove users from your torment object if you want to update tournment.
public async Task<ActionResult<Tournament>> Put([FromBody] Tournament tournament)
{
tournament.Users=null;
_context.Tournaments.Update(tournament);
await _context.SaveChangesAsync();
.....
}
Or if you want to add an user to your tournament
var existing= _context
.Tournaments
.Include(u=>u.Users)
.FirstOrDefault(t=> t.Id== tournament.Id);
if(existing==null) return ...error
if( existing.Users==null) existing.User=new List<User>();
if( existing.Users.Any(u=>u.Id=tournment.User.Id) return ...error
existing.Users.Add(tournment.User);
_context.Entry(existing).State = EntityState.Modified;
_context.SaveChanges();
It looks ackward but you are paying for not adding many-to-many relantionship table explicitly. If you use UserTournment table code will be shorter.
You can try this. I never tried it. Let me know if it works:
var existing = _context
.Tournaments
.Include(u=>u.Users)
.FirstOrDefault(t=> t.Id== tournament.Id);
if(existing==null) return ...error
_context.Entry(existing).CurrentValues.SetValues(tournment);
_context.SaveChanges();

Foreign Key Issues with Application User

I am using entity framework core and code first to make some tables.
I want one table with two Foreign keys, with both being the application user.
I have it built and running, but every time I try to add to the table I get an error
SqlException: Violation of PRIMARY KEY constraint 'PK_ApplicationUser'. Cannot insert duplicate key in object 'dbo.ApplicationUser'. The duplicate key value is (ef20a79d-e819-499d-b006-c32f5cf85577).
This might just be a database design issue, but I feel like that I am doing this should work.
Here is the models:
public class Test
{
public string TestId { get; set; }
[ForeignKey("Giver")]
public string GiverId { get; set; }
public virtual ApplicationUser Giver { get; set; }
[ForeignKey("Getter")]
public string GetterId { get; set; }
public virtual ApplicationUser Getter { get; set; }
}
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
[InverseProperty("Giver")]
public ICollection<CustomModels.Test> Giver { get; set; }
[InverseProperty("Getter")]
public ICollection<CustomModels.Test> Getter { get; set; }
}
Here is the code to create the table:
migrationBuilder.CreateTable(
name: "Tests",
columns: table => new
{
TestId = table.Column<string>(nullable: false),
GetterId = table.Column<string>(nullable: true),
GiverId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Tests", x => x.TestId);
table.ForeignKey(
name: "FK_Tests_ApplicationUser_GetterId",
column: x => x.GetterId,
principalTable: "ApplicationUser",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Tests_ApplicationUser_GiverId",
column: x => x.GiverId,
principalTable: "ApplicationUser",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Tests_GetterId",
table: "Tests",
column: "GetterId");
migrationBuilder.CreateIndex(
name: "IX_Tests_GiverId",
table: "Tests",
column: "GiverId");
This is how I am adding to the DB:
Test test = (new Test
{
Giver = await _userManager.GetUserAsync(User),
Getter = await _userManager.Users.FirstOrDefaultAsync(u => u.Email == "s#gmail.com")
});
_context.Tests.Add(test);
await _context.SaveChangesAsync();
I would like to be able to track which user is giving information and which user is getting information.
We were using just the user email for tracking, but we would have to search for the email every time and I feel like this would be a better long-term solution.
I should be able to add as my "test" rows and repeat users, ie user1 can give to user2 and vise-versa, and that would be 2 different rows.
My guess is _userManager.GetUserAsync(User) isn't operating on the same DbContext as _context. So _context isn't aware of that ApplicationUser and will try to create it. The resolution to this would be to Attach the ApplicationUser before saving.

Categories