Many to many relationship with ASP.NET AspNetUser table gets ignored - asp.net

One user can be a member of many projects while a project can have multiple members.
ASP.NET Identity ApplicationUser
public class ApplicationUser : IdentityUser
{
[Display(Name = "Projekt")]
public virtual ICollection<Project> Projects { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
My Project:
[Table("Projects")]
public class Project : IValidatableObject
{
[Key]
public int ID { get; set; }
[Required, StringLength(128), Display(Name = "Projektname"), Index(IsUnique = true)]
public string Name { get; set; }
// working one-to-many relation
[Display(Name = "Projektleiter")]
public string LeaderID { get; set; }
[ForeignKey("LeaderID"), Display(Name = "Projektleiter")]
public virtual ApplicationUser ApplicationUser { get; set; }
// many-to-many relation gets ignored
[Display(Name = "Mitarbeiter")]
public virtual ICollection<ApplicationUser> ApplicationUsers { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return new List<ValidationResult>();
}
}
No Many-to-Many table is created. The whole relation gets ignored. But the LeaderID thing is working ...
Can somebody tell me what I am missing here? (I googled the hell out of it, I deleted the whole database multiple times, I tried everything I found, no luck ...)

I would keep the ApplicationUser entity separated from whatever logic you have with the project and create another entity called Person.
public class Person
{
//Constuctor : always intantiate lists in ctor
public Person()
{
Projects = new List<Project>();
}
public int Id { get; set; }
public string IdentityId { get; set; } //gets guid from AppUser table
public ApplicationUser Identity { get; set; } // navigation property
public List<Project> Projects { get; set; }
//public int ProjectId {get; set;}//-----optional
}
so in projects know we do the same thing:
[Table("Projects")]
public class Project : IValidatableObject
{
//Constuctor : always intantiate lists in ctor
public Project()
{
Persons = new List<Person>();
}
[Key]
public int ID { get; set; }
[Required, StringLength(128), Display(Name = "Projektname"), Index(IsUnique = true)]
public string Name { get; set; }
public string PersonId { get; set; } //nav property
public List<Persons> Persons { get; set; }
public Person Person { get; set; } //context will be aware that this is fk
[Display(Name = "Projektleiter")]
public string LeaderID { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return new List<ValidationResult>();
}
}
add db sets for both entities in the dbcontext class.
Now you want to get a person with a list of projects and vice versa. For that we use the include:
var x = myContext.Persons.Include(x => x.Projects);
var d = x.ToList();
//you can use the applicationUser entity instead of person but bad things happen as the project grows.

Related

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details

I'm Learning Webapi so I'm trying to build a simple Api connected to SQL server and I got this error when I add new Movie data
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Movies_SuperHeroes_HeroId". The conflict occurred in database "SupersDb", table "dbo.SuperHeroes", column 'HeroId'.
I have two models :
Superhero Model:
namespace SuperHeroesApi.Models
{
public class SuperHero
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int HeroId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[MaxLength(100)]
public string FirstName { get; set; }
[MaxLength(100)]
public string LastName { get; set; }
[MaxLength(100)]
public string City { get; set; }
}
}
Movie Model :
namespace SuperHeroesApi.Models
{
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MovieId { get; set; }
[Required]
[MaxLength(100)]
public string Title { get; set; }
public int Year { get; set; }
public double Rate { get; set; }
public byte [] Poster { get; set; }
[ForeignKey("SuperHero")]
public int HeroId { get; set; }
//public string SuuperHeroName { get; set; }
public virtual SuperHero SuperHero { get; set; }
}
}
dto :
namespace SuperHeroesApi.Otds
{
public class MoviesDtos
{
public string Title { get; set; }
public int Year { get; set; }
public double Rate { get; set; }
public IFormFile Poster { get; set; }
[ForeignKey("SuperHero")]
public int HeroId { get; set; }
}
}
MoviesController:
using SuperHeroesApi.Otds;
namespace SuperHeroesApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MoviesController : ControllerBase
{
private readonly AppDbContext _dbContext;
private new List<string> _allowedExtention = new List<string> { "jbg", "png" };
private long _maxAllowedPosterSize = 5242880;
public MoviesController(AppDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public async Task<IActionResult>GetAllAsync()
{
var movie = await _dbContext.Movies.ToListAsync();
return Ok(movie);
}
[HttpPost]
public async Task <IActionResult> CreateAsync([FromForm] MoviesDtos dto)
{
if (_allowedExtention.Contains(Path.GetExtension(dto.Poster.FileName).ToLower()))
return BadRequest();
using var dataStream = new MemoryStream();
await dto.Poster.CopyToAsync(dataStream);
var movie = new Movie
{
Title = dto.Title,
Year = dto.Year,
Rate = dto.Rate,
Poster = dataStream.ToArray(),
};
await _dbContext.AddAsync(movie);
_dbContext.SaveChanges();
return Ok(movie);
}
}
}
You probably already have existing rows before you made changes to your schema. Now that you're creating a new foreignkey HeroId in movie which cannot be null and an integer for that matter which means it will be a zero by default. It becomes a problem for the existing rows because they will try to reference a Hero entity with Id of 0 which doesn't exist. So, the obvious solution is to make the foreign key nullable and redo the migrations
[ForeignKey("SuperHero")]
public int? HeroId { get; set; }

