Two Foreign Keys in Entity Framework Core - asp.net

I am using Code First approach while creating Database using Entity Framework Core. I would like to create two foreign keys pointing the same table.
My example shows User table which will hold userID and Message table which will hold both Receiver ID and Sender ID (what means both values have to point the same table).
Code for User:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string UserName { get; set; }
[Required]
[MaxLength(50)]
public string Password { get; set; }
public virtual ICollection<Message> MessagesSent { get; set; }
public virtual ICollection<Message> MessagesReceived { get; set; }
}
For Message:
public class Message
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public User Sender { get; set; }
public User Receiver { get; set; }
public int senderId { get; set; }
public int receiverId { get; set; }
[MaxLength(500)]
public string message { get; set; }
}
I am using ASP.NET Core 2 and I am a newbie.
I was trying to use this solution, but unfortunately, I couldn't manage to override OnModelCreating method. It shows that it doesn't exist.
PS. don't mind password field, it is only for testing purpose.
Thank you!

I managed to make it works using Fluent API.
Code in my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Message>()
.HasOne(p => p.Receiver)
.WithMany(t => t.MessagesReceived)
.HasForeignKey(m => m.ReceiverId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Message>()
.HasOne(p => p.Sender)
.WithMany(t => t.MessagesSent)
.HasForeignKey(m => m.SenderId)
.OnDelete(DeleteBehavior.Restrict);
}
What is more, I've discovered a problem with not set User deletion behaviour.
There are two options to solve it.
First is keeping Messages if User was deleted:
.OnDelete(DeleteBehavior.Restrict);
Or second which will remove Messages:
.OnDelete(DeleteBehavior.Cascade);

Related

Establishing one to one relations with Entity Framework 7

