EF6 Many-to-many can not delete - .net-core

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.

Related

The DELETE statement conflicted with a REFERENCE constraint

I have the following entities.
public class GroupUser
{
public long Id { get; set; }
public Group Group { get; set; }
public long GroupId { get; set; }
public string UserId { get; set; }
public User User { get; set; }
}
public class User : IdentityUser
{
public List<GroupUser> GroupUsers { get; set; }
}
public class Group
{
public long Id { get; set; }
public List<GroupUser> GroupUsers { get; set; }
}
I get the delete The DELETE statement conflicted with the REFERENCE constraint "FK_GroupUsers_AspNetUsers_UserId". The conflict occurred in database "db", table "dbo.GroupUsers", column 'UserId' when I try to delete a user even though I make sure i delete existing entities referencing the id of the user before actually deleting the user like below;
var user = await _userManager.FindByEmailAsync(email);
var groupUsers = _context.GroupUsers.Where(c => c.UserId == user.Id);
_context.RemoveRange(groupUsers);
await _context.SaveChangesAsync();
_context.Remove(user);
await _context.SaveChangesAsync();

EF Core 3.18 get sum and count from related table

I have a web api where I am trying to get sum and count of a related table. Using .net core 3 and EF Core 3.1.8.
This is what I have tried:
_context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings.GroupBy(g => g.Bookid).Select(s => new { SumAllVotes = s.Sum(item => item.Rating) }))
.ToListAsync();
But I just get an error message. (see below).
I find it difficault debugging with EF Core as I dont know where it is going wrong. Have been trying a couple of hours, but whatever I write I get the same error message.
Thought maybe you guys were able to see what was wrong.
What I want
I am trying to get Sum of all Rating inside table Ratings.
Rating contains only 0 or 1. And I am trying to sum ratings on each bookid. I wanted to have it in this class public int SumAllVotes { get; set; }.
Because I list out all Books...and one of the properties will then be SumAllVotes. (And also CountAllVotes, when I have finished this problem).
By the end I will have a SumAllVotes and CountAllVotes and can calculate the percentage of how many have pressed "1".
Error message:
An unhandled exception occurred while processing the request.
InvalidOperationException: Lambda expression used inside Include is
not valid.
Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression
source, Expression expression, bool thenInclude)
What I have tried:
[HttpGet]
public async Task<ActionResult<IEnumerable<Books>>> GetBooks()
{
Guid userid = Guid.Parse(this.User.FindFirst(ClaimTypes.NameIdentifier).Value);
return await _context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings.GroupBy(g => g.Bookid).Select(s => new { SumAllVotes = s.Sum(item => item.Rating) }))
.ToListAsync();
}
Books and Ratings are defined as -
public partial class Books
{
public Books()
{
Bookmarks = new HashSet<Bookmarks>();
Comments = new HashSet<Comments>();
Favourites = new HashSet<Favourites>();
BookCategories = new HashSet<BookCategories>();
Resources = new HashSet<Resources>();
Ratings = new HashSet<Ratings>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public byte Scaleoffun { get; set; }
public byte Scaleoflearning { get; set; }
public int? Goal { get; set; }
public int? Secondgoal { get; set; }
public int? Thirdgoal { get; set; }
public int? Subjectid { get; set; }
public int? Categoryid { get; set; }
public string Language { get; set; }
public string Estimatedtime { get; set; }
public string Image { get; set; }
public int? File { get; set; }
public int? Ownerid { get; set; }
public DateTime Createdon { get; set; }
public DateTime? Lastmodifiedon { get; set; }
public string Active { get; set; }
public string Url { get; set; }
public Guid Userid { get; set; }
public byte? Grade { get; set; }
[NotMapped]
public int SumAllVotes { get; set; }
[NotMapped]
public int CountAllVotes { get; set; }
public virtual Categories Category { get; set; }
public virtual Curriculum GoalNavigation { get; set; }
public virtual Users Owner { get; set; }
public virtual Curriculum SecondgoalNavigation { get; set; }
public virtual Subjects Subject { get; set; }
public virtual Curriculum ThirdgoalNavigation { get; set; }
public virtual ICollection<Bookmarks> Bookmarks { get; set; }
public virtual ICollection<Comments> Comments { get; set; }
public virtual ICollection<Favourites> Favourites { get; set; }
public virtual ICollection<BookCategories> BookCategories { get; set; }
public virtual ICollection<Resources> Resources { get; set; }
public virtual ICollection<Ratings> Ratings { get; set; }
}
public partial class Ratings
{
public int Id { get; set; }
public int? Bookid { get; set; }
public string Type { get; set; }
public Int16? Rating { get; set; }
public Guid Userid { get; set; }
public string Subject { get; set; }
public DateTime Createdon { get; set; }
public DateTime? Modifiedon { get; set; }
public byte? Active { get; set; }
public virtual Books Book { get; set; }
//public virtual Users User { get; set; }
}
These are some other solutions I have tried, but got the same error message:
.Include(r=> r.Ratings.Sum(i=>i.Rating))
and
.Include(r => new { m = r.Ratings.GroupBy(g => g.Bookid) })
You don't need to group child entities by parent's Id. When you Include one-to-many child entities, they are added to their parent's child list, and hence grouped by their parent's identity, based on the relationship between them. All you need to do is tell EF what values you want from that child list.
Change your query to -
_context.Books
.Include(r => r.BookCategories)
.Include(r => r.Resources)
.Include(r => r.Ratings)
.Select(p => new
{
// set ALL the primitive properties from Books entity
Id = p.Id,
Title = p.Title,
// etc ...
// set the computed properties
CountAllVotes = p.Ratings.Count,
SumAllVotes = p.Ratings.Sum(x => x.Rating)
// set the related entities
BookCategories = p.BookCategories,
Resources = p.Resources
})
.ToListAsync();
AutoMapper has a ProjectTo method that generates the required query and does the projection (the Select part) automatically. You can use that to avoid the hassle of setting all those properties manually.
I suggest you don't use Include with Select. Read article how to make queries with Projection (Select). Note, that Rating.Rating is nullable and you need to handle this. Here is a possible code sample:
var view = await _context.Books
.Where(your condition)
.Select(item => new
{
//Todo: fill other props
SumAllVotes = item.Ratings.Sum(rating => (Int16?) rating.Rating),
CountAllVotes = item.Ratings.Count,
})
.ToListAsync()

EF Core 3.1.4 Navigation property returning null in One-To-One relationship

I am having a problem where a navigation property is always returning null in a One-to-One relationship in EF Core 3.1.4.
My models are structured like so:
public class UserCredential
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public byte[] Salt { get; set; }
public bool IsLocked { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public DateTime? DeletedDate { get; set; }
public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Suffix { get; set; }
public bool IsDeleted { get; set; }
public List<Address> Addresses {get;set;}
public DateTime DOB { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public DateTime? DeletedDate { get; set; }
public Guid UserCredentialId { get; set; }
public UserCredential UserCredential { get; set; }
}
Based off what I understand, that should have been enough for EF Core to infer the One-To-One relationship. But it didnt work so I defined the relationshup in my dbContext like so:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserProfile>(entity =>
{
entity.HasOne(up => up.UserCredential)
.WithOne(uc => uc.UserProfile)
.HasForeignKey<UserProfile>( up => up.UserCredentialId);
});
}
I checked the db and there is a foreign key from UserProfile -> UserCredentials defined in the table. Likewise both tables have Id as a primary key.
If I post data to a "addUser" endpoint it will be added correctly in the db.
{
"username": "test3",
"password": "password123",
"UserProfile":{
"FirstName": "John",
"LastName": "Doe"
}
}
Db Screenshot
However "UserProfile" in my model is always null.
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
IronCarp.Identity.Models.UserCredential.UserProfile.get returned null.
I'm using a repository to interact with the db and the method that is returning my data/model seems simple enough.
private async Task<UserCredential> GetUserCredentials(string username)
{
return await context.UserCredentials.Where(u => u.Username == username).FirstOrDefaultAsync();
}
Any help is appreciated, I am not sure what I am missing, thanks!
try to include navigation property in linkq, try something like this:
private async Task<UserCredential> GetUserCredentials(string username)
{
return await context.UserCredentials.Include(x => x.UserProfile).Where(u => u.Username == username).FirstOrDefaultAsync();
}
For me this issue was happening intermittently in my integration tests.
It turned out because I was mistakenly registering the EF Core DbContext as Transient and not Scoped:
Services.AddDbContext<SubscriptionsContext>(
options => options.UseNpgsql(appConfig.DatabaseConnectionString,
optionsBuilder => optionsBuilder.EnableRetryOnFailure(3)), ServiceLifetime.Transient);
Should be:
Services.AddDbContext<SubscriptionsContext>(
options => options.UseNpgsql(appConfig.DatabaseConnectionString,
optionsBuilder => optionsBuilder.EnableRetryOnFailure(3)), ServiceLifetime.Scoped);
If you look at the default parameter on the AddDbContext() extension method, it's always Scoped:

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.

Two Foreign Keys in Entity Framework Core

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

Resources