CRUD operation on many-to-many relationship in ASP.NET MVC - asp.net-mvc

I have 2 tables in my ASP.NET MVC project: store and product.
The relationship between these tables is many-to-many, so I also have a table StoreProduct.
I want to do CRUD operations on table product, but I need column StoreID from table StoreProduct.
How can I get the storeid from StoreProduct to do CRUD operation on product table?

here is how you'd do it if you want a many-to-many relationship with enums i.e. you want many user roles and work with enums moreover, you can't store it as a flag because you need to know about the roles/privileges without source code(on db side).
TLDR ;) You'd have to create a join table which contains about about who has what privilege(or roles if you want).
There is a Users table which has a list of privileges, a privilege table which has privilege definition i.e. Id, name. And a Join table which will have User and Privilege as it's key. If an entry against this user/privilege combination is present that means this user has this privilege/role.
The code:
//for enum
public enum UserPrivilegeId : int
{
AddProject = 0,
ModifyProject = 1,
DeleteProject = 2,
AddUser = 3,
ModifyUser = 4,
DeleteUser = 5
}
//User class
public record User
{
public User()
{
Privileges = new HashSet<Privilege>();
}
public int Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public virtual ICollection<Privilege> Privileges { get; set; }
public virtual List<UserPrivilege> UserPrivileges { get; set; }
}
//Privilege Class
public record Privilege //note record is IMPORTANT here, because this forces it to compare by value, if you want to *use a class*, then make sure to override GetHashCode and Equals
{
public Privilege()
{
Users = new HashSet<User>();
}
public Privilege(UserPrivilegeId privilegeId, string privilegeName)
{
PrivilegeId = privilegeId;
PrivilegeName = privilegeName;
Users = new HashSet<User>();
}
[Key]
public UserPrivilegeId PrivilegeId { get; set; }
public string PrivilegeName { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual List<UserPrivilege> UserPrivileges { get; set; }
}
//and finally the UserPrivilege join class
public record UserPrivilege
{
public UserPrivilegeId PrivilageId { get; set; }
public Privilege Privilage { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
//The set-up in dbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Privilege>()
.HasKey(p => p.PrivilegeId);
modelBuilder.Entity<Privilege>()
.Property(p => p.PrivilegeId)
.HasConversion<int>();
modelBuilder.Entity<User>()
.HasMany(user => user.Privileges)
.WithMany(privilege => privilege.Users)
.UsingEntity<UserPrivilege>(
j => j
.HasOne(up => up.Privilage)
.WithMany(u => u.UserPrivileges)
.HasForeignKey(up => up.PrivilageId),
j => j
.HasOne(up => up.User)
.WithMany(p => p.UserPrivileges)
.HasForeignKey(up => up.UserId),
j =>
{
j.Property(u => u.PrivilageId).HasConversion<int>();
j.HasKey(u => new { u.PrivilageId, u.UserId });
});
//this adds definitions of privileges to the table
modelBuilder.Entity<Privilege>()
.HasData(
Enum.GetValues(typeof(UserPrivilegeId))
.Cast<UserPrivilegeId>()
.Select(p => new Privilege(p, p.ToString())));
base.OnModelCreating(modelBuilder);
}
Use it by creating a wrapper around it with a boolean on IsActive like this:
public class UserPrivelegesDTO
{
public UserPrivelegesDTO(UserPrivilegeId privilege, bool isActive)
{
this.PrivilegeId = privilege;
this.PrivilegeName = privilege.ToString();
this.IsActive = isActive;
}
public UserPrivilegeId PrivilegeId { get; set; }
public string PrivilegeName { get; set; }
public bool IsActive { get; set; }
}
If you want to convert from List<Privileges> to List<UserPrivilegeDTO>, you can
return await _context.Privileges.OrderBy(x => x.PrivilegeId).ToListAsync(cancellationToken);
To Convert back to List<Privileges>, simply
var privileges = _userPrivilegesViewModel.Privileges.Where(x => x.IsActive).Select(x => new Privilege(x.PrivilegeId, x.PrivilegeName));
If you want to check if the user has privilege
var user = _context.Users.Include(x => x.Privileges).FirstAsync(x => x.Id == 1);
if (request.Editor.Privileges.Any(p => p.PrivilegeId == UserPrivilegeId.ModifyUser))
return true;
When you want to update privileges
var PrivilegeChangeUser = await
_context.Users
.Include(user => user.Privileges)
.Include(user => user.UserPrivileges)
.FirstOrDefaultAsync(user => user.Id == request.UserId);
//**NOTE**you *need* to include the join table i.e. UserPrivileges in order replace the privileges, if you do not include it EF will try to add the privileges which already exist :(
//To update the privileges from an IEnumerable<UserPrivilegeIdEnum>
//first get the privileges objects and add that to users
var AllPrivileges =
await _context.Privileges
.Include(x => x.UserPrivileges)
.Include(x => x.Users)
.Where(x =>
request.Privileges
.Contains(x.PrivilegeId)
).ToListAsync(cancellationToken);
PrivilegeChangeUser.Privileges = AllPrivileges;
I have used an enum as the primary key, but you can of course just use a standard int too. For more information read this about how to configure a many-to-many relationship in efcore

Related

Entity Framework Selecting columns from multiple tables (for ThenInclude)?

I have question about select...
var applicationUser = unitOfWork.ApplicationUsers.GetAll().Include(i => i.ApplicationUserRoles).ThenInclude(i => i.ApplicationRole)
.Where(i => i.UserName.ToUpper() == userName.ToUpper())
.Select(i => new
{
i.Email,
i.FirstName,
i.LastName,
i.PhoneNumber,
i.ImageUrl,
i.JoinedDate,
i.DateOfBirth,
i.ApplicationUserRoles
})
.FirstOrDefault();
I cant get ApplicationRole how to use it in select ?
If you are using projection with Select you don't need to use Include. Just select the values. To get the Roles via the UserRoles you will need to Select to retrieve those:
var applicationUser = unitOfWork.ApplicationUsers.GetAll()
.Where(i => i.UserName.ToUpper() == userName.ToUpper())
.Select(i => new
{
i.Email,
i.FirstName,
i.LastName,
i.PhoneNumber,
i.ImageUrl,
i.JoinedDate,
i.DateOfBirth,
ApplicationRoles = i.ApplicationUserRoles.Select(x => x.ApplicationRole).ToList()
})
.FirstOrDefault();
You can further refine this by sub-selecting just the role details you need from the application role...
// ...
ApplicationRoles = i.ApplicationUserRoles.Select(x =>
x.ApplicationRole.Select(ar => new { ar.RoleId, ar.RoleName })).ToList()
I recommend using SingleOrDefault rather than FirstOrDefault if you are expecting at most 1 result. First/FirstOrDefault should always be used with an OrderBy/OrderByDescending condition to ensure predictable results.
If I can use for ApplicationRoles, it is working
.Select(i => new
{i.Email,i.FirstName,i.LastName,i.PhoneNumber,i.ImageUrl,i.JoinedDate,i.DateOfBirth,
ApplicationRoles = i.ApplicationUserRoles.Select(x => x.ApplicationRole).ToList(),
})
I cant use for ApplicationUserRoles how is it work for it ?
public class ApplicationUserSummary
{
public DateTime JoinedDate { get; set; }
public string ImageUrl { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public ICollection<ApplicationRole> ApplicationRoles { get; set; }
public ICollection<ApplicationUserRole> ApplicationUserRoles { get; set; }
}

How to map redundant relationship in EF6

I have a Quote object, with a collection of QuoteAnswer objects. I also want a shortcut to the latest QuoteAnswer. So I modeled (irrelevant code ommitted for brevity):
public class Quote
{
public int Id { get; set; }
public ICollection<QuoteAnswer> Answers { get; set; }
public QuoteAnswer LatestAnswer { get; set; }
public int LatestAnswerId { get; set; }
}
public class QuoteAnswer
{
public int Id { get; set; }
public Quote Quote { get; set; }
public int QuoteId { get; set; }
/* Did not map this, not interested/not needed
* public Quote LastAnswerFor { get; set; }
* public int LastAnswerForId { get; set; }
*/
}
That's beacuse I want to be able to do this:
var quotes = context.Quotes
.Include(x => x.LatestAnswer)
.ToList();
Instead of this:
var quotes = context.Quotes
.Include(x => x.Answers)
.ToList();
foreach (var q in quotes)
{
var latestAnswer = q.Answers.OrderByDescending(x => x.Date).FirstOrDefault();
}
Which would obviously force me to load unecessary data.
The Problem
When I try to map this do database code (add a migration), I get a new property I don't know where it's comming from.
Generated migration code (parts ommitted for brevity):
CreateTable(
"dbo.QuoteAnswer",
c => new
{
Id = c.Int(nullable: false),
QuoteId = c.Int(nullable: false),
QuoteId1 = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.Quote", t => t.QuoteId)
.ForeignKey("dbo.Quote", t => t.QuoteId1)
.Index(t => t.QuoteId)
.Index(t => t.QuoteId1);
AddColumn("dbo.Quote", "LatestAnswerId", c => c.Int());
CreateIndex("dbo.Quote", "LatestAnswerId");
AddForeignKey("dbo.Quote", "LatestAnswerId", "dbo.QuoteAnswer", "Id");
What's that QuoteId1 thing? I get the QuoteId, but I don't recognize QuoteId1.
How can I achive this mapping? Is this even supported in EF6?
First, it's possible. The explicit FK property should be removed:
public class Quote
{
public int Id { get; set; }
public string Data { get; set; }
public ICollection<QuoteAnswer> Answers { get; set; }
public QuoteAnswer LatestAnswer { get; set; }
}
and the new relationship should be mapped with fluent API:
modelBuilder.Entity<Quote>()
.HasOptional(e => e.LatestAnswer)
.WithOptionalDependent()
.Map(m => m.MapKey("LatestAnswerId"));
But I won't recommend you doing it because it would introduce a lot of maintenance problems - aside of the obvious need to keep it up-to-date, it would create circular FK dependency, so basically all CUD operations would be problematic (if working at all).
I think you are trying to solve the "loading unnecessary data" problem is a wrong way. You can achieve the same goal by using simple projection:
var quotesWithLatestAnswer = context.Quotes
.Select(q => new { Quote = q, LatestAnswer = q.Answers.OrderByDescending(a => a.Date).FirstOrDefault() })
.ToList();
Note that the code inside Select will be translated to SQL and executed in the database, returning only the data needed.
To return the latest answer as part of your entity, you can make mark it as unmapped:
public class Quote
{
public int Id { get; set; }
public string Data { get; set; }
public ICollection<QuoteAnswer> Answers { get; set; }
[NotMapped]
public QuoteAnswer LatestAnswer { get; set; }
}
and use a combination of LiNQ to Entities (SQL) and LINQ to Objects query:
var quotes = context.Quotes
.Select(q => new { Quote = q, LatestAnswer = q.Answers.OrderByDescending(a => a.Date).FirstOrDefault() })
.AsEnumerable() // end of db query
.Select(qi => { qi.Quote.LatestAnswer = qi.LatestAnswer; return qi.Quote; })
.ToList();
This way you'll have clean and easy to maintain relational database model as well as efficient retrieval of the data needed.

.NET Core identity user id comes differently from database

I am trying to get current user's id with identity in .NET Core. Managed to create new user and login with it but whenever logging in with same user identity returns different guid.
I have a product entity which stores userId as foreign key. Whenever I try to add new product it throws error because given userId is not in user table.
But I register a new user and go add new product immediately it works. Lists products. However, logout and login again with same user, products are not listed. When I debug it saw that the _userManager.GetUserId(User) returning a different value.
Why could it be happened?
How can I fix this?
UPDATE
My User Creation Code
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(HomeController.Index), "Home");
}
My User Login Code
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return RedirectToLocal(returnUrl);
}
Here is My ApplicationUser Model Class
public class ApplicationUser : IdentityUser
{
[Key]
[Column("TABLEID")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override string Id { get; set; }
[Required]
[Column("AD")]
public string Name { get; set; }
[Required]
[Column("SOYAD")]
public string Surname { get; set; }
[Required]
[Column("AKTIF")]
public bool IsActive { get; set; }
[Required]
[Column("SIL")]
public bool IsDeleted { get; set; }
[Required]
[Column("KAYITTARIHI")]
public DateTime RecordDate { get; set; }
public List<Vehicle> Vehicles { get; set; }
public List<VehicleImage> VehicleImages { get; set; }
public List<TransportAdvertise> TansportAdvertises { get; set; }
public List<TransportRequest> TransportRequests { get; set; }
}
Here is My DbContext
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<VehicleImage> VehicleImages { get; set; }
public DbSet<TransportAdvertise> TransportAdvertises { get; set; }
public DbSet<TransportRequest> TransportRequests { get; set; }
public DbSet<TransportRoute> TransportRoutes { get; set; }
public DbSet<City> Cities { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
// Identity built-in models.
builder.Entity<IdentityUser>().ToTable("TBLKULLANICI");
builder.Entity<IdentityRole>().ToTable("TBLROL");
builder.Entity<IdentityUserRole<string>>().ToTable("TBLKULLANICIROL");
builder.Entity<IdentityRoleClaim<string>>().ToTable("TBLROLECLAIM");
builder.Entity<IdentityUserClaim<string>>().ToTable("TBLUSERCLAIM");
builder.Entity<IdentityUserLogin<string>>().ToTable("TBLUSERLOGIN");
builder.Entity<IdentityUserToken<string>>().ToTable("TBLUSERTOKEN");
// Custom models.
builder.Entity<Vehicle>().ToTable("TBLARAC");
builder.Entity<VehicleImage>().ToTable("TBLARACRESIM");
builder.Entity<TransportAdvertise>().ToTable("TBLNAKLIYEILAN");
builder.Entity<TransportRequest>().ToTable("TBLNAKLIYEISTEK");
builder.Entity<TransportRoute>().ToTable("TBLROTA");
builder.Entity<City>().ToTable("TBLSEHIR");
// FK mappings..
builder.Entity<Vehicle>()
.HasOne(v => v.User)
.WithMany(u => u.Vehicles)
.HasForeignKey(v => v.UserId)
.HasConstraintName("FK_VEHICLE_USER");
// Vehicle image model fks.
builder.Entity<VehicleImage>()
.HasOne(vi => vi.Vehicle)
.WithMany(v => v.Images)
.HasForeignKey(vi => vi.VehicleId)
.HasConstraintName("FK_VEHICLE_IMAGE_VEHICLE");
builder.Entity<VehicleImage>()
.HasOne(vi => vi.User)
.WithMany(u => u.VehicleImages)
.HasForeignKey(vi => vi.UserId)
.HasConstraintName("FK_VEHICLE_IMAGE_USER");
// TransportAdvertise model fks.
builder.Entity<TransportAdvertise>()
.HasOne(ta => ta.User)
.WithMany(u => u.TansportAdvertises)
.HasForeignKey(ta => ta.UserId)
.HasConstraintName("FK_TRANSPORT_ADS_USER");
builder.Entity<TransportAdvertise>()
.HasOne(ta => ta.Vehicle)
.WithMany(v => v.TransportAdvertises)
.HasForeignKey(ta => ta.VehicleId)
.HasConstraintName("FK_TRANSPORT_ADS_VEHICLE");
// TransportRequest model fks.
builder.Entity<TransportRequest>()
.HasOne(tr => tr.User)
.WithMany(u => u.TransportRequests)
.HasForeignKey(tr => tr.UserId)
.HasConstraintName("FK_TRANSPORT_RQST_USER");
builder.Entity<TransportRequest>()
.HasOne(tr => tr.TransportAdvertise)
.WithMany(ta => ta.TransportRequests)
.HasForeignKey(tr => tr.TransportAdvertiseId)
.HasConstraintName("FK_TRANSPORT_RQST_ADS");
// TransportRoute model fks.
builder.Entity<TransportRoute>()
.HasOne(tr => tr.City)
.WithMany(c => c.TransportRoutes)
.HasForeignKey(tr => tr.CityId)
.HasConstraintName("FK_TRANSPORT_ROUTE_CITY");
builder.Entity<TransportRoute>()
.HasOne(tr => tr.TransportAdvertise)
.WithMany(ta => ta.TransportRoutes)
.HasForeignKey(tr => tr.TransportAdvertiseId)
.HasConstraintName("FK_TRANSPORT_ROUTE_ADS");
}
Vehicle Creation Code
[HttpPost]
public IActionResult AddNewVehicle(VehicleViewModel model)
{
var id = _userManager.GetUserId(User);
// model property check needs to be done before.
// vehicle table does not accept nulls.
var vehicle = new Vehicle()
{
Model = model.Model,
Capacity = model.Capacity,
Description = model.Description,
Year = model.Year,
Active = true,
Delete = false,
RecordDate = DateTime.Now,
UserId = id
};
_vehicleService.AddNew(vehicle);
return RedirectToAction("Index");
}
Do a simple experiment... Create a new user and note its Id... Immediately add a product and check again if the user ID has changed after the save operation. It is possible the user details is being mutated/changed upon save operation. You can also paste the code samples for creating a user, creating a product and retrieving a user here. The problem is most likely where u are saving the vehicles. At that point the user details is either being mutated, or the vehicles is being stored with a different user. The vehicles has a 1 to 1 relationship with the user. So when creating a vehicle be careful not to be doing vehicle.user = new User; Ensure you are doing the following.
1- Get the user from the Db :
var user = await UserManager.FindByIdAsync("Id of currently logged in
user");
2- set vehicle.user to the user retrieved above:
vehicle.user = user.
You can share code where u are saving vehicle for people to be able to help. Also you can prevent the user Id from being automatically generated in the db and add a [Required] attribute on the Id field. You can the do
user.Id = Guid.Guid.NewGuid()
. This can help prevent issues where a new user is automatically added to the db when u create a vehicle

how to properly configure using fluent api as data annotation not working properly

This is what i was trying to achieve but entity framework didn't let me do this:
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.Following)
.WithMany(u => u.Followers)
.Map(m =>
{
m.ToTable("FollowTables");
m.MapLeftKey("UserId");
m.MapRightKey("FollowId");
});
But if i use this code then in database, table is created but with no primary key in it.only two column got created.
If i use this code then proper table is created with primary key too in it.
public class FollowTable
{
[Key]
public int autoId { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public ApplicationUser User { get; set; }
[ForeignKey("Follow")]
public int? FollowId { get; set; }
public ApplicationUser Follow { get; set; }
}
here, autoId is primary key and UserId and FollowId both are foreign key to ApplicationUser class where UserId is user's own id and FollowId are the ids which user is following.
autoId UserId FollowId
1 4 11
2 4 12
3 4 13
but my ApplicationUser class structure is something like this :
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public string Address { get; set; }
public ApplicationUser()
{
this.Followers = new HashSet<ApplicationUser>();
this.Following = new HashSet<ApplicationUser>();
}
// calculated properties
public ICollection<ApplicationUser> Following { get; set; }
public ICollection<ApplicationUser> Followers { get; set; }
i want to use these collections badly.
But this code i think is creating a new table in database named dbo.ApplicationUserApplicationUsers with columns ApplicationUser_Id and ApplicationUser_Id1.
i dont want this table.also my code is too entering data into this table like this follow function:
public virtual bool Follows(int userId, int accountId)
{
var targetUser = _context.Users
.Where(u => u.Id == accountId && u.Id != userId)
.Select(u => new
{
User = u,
CanFollow = u.Followers.All(f => f.Id != userId)
})
.FirstOrDefault();
if (targetUser == null || !targetUser.CanFollow) return false;
var currentUser = _context.Users
.Include(u => u.Following)
.FirstOrDefault(u => u.Id == userId);
if (currentUser == null) return false;
if (currentUser.Following.All(u => u.Id != accountId))
{
currentUser.Following.Add(targetUser.User);
_context.SaveChanges();
return true;
}
return false;
}
Now, suggest me how to configure mapping with or without fluent api.i want to enter data in follow table not in table which was auto generated.
You're conflating two separate relationships. If you were dealing with something like "Friends", then that's a straight M2M: by virtue of me being your friend, you are also my friend. However, a user could follow many people and be followed by none, that means you need two separate relationships to to track that.
public class ApplicationUser
{
...
public virtual ICollection<ApplicationUser> Following { get; set; }
public virtual ICollection<ApplicationUser> Followers { get; set; }
public class Mapping : EntityTypeConfiguration<ApplicationUser>
{
HasMany(m => m.Following).WithMany();
HasMany(m => m.Followers).WithMany();
}
}
Then in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ApplicationUser.Mapping());
}
Entity Framework will handle the join tables. You don't need to worry about that, unless you need an M2M with a payload, like the date someone started following or the user started following someone else.

Fluent Nhibernate ApplyFilter on Join column

I'm trying to apply a filter (ApplyFilter) on a column that is Join (and projected) from another table. I have the following entity:
public class User
{
public virtual int Id { get; private set; }
public virtual string EMail { get; set; }
...
public virtual bool IsActive { get; set; }
public virtual int CompanyId { get; set; }
}
With a UserMap:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
Id(x => x.Id, "UserId");
Map(x => x.EMail);
...
Join("CompanyUser", r =>
{
r.KeyColumn("UserId");
r.Map(x => x.IsActive);
r.Map(x => x.CompanyId);
r.Fetch.Join();
});
ApplyFilter<CompanyFilter>("this_1_.CompanyId = :companyId");
}
This actually works right now, but as you can see I'm having to include the alias name "this_1_" for the CompanyUser table in the filter... this doesn't sound correct, but if I leave the filter as is defined in the FilterDefinition class I get a Sql with a:
where this.CompanyId = ?p0
which is not mapped 'cos the CompanyId column comes from a different projection (CompanyUser as this_1_)
Is there a way to correct this and let nhibernate figure out the correct alias for the filter?
Thanks in advance for any help.
I think you must apply the filter on the join:
Join("CompanyUser", r =>
{
r.KeyColumn("UserId");
r.Map(x => x.IsActive);
r.Map(x => x.CompanyId);
r.Fetch.Join();
}).ApplyFilter<CompanyFilter>("CompanyId = :companyId");

Resources