How to save changes properly in code-first without navigation property? - c#

I have problem to design to split config without navigation property. it makes each work/transaction call SaveChanges twice. I dont know it effect performance or not. I see alot tutorial only call SaveChanges one time only. Is there a new ways entityframework to design code-first without navigation property?
Program
using (ApplicationDbContext context = new())
{
using (var contextTransaction = context.Database.BeginTransaction())
{
var account = new Account
{
AccountId = "stack237762"
};
context.Accounts.Add(account);
context.SaveChanges();
context.Users.Add(new User
{
Username = "Admin",
Password = "PassAdmin",
AccountId = account.Id
});
context.SaveChanges();
contextTransaction.Commit();
}
}
Entities
public class User
{
public int Id { get; set; }
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public int AccountId { get; set; }
}
public class Account
{
public int Id { get; set; }
public string AccountId { get; set; } = string.Empty;
}
Context
public class UserEntityConfig : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey("Id");
builder.Property(t => t.Id)
.ValueGeneratedOnAdd();
builder.Property(t => t.Username)
.IsRequired();
builder.Property(t => t.Password)
.IsRequired();
builder.HasOne<Account>()
.WithOne()
.HasForeignKey<User>(u => u.AccountId);
}
}

Related

Entity GUID is overwritten when manually populated

I am populating my context for my tests. but for some reason my Guids gets overwritten upon creation of the entity.
I think there is something wrong on my context setup, but I am below average when it comes to entity framework setups.
Edit:
Seed Code:
try
{
using (var context = this.GetContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var lets = new Entities.Models.Users()
{
Id = Guid.Parse("3859e4c1-aaf7-4d9b-bc5e-8730ae9ad531"),
Name = "Test Pilot",
Organization = new Entities.Models.Organizations
{
Id = 12312
},
AppRoles = new Entities.Models.UserRequestRoles
{
AppRole = ""
}
};
context.Users.Add(lets);
context.SaveChanges();
}
}
catch (Exception e)
{
throw e;
}
User Entity:
public class Users
{
[Key]
[Column("Id")]
public Guid Id { get; set; }
// User Details
public string GivenName { get; set; }
public string Surname { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Status { get; set; }
public virtual Organizations Organization { get; set; }
[ForeignKey("Id")]
public virtual UserRequestRoles AppRoles { get; set; }
[ForeignKey("UserId")]
public virtual IEnumerable<UserOrganizations> UserOrganizations { get; set; }
}
Context builder:
builder.Entity<Users>(a =>
{
a.HasKey(a => a.Id);
a.HasOne(b => b.Organization)
.WithMany(b => b.Users);
a.HasMany<UserOrganizations>(c => c.UserOrganizations);
a.HasOne(b => b.AppRoles);
});
There are no errors. its just populating my context.
Any idea why is this happening, thank you
Any idea why is this happening
It's happening because you instructed EF to automatically populate the value of that column via the .HasKey() extension method. Remove the call to that method - you've already specified that the column is a key column with the [Key] attribute defined on it, there's no need to tell EF twice.
In your test, you then need to configure your entity so that it ignores the [Key] attribute:
builder.Entity<Users>(a =>
{
a.HasOne(b => b.Organization)
.WithMany(b => b.Users);
a.HasMany<UserOrganizations>(c => c.UserOrganizations);
a.HasOne(b => b.AppRoles);
}).Property(user => user.Id)
.ValueGeneratedOnAddOrUpdate()
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);

How to create an Add Friend functionality between two individual user accounts in the same table in asp.net core identity