My dbContext return null, when I want get list of user, which are from identity

My dbContext return null, when I want get list of user in Index View. This list are from my database AspNetUsers table, which has been generate by identity. I can get other my database table list.
There is my ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<ProductCategory> ProductCategories { get; set; }
public DbSet<ProductBrand> ProductBrands { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Address> Address { get; set; }
public DbSet<Recipe> Recipes { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Order_detail> Order_Details { get; set; }
}
There is my UserController
[Area("Admin")]
public class UserController : Controller
{
UserManager<IdentityUser> _userManager;
private ApplicationDbContext _db;
public UserController(UserManager<IdentityUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_db = db;
}
public IActionResult Index()
{
return View(_db.ApplicationUsers.ToList());
}
}
There is my ApplicationUser.Model, which inherit IdendityUser
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public string Surname { get; set; }
public ICollection<Recipe> Products { get; set; }
public ICollection<Order> Orders { get; set; }
}
I don't know how you register ApplicationDbContext and Identity framework on your ASP.NET Core MVC application because you didn't show them on the question.
There are couple problems in your code.
First, if you have a custom IdentityUser, like the ApplicationUser you have, you would have to use the generic version of IdentityDbContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
...
}
You would need to use the matching generic version of IdentityDbContext if you have any of following:
Custom IdentityUser
Custom IdentityRole
Custom primary key
All 7 classes, user and role, plus IdentityUserRole, IdentityUserClaim, IdentityRoleClaim, IdentityUserLogin, and IdentityUserToken
After you register your custom class with IdentityDbContext, you don't need to put the class as one of the DbSet<> there:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
...
public DbSet<ProductCategory> ProductCategories { get; set; }
public DbSet<ProductBrand> ProductBrands { get; set; }
public DbSet<Product> Products { get; set; }
// public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Address> Address { get; set; }
public DbSet<Recipe> Recipes { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Order_detail> Order_Details { get; set; }
}
You would also need to use the generic version of AddIdentity<TUser>, AddDefaultIdentity<TUser>, or AddIdentityCore<TUser> in your Startup.cs, depending on what you need:
AddDefaultIdentity = AddIdentity + AddDefaultTokens + AddDefaultUI
You didn't specify what version of ASP.NET Core Identity you're using so I don't exactly know which one you use, but the following is how I registered it:
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.User.RequireUniqueEmail = ...;
...
})
.AddEntityFrameworkStores<ApplicationDbContext>();
I have all 7 classes customized as well as change the primary key from string to Guid.
Lastly, to use the dependency injected UserManager and SignInManager, you would need to correct generic version of them as well:
[Area("Admin")]
public class UserController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public UserController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public IActionResult Index()
{
// Get the user list
var users = _userManager.Users.ToList();
// Build your view model to define what your UI only needs, not just passing
// everything to it
var vm = new UserManagementListViewModel
{
Users = users.Select(x => new UserViewModel
{
UserId = x.Id,
UserSurname = x.Surname,
ProductCount = x.Products.Count(),
OrderCount = x.Orders.Count()
})
};
return View(vm);
}
}

Sqlite/SQLServer provider differences

