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.
Related
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);
}
}
https://i.imgur.com/rvWQVQt.png
So basically, I want to be able to define a User and have them be able to have a list of other Users that I designate as their friends - for some reason I'm stumped
Here are my classes and attempt so far:
public class User : BaseEntity, IUser
{
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Guid PhotoId { get; set; }
public string Mobile { get; set; }
public IList<ClubbrEvent> ClubbrEvents { get; set; }
public bool ProfileComplete { get; set; }
public List<UserFriends> Friends { get; set; }
public List<UserFriends> FriendsOf { get; set; }
}
public class UserFriends
{
public Long UserId { get; set; }
public User User { get; set; }
public Long FriendId { get; set; }
public User Friend { get; set; }
}
public class UserFriendsConfiguration: IEntityTypeConfiguration<UserFriends>
{
public void Configure(EntityTypeBuilder<UserFriends> builder)
{
builder.HasOne(f => f.Friend)
.WithMany(fo => fo.FriendsOf)
.HasForeignKey(fk => fk.FriendId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(u => u.User)
.WithMany(f => f.Friends)
.HasForeignKey(fk => fk.UserId);
}
}
But when I try to add a migration I get the following error:
The entity type 'UserFriends' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.
Ok, I figured it out, so leaving this here for anyone else in the same situation.
First, I had made a mistake in my join table properties - I had made them long but they should have been guid
Second, I defined the key in the config like so:
builder.HasKey(k => new { k.UserId, k.FriendId });
So in full:
public void Configure(EntityTypeBuilder<UserFriends> builder)
{
builder.HasKey(k => new { k.UserId, k.FriendId });
builder.HasOne(f => f.Friend)
.WithMany(fo => fo.FriendsOf)
.HasForeignKey(fk => fk.FriendId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(u => u.User)
.WithMany(f => f.Friends)
.HasForeignKey(fk => fk.UserId);
}
Running the migration and update now gives me what I need:
https://i.imgur.com/my674wx.png
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 ();
}
I have the following architecture:
Standard ASP.NET Identity tables, user can have one and only one role
There are 2 roles : Driver and Manager
User has driver or manager role. Every driver has record in Driver table, Every manager has record in Manager table.
Both, Manager and Driver tables, has FirstName and LastName
My entities and map among them (0..1:1):
public class AspNetUser
{
//.....
public virtual Manager Manager { get; set; }
public virtual Driver Driver { get; set; }
}
public partial class Driver
{
public int Id { get; set; }
//....
public virtual AspNetUser AspNetUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Manager
{
public int Id { get; set; }
//....
public virtual AspNetUser AspNetUser { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Nickname { get; set; }
}
public class AspNetUserMap : EntityTypeConfiguration<AspNetUser>
{
public AspNetUserMap()
{
this.HasKey(dp => dp.Id);
this.Property(p => p.UserName).IsRequired().HasMaxLength(256);
this.HasIndex(p => p.UserName).IsClustered(false).IsUnique();
this.HasOptional(c => c.Driver)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
this.HasOptional(c => c.Manager)
.WithOptionalPrincipal(a => a.AspNetUser)
.Map(m => m.MapKey("AspNetUserId"));
Now I need to get all users from AspNetUser, but with their FN/LN. To get FN/LN I should check role of user and then call Driver or Manager.
I have the following code:
List<ChatUserDomain> returnedUsers = new List<ChatUserDomain>();
var users = (from i in db.AspNetUsers
.Include(path => path.Driver)
.Include(path => path.Manager)
.Include(path => path.AspNetRoles)
where i.CompanyId == db.AspNetUsers.Where(p => p.UserName.Equals(username, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault().CompanyId
select new
{
AspNetUser = i,
Username = i.UserName,
....
}
).ToList();
foreach (var user in users)
{
returnedUsers.Add(new ChatUserDomain()
{
UserId = user.AspNetUser.Id,
Name = GetNameByUser(user.AspNetUser),
...
});
}
return returnedUsers;
and GetNameByUser method:
private string GetNameByUser(AspNetUser aspNetUser)
{
IProfile profile = GetProfileByUser(aspNetUser);
if (!String.IsNullOrWhiteSpace(profile.FirstName) && !String.IsNullOrEmpty(profile.LastName))
{
return (GetFirstLettersOfFullName(profile.FirstName, profile.LastName));
}
else
{
return (aspNetUser.UserName.Substring(0, 2));
}
}
private IProfile GetProfileByUser(AspNetUser aspNetUser)
{
IProfile profile = null;
var role = aspNetUser.AspNetRoles.FirstOrDefault().Name;
if (role.Equals(Domain.StaticStrings.RoleStaticStrings.ROLE_DRIVER))
profile = aspNetUser.Driver;
else
profile = aspNetUser.Manager;
return profile;
}
It works, but it creates additional user.Count calls to DB. How to say to EF "load Manager and Driver data in the parent request"?
Bellow code snippet showing my scenario:
[Table("User")]
public partial class UserModel
{
public UserModel()
{
UserRole = new HashSet<UserRoleModel>();
}
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<UserRoleModel> UserRole { get; set; }
}
[Table("UserRole")]
public partial class UserRoleModel
{
public UserRoleModel()
{
User = new HashSet<UserModel>();
}
public int RoleID { get; set; }
public string RoleName { get; set; }
public virtual ICollection<UserModel> User { get; set; }
}
Now within OnModelCreating(DbModelBuilder modelBuilder) EF Generate code like bellow
modelBuilder.Entity<UserModel>()
.HasMany(e => e.UserRole)
.WithMany(e => e.User)
.Map(m => m.ToTable("UserRoleMapping").MapLeftKey("UserID").MapRightKey("UserRoleID"));
now this is fine add / insert data into UserRoleMapping table. But how to
Get / Update data from UserRoleMapping table ?
I try to solve this issue following create-code-first-many-to-many the post and come-up with third class with join entity
public partial class UserRoleMappingModel
{
[Key, Column(Order = 0)]
public Guid UserId { get; set; }
public UserModel User { get; set; }
[Key, Column(Order = 1)]
public int RoleId { get; set; }
public UserRoleModel UserRole { get; set; }
}
then add public virtual ICollection<UserRoleMappingModel> UserRoleMapping { get; set; } in both the UserModel and UserRoleModel class
But when I try to GET value from database using bellow code
var results = _userRepository.GetAll()
.Include(r => r.UserRoleMapping
.Select(s => s.UserRole))
.SingleOrDefault(e => e.ID == id);
It throws ERROR
"An error occurred while executing the command definition. See the
inner exception for details.System.Data.SqlClient.SqlException
(0x80131904): Invalid object name 'dbo.UserRoleMappingModel'.\r\n
Even I tried bellow Configuration within OnModelCreating, but nothing work as expected
modelBuilder.Entity<UserRoleMappingModel>()
.HasKey(e => new { e.UserId, e.RoleId });
Your class UserRoleMappingModel has no Table-Attribute. Bcause of this, EF searches for a Table UserRoleMappingModel instead von UserRoleMapping.
You have to choose: Either map the n-to-n relationship and don't access the mapping-table or load the table to access the values in it.
As workaround you could implement a Not-Mapped column:
[Table("User")]
public partial class UserModel
{
public UserModel()
{
UserRole = new HashSet<UserRoleModel>();
}
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<UserRoleMappingModel> Mappings { get; set; }
public virtual ICollection<UserRoleModel> UserRole
{
get
{
return this.Mappings.Select(s => s.UserRole);
}
}
}
AS per GertArnold response I solve the issue in bellow way.
1st Remove below settings
modelBuilder.Entity<UserModel>()
.HasMany(e => e.UserRole)
.WithMany(e => e.User)
.Map(m => m.ToTable("UserRoleMapping").MapLeftKey("UserID").MapRightKey("UserRoleID"));
2nd Add bellow settings
modelBuilder.Entity<UserRoleMappingModel>()
.HasKey(e => new { e.UserId, e.RoleId });
3rd add table property in Mapping Model
[Table("UserRoleMapping")]
public partial class UserRoleMappingModel
{
[Key, Column(Order = 0)]
public Guid UserId { get; set; }
public UserModel User { get; set; }
[Key, Column(Order = 1)]
public int RoleId { get; set; }
public UserRoleModel UserRole { get; set; }
}
4th Create a Mapping Repository
IUserRoleMappingRepository
5th a simple get Method (Problem Solved)
var results = _userRoleMappingRepository.SearchFor(e => e.UserId == id)
.Select(s => new
{
s.UserId,
s.UserRoleId,
s.UserRole.RoleName
})
.FirstOrDefault();
Point to be noted : using bellow query I able to get result but unable to serialize with Newtonsoft.Json due to self referencing issue
var results = _userRepository.GetAll()
.Include(r => r.UserRoleMapping
.Select(s => s.UserRole))
.SingleOrDefault(e => e.ID == id);
Try bellow JsonSerializerSettingssetting alternatively but unable to serialize sucessfully
PreserveReferencesHandling = PreserveReferencesHandling.All / Object
ReferenceLoopHandling = ReferenceLoopHandling.Serialize / Ignore