I am developing a mini social media web app and I use ASP.NET Identity to create and manage user accounts.
I want to add another user account as a friend to my account. I could succesfully do that but the problem is when I checked my added friend's account, there is no update in his friends list. It's empty.
Here is my User class inherited from IdentityUser,
public class AppUser : IdentityUser
{
public AppUser()
{
this.Friends = new HashSet<AppUser>();
}
public int Age { get; set; }
public string Sex { get; set; }
public string City { get; set; }
public string Education { get; set; }
public string Description { get; set; }
public string? FriendOfUserId { get; set; }
public virtual AppUser FriendOf { get; set; }
public ICollection<AppUser> Friends { get; set; }
}
My DbContext class,
public class ApplicationDbContext : IdentityDbContext<AppUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<AppUser>(x =>
{
x
.HasMany(x => x.Friends)
.WithOne(x => x.FriendOf)
.HasForeignKey(x => x.FriendOfUserId);
});
}
public DbSet<AppUser> Users { get; set; }
}
My Controller Class method to add friend,
public async Task<IActionResult> AddFriend(string id)
{
var addFriend = await context.Users.Include(u => u.Friends).FirstOrDefaultAsync(u => u.Id == id);
var user = await userManager.GetUserAsync(this.User);
var u = await context.Users.Include(u => u.Friends).FirstOrDefaultAsync(u => u.Id == user.Id);
user.FriendOf = addFriend;
user.Friends.Add(addFriend);
await context.SaveChangesAsync();
return Redirect("/");
}
I think you're not modeling your entity correctly. Since an user can have a list of friends, and also be a friend of other users, I guess you need to capture the latter part in the model.
Since this is a many-to-many relationship, and EF Core still hasn't supported it without declaring an entity to represent the join table, you need to defind that entity as well:
public class AppUser : IdentityUser
{
public int Age { get; set; }
public string Sex { get; set; }
public string City { get; set; }
public string Education { get; set; }
public string Description { get; set; }
public ICollection<AppUserFriendship> FriendsOf { get; set; }
public ICollection<AppUserFriendship> Friends { get; set; }
}
public class AppUserFriendship
{
public string UserId { get; set; }
public AppUser User { get; set; }
public string UserFriendId { get; set; }
public AppUser UserFriend { get; set; }
}
And then you need to configure their relationships:
public class ApplicationDbContext : IdentityDbContext<AppUser>
{
...
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<AppUserFriendship>(b =>
{
b.HasKey(x => new { x.UserId, x.UserFriendId };
b.HasOne(x => x.User)
.WithMany(x => x.Friends)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.UserFriend)
.WithMany(x => x.FriendsOf)
.HasForeignKey(x => x.UserFriendId)
.OnDelete(DeleteBehavior.Restrict);
});
}
public DbSet<AppUser> Users { get; set; }
}
Note OnDelete(DeleteBehavior.Restrict). You have to set it to something other than DeleteBehavior.Cascade which is the default to prevent the cascade deletion.
Disclaimer: I wrote all by hand. Never test it.
You will need to use the Include function.
// What you have is fine.
var friend = context.Users.Select ( u => u == id );
// this is what needs to occur in await userManager.GetUserAsync(this.User);
// Without the include the Navigation Property will not be tracked.
var user = context.Users
.Select ( u => u == id )
.Include ( u => u.Friends );
user.Friends.Add ( friend );
context.SaveChanges ();
Check out loading related data.
https://learn.microsoft.com/en-us/ef/core/querying/related-data
EDIT:
Take a look at this post it is a duplicate of this one.
Many-to-many self referencing relationship
EDIT 2:
So the issue you are having is that you are still trying to model this as one table. Think about how this would be structured in a SQL database. How could a single table contain a collection (Friend) in a single column? To accomplish this we will create a new table to model the relationship between AppUsers.
public class Friendship
{
// Foreign Key
Int32 MeId { get; set; }
// Foreign Key
Int32 FriendId { get; set; }
// Navigation Property
AppUser Me { get; set; }
// Navigation Property
AppUser Friend { get; set; }
}
public class AppUser : IdentityUser
{
public AppUser()
{
this.Friends = new HashSet<AppUser>();
}
// Primary Key. Is this defined in IdentityUser?
public int Id { get; }
public int Age { get; set; }
public string Sex { get; set; }
public string City { get; set; }
public string Education { get; set; }
public string Description { get; set; }
// This is considered a Navigation Property
public ICollection<Friendship> Friends { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<AppUser>(x =>
{
x.HasPrimaryKey ( x => x.Id );
});
builder.Entity<Friendship>( f =>
{
f.HasKey ( f => new { f.MeId, f.FriendId } );
f
.HasOne( f => f.Me )
.WithMany ( u => u.Friends )
.HasForeignKey( f => f.MeId );
f
.HasOne( f => f.Friend )
.WithMany ( u => u.Friends )
.HasForeignKey( f => f.FriendId );
});
}
At this point you should be able to query the join table for friendships.
public void AddFriend ( AppUser user, AppUser friend ) {
var trackedUser = context.AppUsers
.Select ( u => u.Id == user.Id )
.Include ( u => u.Friends );
.FirstOrDefault ();
trackedUser.Friends.Add ( new Friendship () {
MeId = user.Id,
FriendId = friend.Id
});
context.SaveChanges ();
}

EF Core delete data in many to many relationship

I have the following tables:
public class Team
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
public class UserTeam
{
public long UserId { get; set; }
public User User { get; set; }
public long TeamId { get; set; }
public Team Team { get; set; }
}
the many to many relationship is defined in context:
modelBuilder.Entity<UserTeam>()
.HasKey(bc => new { bc.UserId, bc.TeamId });
modelBuilder.Entity<UserTeam>()
.HasOne(bc => bc.User)
.WithMany(b => b.UserTeams)
.HasForeignKey(bc => bc.UserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<UserTeam>()
.HasOne(bc => bc.Team)
.WithMany(c => c.UserTeams)
.HasForeignKey(bc => bc.TeamId)
.OnDelete(DeleteBehavior.Restrict);
I am trying to delete some users from a team with the following code:
public async Task RemoveUsersFromTeam(int teamId, List<long> users)
{
Team existingTeam = await dbContext.Team.Include(x => x.UserTeams).FirstOrDefaultAsync(x => x.Id == teamId);
foreach (var user in users)
{
existingTeam.UserTeams.Remove(new UserTeam { UserId = user });
}
await dbContext.SaveAsync();
}
but this query is not deleting the users that I pass. Anyone knows why this happens?
You can delete objects by Id using the new and attach method, or by passing the actual entity.
Passing Entity
public async Task RemoveUsersFromTeam(int teamId, List<long> userIds)
{
Team existingTeam = await dbContext.Team.Include(x => x.UserTeams).FirstOrDefaultAsync(x => x.Id == teamId);
foreach (var userId in userIds)
{
var userTeam = existingTeam.UserTeams.FirstOrDefault(x => x.UserId == userId);
if(userTeam != null)
{
existingTeam.UserTeams.Remove(userTeam);
}
}
await dbContext.SaveAsync();
}
Delete By Id
public async Task RemoveUsersFromTeam(int teamId, List<long> userIds)
{
Team existingTeam = await dbContext.Team.FirstOrDefaultAsync(x => x.Id == teamId);
foreach (var userId in userIds)
{
var userTeam = new UserTeam { UserId = userId });
dbContext.UserTeams.Attach(userTeam);
existingTeam.UserTeams.Remove(userTeam);
}
await dbContext.SaveAsync();
}
Option two does not require selecting UserTeams from the database and will be slightly more efficient in that respect. However option one may be more understandable. You should choose which best fits your situation.
Personally I prefer option two, as include will select the whole entity and in some cases that could be a lot more than necessary.

Keep multiple objects with the same key in DbContext

I am trying to create ASP.NET MVC web application using Entity Framework Code First.
I have two simple models
public class Assignment
{
public int Id { get; set; }
[ForeignKey("Initiator")]
public int InititatorId { get; set; }
public virtual User Initiator { get; set; }
public virtual List<User> Debtors { get; set; }
public virtual List<User> Responsibles { get; set; }
public Assignment()
{
Debtors = new List<User>();
Responsibles = new List<User>();
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
and my DbContext looks like
public class AssignmentContext : DbContext
{
public DbSet<Assignment> Assignments { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Assignment>()
.HasRequired(r => r.Initiator)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Assignment>()
.HasMany(m => m.Responsibles)
.WithMany()
.Map(m =>
{
m.MapLeftKey("AssignmentId");
m.MapRightKey("UserId(Responsible)");
m.ToTable("AssignmentResponsibles");
});
modelBuilder.Entity<Assignment>()
.HasMany(m => m.Debtors)
.WithMany()
.Map(m =>
{
m.MapLeftKey("AssignmentId");
m.MapRightKey("UserId(Debtor)");
m.ToTable("AssignmentDebtors");
});
}
Is there a way to keep the same User instance in the Lists Debtors and Responsibles?
Because I'm getting the error when I'm trying to save changes.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I need to do this because it is required by the application logic.
Thanks in advance.
Update:
Here on HttpPost the error appears (The same error in Console app sample):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Text,CreationDate,InititatorId,Debtors,Responsibles")] Assignment assignment)
{
if (ModelState.IsValid)
{
assignment.Debtors.RemoveAll(r => r.Name == "Roman");
db.Entry(assignment).State = EntityState.Modified; // HERE!
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.InititatorId = new SelectList(db.Users, "Id", "Name", assignment.InititatorId);
return View(assignment);
}
Debtors and Responsbiles elements from the view are passed by hidden input.
Why don't you just get the data first instead of directly attach it to the dbcontext, the same assignment with the same id might have been attached locally within the same context.
instead of these
assignment.Debtors.RemoveAll(r => r.Name == "Roman");
db.Entry(assignment).State = EntityState.Modified; // HERE!
db.SaveChanges();
try these
var assignmentDb = db.Assignments.FirstOrDefault(asg => asg.Id == assignment.Id);
assignmentDb.Debtors.RemoveAll(r => r.Name == "Roman");
db.SaveChanges();

DbUpdateException when seeding data

I'm trying to populate my code first database with an admin user account for myself so that I can access the system.
To do this, I'm calling a Seed method in my Global.asax file which does the following:
public void Seed()
{
if (!Roles.Any())
{
List<Role> roles = new List<Role>
{
new Role { RoleName = "Admin" },
new Role { RoleName = "User" }
};
foreach (Role r in roles)
{
Roles.Add(r);
}
SaveChanges();
}
if (!Users.Any())
{
User u = new User();
u.EmailAddress = "my#email.address";
u.Username = "ortund";
u.Password = Hashing.CreateHash("p455W0rd");
u.Role = Roles.Single(r => r.RoleName == "Admin");
Users.Add(u);
SaveChanges();
}
}
User and Role are defined as follows:
namespace Logan.Web.Objects
{
public class User : LoganBaseObject<User>
{
public string Username { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string Biography { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<Article> Articles { get; set; }
public User()
{
Username = String.Empty;
EmailAddress = String.Empty;
Password = String.Empty;
Biography = String.Empty;
Articles = new List<Article>();
}
}
}
public class Role : LoganBaseObject<Role>
{
public string RoleName { get; set; }
public virtual ICollection<User> Users { get; set; }
public Role()
{
RoleName = String.Empty;
Users = new List<User>();
}
}
}
namespace Logan.Web.DBContext
{
public class RoleDBContext : LoganDBBaseObject<Role>
{
private static WebDBContext db = new WebDBContext();
public RoleDBContext()
: base()
{
Property(p => p.RoleName)
.HasColumnName("sRoleName")
.IsRequired();
HasMany(m => m.Users)
.WithRequired();
ToTable("Roles");
}
}
public class UserDBContext : LoganDBBaseObject<User>
{
private static WebDBContext db = new WebDBContext();
public UserDBContext()
: base()
{
Property(p => p.Username)
.HasColumnName("sUsername")
.HasMaxLength(20)
.IsRequired();
Property(p => p.EmailAddress)
.HasColumnName("sEmailAddress")
.HasMaxLength(200)
.IsRequired();
Property(p => p.Password)
.HasColumnName("sPassword")
.HasMaxLength(255)
.IsRequired();
Property(p => p.Biography)
.HasColumnName("sBiography")
.HasColumnType("text");
HasRequired(r => r.Role)
.WithMany(m => m.Users)
.Map(x => x.MapKey("fkRoleID"))
.WillCascadeOnDelete(false);
Property(p => p.CreateDate)
.HasColumnType("datetime");
ToTable("Users");
}
}
}
When the Seed method above gets to SaveChanges(), I get the following error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
Obviously this has something to do with the relationships that are being constructed here, but I don't actually know what I'm doing wrong.
Can anyone recommend a solution and/or explain what the problem is here?
Thanks in advance!
EDIT
Here's a screenshot of the structure that is genergated. The PKey field is added from LoganBaseObject<T>:
I really need to learn to include absolutely all the information from the tables.
I had some datetime columns in the User table for which I wasn't supplying values in the Seed method. See here:
public class User : LoganBaseObject<User>
{
public string Username { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string Biography { get; set; }
public virtual Role Role { get; set; }
public bool PasswordReset { get; set; }
public string PasswordResetKey { get; set; }
public DateTime ResetExpiry { get; set; }
public string CreatedBy { get; set; }
public DateTime CreateDate { get; set; }
}
I still don't get why missing values would produce this error about foreign keys, but supplying values in the Seed method fixed the problem.

Categories