I have some troubles using sqlite provider to write integration tests.
If I attach a tree of entities to my DbContext, I get relations broken (the same thing with SqlServer provider works).
See sample for more information.
For precision, the PK of the 3 tables are Id + TenantId. TenantId is Set only in the SaveChangesAsync method, depending on the connected User.
If I want it to work on sqlite, I have to set the TenantId on the 3 objects before attaching them to the context... Why is this different with SQL Server provider ?
public class Store {
public int Id { get; set; }
public int TenantId { get; set; }
public List<Product> Products { get; set; }
}
public class Product {
public int Id { get; set; }
public int TenantId { get; set; }
public Store Store { get; set; }
public int StoreId { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
}
public class Category {
public int Id { get; set; }
public int TenantId { get; set; }
public List<Product> Products { get; set; }
}
[Test]
public void Test_Create(){
var store = new Store();
store.Products = new List<Product>();
var product = new Product();
product.Category = new Category();
store.Products.Add(product);
dbContext.Stores.Add(store);
// fails, product is detached from the Store after attaching to DbContext
Assert.Single(store.Products);
}

Best way to create a map between two entities with a third one from another context

Hi I'd like to create a map between two entities (source: User, target: UserInfosDto) while one member of the target DTO (UserItemPreference) needs info from a third entity inside another context.
public class UserInfosDto
{
//public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public UserItemPreferencesDto UserItemPreferences { get; set; }
}
public class UserItemPreferencesDto
{
public bool SeeActuality { get; set; }
public bool IsInEditorMode { get; set; }
}
public class User
{
public string IdentityId { get; set; }
//...
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class UserIdentity
{
public string IdentityId { get; set; }
//...
public bool SeeActuality { get; set; }
public bool IsInEditorMode { get; set; }
}
User and UserIdentity come from different databases but have a common property IdentityId. I thought about using ITypeConverter in which I would inject the UserIdentity dbContext. Problem is that I can't find a way to use ITypeConverter on one member only.
Use an IValueResolver instead, which allows to resolve separate members instead of full types.
For your case above it will look like
public class UserItemPreferencesResolver
: IValueResolver<User, UserInfosDto, UserItemPreferencesDto>
{
private readonly UserEntityDbContext _dbContext;
public UserItemPreferencesResolver(UserEntityDbContext dbContext)
{
_dbContext = dbContext;
}
public UserItemPreferencesDto Resolve(
User source,
UserInfosDto destination,
UserItemPreferencesDto destinationMember,
ResolutionContext context
)
{
UserItemPreferencesDto preferences = /* resolve from _dbContext (and transform) */
return preferences;
}
}
Your create the mapping via
CreateMap<User, UserInfosDto>()
.ForMember(
dest => dest.UserItemPreferences,
opt => opt.MapFrom<UserItemPreferencesResolver>()
);

Add list of models to Asp.net Identity

I want to assign a list of stores to a particular client, based on the Identity Model in MVC 5. I want to be able to register a user/client using the default registration in the MVC 5 example code and be able to add a store, then assign the store to the user.
I'm having trouble being able to create a working viewmodel that incorporates the idenity model with a new list.
Here is what I have so far:
Model:
public class ApplicationUser : IdentityUser
{
//[Required]
[Display(Name = "Client/Company Name")]
public string ClientName { get; set; }
public List<Store> Stores { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
I tried to add to the existing default dbcontext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public DbSet<Store> Stores { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
My controller blows up when I try to set my store clientname to the user clientname. Clientname or any other property is unavailable for ApplicationUser.
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Stores
public ActionResult Index(ClientStoreViewModel viewModel)
{
var clients = from s in db.Stores
join c in db.Users
on s.ClientName equals u.ClientName
select new ClientStoreViewModel() { Stores = s, Users = u };
return View(clients);
}
Store class:
public class Store
{
public int Id { get; set; }
//public int ClientId { get; set; }
[Display(Name = "Client Name")]
public string ClientName { get; set; }
[Display(Name = "Store Name")]
public string Brand { get; set; }
[Display(Name = "Store Number")]
public string StoreNumber { get; set; }
[Display(Name="Store Login URL")]
public string LoginURL { get; set; }
}
Finally my viewmodel:
public class ClientStoreViewModel
{
public ApplicationUser Users { get; set; }
public Store Stores { get; set; }
public ClientStoreViewModel()
{
Stores = new Store();
Users = new ApplicationUser();
}
}

Resources