Having the following parent class:
[Table("smart_recharge_registro")]
public class SmartRechargeRegistro
{
[Key]
public int id { get; set; }
public SmartRechargeRequest request { get; set; }
public SmartRechargeProceso proceso { get; set; }
public SmartRechargeResponse response { get; set; }
}
Which in turn references the following child classes:
[Table("smart_recharge_request")]
public class SmartRechargeRequest
{
public String nombreDeUsuario { get; set; }
public String passwordDeUsuario { get; set; }
public String msisdnSuscriptor { get; set; }
}
and:
[Table("smart_recharge_proceso")]
public class SmartRechargeProceso
{
[Key]
public int id { get; set; }
public String carrierId { get; set; }
public String cliente { get; set; }
public String network { get; set; }
}
and lastly:
[Table("smart_recharge_response")]
public class SmartRechargeResponse
{
public Boolean responseSuccess { get; set; }
public int responseCode { get; set; }
public String? responseDetails { get; set; }
}
The Add-Migration and Update-Database command execute without problems. However, when I try to save
await _repository.RegistroColeccion.AddAsync(registro);
await _repositorio.SaveChangesAsync();
I get the following error:
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Cannot add
or update a child row: a foreign key constraint fails
(beservicebroker_dev.registro_eventos_srdotnet, CONSTRAINT
FK_registro_eventos_srdotnet_SmartRechargeProceso_procesoid FOREIGN
KEY (procesoid) REFERENCES smartrechargeproceso (id) O)
To solve the problem, I tried to create one-to-one relationships following this tutorial
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.request)
.WithOne(r => r.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeRequest>(r => r.id);
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.proceso)
.WithOne(p => p.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeProceso>(p => p.id);
modelBuilder.Entity<SmartRechargeRegistro>()
.HasOne(s => s.response)
.WithOne(r => r.SmartRechargeRegistro)
.HasForeignKey<SmartRechargeResponse>(r => r.id);
Inside SmartRechargeRequest, SmartRechargeProceso and SmartRechargeResponse, added the following:
[JsonIgnore]
public SmartRechargeRegistro SmartRechargeRegistro { get; set; }
Also added inside SmartRechargeRequest and SmartRechargeResponse an id
[Key]
public int id { get; set; }
I'm still unable to test the endpoint because the SmartRechargeRequest and SmartRechargeResponse are completely disfigured in the swagger (even if the [JsonIgnore] or [IgnoreDataMember] annotations are set) due to the presence of that SmartRechargeRegistro object.
I'm pretty sure my solution is misguided and I'm getting the process completely wrong.
What would be the proper way to map one-to-one relationships for this case? Any help will be appreciated.
Please note that in reality, these classes are huge (dozens of properties), so it's not possible to merge all of them on a single table.

EF6 Many-to-many can not delete

I have created table using EF6 core using below code.
The problem is, UserA , UserB created and assigned the different claims. No issue at all. When assigning existing claims (for example UserB is assigned UserA's claims --> [tying to do] delete UserB's claims & Changing UserB's subject to UserA's subject) then I am deleting UserB's claim by subjectId this create the primary key duplicate issue in UserUserClaims tables bcos SubjectId and ClaimId already existing
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 List<UserClaimRelation> UserClaimRelations { get; set; }
}
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; }
public List<UserClaimRelation> UserClaimRelations { get; set; }
}
public class UserClaimRelation
{
public Guid UserClaimId{ get; set; }
public UserClaim Claim { get; set; }
public Guid UserSubject{ get; set; }
public User User { get; set; }
}
public class MyDbContext: DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<UserClaim> UserClaims { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>()
.HasMany(p => p.Claims)
.WithMany(p => p.Users)
.UsingEntity<UserClaimRelation>(
j => j
.HasOne(pt => pt.Claim)
.WithMany(t => t.UserClaimRelations)
.HasPrincipalKey(f => f.UserClaimId),
j => j.HasOne(p => p.User)
.WithMany(t => t.UserClaimRelations)
.HasPrincipalKey( t => t.Subject),
j => j.HasKey( t => new {t.UserClaimId, t.UserSubject})
);
}
I am trying to delete the Claims by SubjectId
public async Task<bool> DeleteUserClaimsBySubjectAsync(Guid subject)
{
if (subject == Guid.Empty)
{
throw new ArgumentNullException(nameof(subject));
}
var claims = await _context.UserClaims
.Where(x => x.Subject == subject).ToListAsync();
foreach (var claim in claims)
{
_context.UserClaims.Remove(claim);
}
//_context.UserClaims.RemoveRange(claims);
return (await _context.SaveChangesAsync() > 0);
}
While deleting claims in DeleteUserClaimsBySubjectAsync I am getting the below error
SqlException: Violation of PRIMARY KEY constraint 'PK_UserClaimRelation'. Cannot insert duplicate key in object 'dbo.UserClaimRelation'. The duplicate key value is (13229d33-99e0-41b3-b18d-4f72127e3971, 8acbbd40-1608-41f2-de59-08d9b58b83d3).
SQL Profiler Query
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [UserClaimRelation] ([UserClaimId], [UserSubject])
VALUES (#p0, #p1),
(#p2, #p3),
(#p4, #p5),
(#p6, #p7),
(#p8, #p9);
DELETE FROM [UserClaims]
WHERE [UserClaimId] = #p10 AND [ConcurrencyStamp] = #p11;
SELECT ##ROWCOUNT;
DELETE FROM [UserClaims]
WHERE [UserClaimId] = #p12 AND [ConcurrencyStamp] = #p13;
SELECT ##ROWCOUNT;
DELETE FROM [UserClaims]
WHERE [UserClaimId] = #p14 AND [ConcurrencyStamp] = #p15;
SELECT ##ROWCOUNT;
DELETE FROM [UserClaims]
WHERE [UserClaimId] = #p16 AND [ConcurrencyStamp] = #p17;
SELECT ##ROWCOUNT;
DELETE FROM [UserClaims]
WHERE [UserClaimId] = #p18 AND [ConcurrencyStamp] = #p19;
SELECT ##ROWCOUNT;
',N'#p0 uniqueidentifier,#p1 uniqueidentifier,#p2 uniqueidentifier,#p3 uniqueidentifier,#p4 uniqueidentifier,#p5 uniqueidentifier,#p6 uniqueidentifier,#p7 uniqueidentifier,#p8 uniqueidentifier,#p9 uniqueidentifier,#p10 uniqueidentifier,#p11 nvarchar(4000),#p12 uniqueidentifier,#p13 nvarchar(4000),#p14 uniqueidentifier,#p15 nvarchar(4000),#p16 uniqueidentifier,#p17 nvarchar(4000),#p18 uniqueidentifier,#p19 nvarchar(4000)',
#p0='6203BBC3-BD52-4E32-3DDD-08D9B86AB745',#p1='D860EFCA-22D9-47FD-8249-791BA61B07C7',
#p2='80B958C5-FF44-400F-3DDE-08D9B86AB745',#p3='D860EFCA-22D9-47FD-8249-791BA61B07C7',
#p4='A844CB59-5A00-488A-3DDF-08D9B86AB745',#p5='D860EFCA-22D9-47FD-8249-791BA61B07C7',#p6='794AB806-3215-45AF-3DE0-08D9B86AB745',#p7='D860EFCA-22D9-47FD-8249-791BA61B07C7',#p8='80EFF0DF-AE56-419A-3DE1-08D9B86AB745',#p9='D860EFCA-22D9-47FD-8249-791BA61B07C7',#p10='1A48CF6E-E1CD-4042-3DE4-08D9B86AB745',#p11=N'742674d8-3c59-4be4-ac09-38c6804acb66',#p12='258ABD95-6B2A-45CC-3DE6-08D9B86AB745',#p13=N'd206316a-71af-46b7-92e4-bddca669ad87',#p14='4FB1D2E3-18BE-4AFC-3DE3-08D9B86AB745',#p15=N'3eb17fb7-998b-47e1-a97f-3640cbd82b7a',#p16='6C2B9A18-5C8F-4068-3DE5-08D9B86AB745',#p17=N'415b2cc1-b50a-4ec8-872d-13b6342dcd33',#p18='951CEEA6-D057-4588-3DE2-08D9B86AB745',#p19=N'3dfd76dd-d515-4daa-893c-f1ae97aa063a'
Ok, so you want many-to-many relationship here - multiple user can have multiple different claims and multiple claims can be assigned to multiple users.
So first - Users:
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>();
}
Then, UserClaims
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; }
}
Notice that there is no navigation property leading to UserClaimRelation in any of the above. In the matter of fact, we remove this UserClaimRelation as an separate entity model entirely - it is not needed for this example. You can map it if you really want, but lets focus on making this work.
Now, the relationship setup:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(x => x.Claims)
.WithMany(x => x.Users)
.UsingEntity<Dictionary<string, object>>(
"UserClaimRelation",
x => x.HasOne<User>().WithMany(),
x => x.HasOne<UserClaim>().WithMany(),
x => x.ToTable("UserClaimRelation"));
}
Now, after updating / recreating / reapplying migrations to the database (depends on your way of doing things) you should be able to delete the claims by subject without modifying your DeleteUserClaimsBySubjectAsync method.
This should set it all up and create intermediary table.
Validate this code, I was typing from memory
I applied the code. Tables created like below image. But when I try to update an userA's subject with userB's Subject (same claims of userB) I got the below error
Updating
SqlException: Violation of PRIMARY KEY constraint 'PK_UserClaimRelation'. Cannot insert duplicate key in object 'dbo.UserClaimRelation'. The duplicate key value is (bd7eb87f-2a5f-4921-758e-08d9b97d3121, 13229d33-99e0-41b3-b18d-4f72127e3971). bcos its trying to insert in UserClaimRelation, same as the profiler query I given initially.

How to define two FK in the same table?

I have a table called User which inherit the properties from IdentityUser, inside that table I added a reference to the UserFriendship table which need to store all the user friendship:
public class User : IdentityUser
{
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
public virtual ICollection<UserFriendship> UserFriendship { get; set; }
}
Essentially the UserFriendship contains two users, who are those who have a common friendship, this is the model definition:
public class UserFriendship
{
public int Id { get; set; }
[Key, ForeignKey("UserA")]
public string UserAId { get; set; }
public User UserA { get; set; }
[Key, ForeignKey("UserB")]
public string UserBId { get; set; }
public User UserB { get; set; }
[Required]
public DateTime DateTime { get; set; }
}
I defined the UserA and the UserB which are two FK of a User that are contained inside AspNetUsers table.
Now inside the FluentAPI I declared the following:
builder.Entity<UserFriendship>(entity =>
{
entity.HasKey(f => f.Id);
entity.HasOne(u => u.UserA)
.WithMany(n => n.UserFriendships)
.HasForeignKey(u => u.UserAId)
.IsRequired();
entity.HasOne(u => u.UserB)
.WithMany(n => n.UserFriendships)
.HasForeignKey(u => u.UserBId)
.IsRequired();
});
when I execute this command:
add-migration InitialMigration -context MyAppContext
I'll get:
Cannot create a relationship between 'User.UserFriendships' and 'UserFriendship.UserB', because there already is a relationship between 'User.UserFriendships' and 'UserFriendship.UserA'. Navigation properties can only participate in a single relationship.
I'm not an expert of EnityFramework, but based on that error I think that I cannot define two FK in the same table?
Sorry for any mistake, thanks.
You can define more than one FK in table.
The problem here is you are pointing two times to one navigation property - UserFriendships. The solution would be to create two navigation properties.
Navigation properties are used to browse the related data for specified foreign-key (you have one-to-many relationship) of entity.
Try this:
public class User
{
public string FirstName { get; set; }
public DateTime BirthDate { get; set; }
public ICollection<UserFriendship> UserAFriendships { get; set; }
public ICollection<UserFriendship> UserBFriendships { get; set; }
}
public class UserFriendship
{
public int Id { get; set; }
public string UserAId { get; set; }
public User UserA { get; set; }
public string UserBId { get; set; }
public User UserB { get; set; }
public DateTime DateTime { get; set; }
}
And define the relationship through fluent api as following:
modelBuilder.Entity<UserFriendship>(entity =>
{
entity.HasKey(f => f.Id);
entity.HasOne(u => u.UserA)
.WithMany(n => n.UserAFriendships)
.HasForeignKey(u => u.UserAId)
.IsRequired();
entity.HasOne(u => u.UserB)
.WithMany(n => n.UserBFriendships)
.HasForeignKey(u => u.UserBId)
.IsRequired();
});
What is more - you don't need to specify attributes Key, ForeignKey if you use Fluent API.

EF core , Code First Table rename not detected for migration definition, Scafolder is empty

I get empty migration builder when I change a class name in EF core.
In older EFs , it usually auto generate the code for renaming tables.
But not working in EF core
public class EventComment : Comment
{
[Key]
public int CommentID { get; set; }
public int? ParentID { get; set; }
public virtual EventComment Parent { get; set; }
public int EventID { get; set; }
public virtual EventMaster EventMaster { get; set; }
}
public class Comment
{
public string CommentTitle { get; set; }
public string CommentDetails { get; set; }
public int UpVoteCount { get; set; }
public int DownVoteCount { get; set; }
public int CommentEmotion { get; set; }
public string CommentedByID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
now changing EventComment to CommentMaster. The migration is empty.
I use fluent API for configuring
builder.Entity<EventComment>()
.HasOne(e => e.EventMaster)
.WithMany()
.HasForeignKey(m => m.EventID);
builder.Entity<EventComment>()
.HasOne(e => e.ApplicationUser)
.WithMany()
.HasForeignKey(m => m.CommentedByID);
builder.Entity<EventComment>()
.HasOne(e => e.Parent)
.WithMany()
.HasForeignKey(x => x.ParentID);
This is because of the EF Core default table mapping convention (highlights are mine):
By convention, each entity will be setup to map to a table with the same name as the DbSet<TEntity> property that exposes the entity on the derived context. If no DbSet<TEntity> is included for the given entity, the class name is used.
I guess this is different from the previous EF. The essential part is that although you renamed the entity class, if you keep the old DbSet property name, the table name will not change.

Many to many relation between Identity and custom table. EF7 - Code first

How can I make many to many relation between AspNetRoles from Identity 3.0 and my custom table? I want simple 3 table, with both PermissionId and RoleId, something like AspNetUsersRole. I have something like this:
public class Permission
{
public int PermissionId { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationRole> Roles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<Permission> Permissions { get; set; }
}
But when I want to add migration, I got error:
Unable to determine the relationship represented by navigation property 'ApplicationRole.Permissions' of type 'ICollection<Permission>'. Either manually configure the relationship, or ignore this property from the model.
EF Core (EF7) does not currently support many to many relationship without a join entity. (Reference)
So, what you should do is to create an entity class for the join table and mapping two separate one-to-many relationships. Like;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Regarding to this question answer, it can be done more easily like this-
class Photo
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
class PersonPhoto
{
public int PhotoId { get; set; }
public Photo Photo { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
class Person
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
Be sure to configure PersonPhoto with a composite key:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonPhoto>().HasKey(x => new { x.PhotoId, x.PersonId });
}
To navigate, use a Select:
// person.Photos
var photos = person.PersonPhotos.Select(c => c.Photo);
Add This namespace-
using Microsoft.AspNetCore.Identity;
public class Permission
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PermissionId { get; set; }
public string Name { get; set; }
public string UserIdFK { get; set; } //Foreign Key of Identity tbl
[ForeignKey("UserIdFK")]
public IdentityUser UserDetail { get; set; }
}
That's it, Happy coding :)

Resources