as I wrote in title, I have this code:
public class ApplicationUser : IdentityUser
{
public virtual MapPosition MapPosition { get; set; }
public ApplicationUser()
{
MapPosition = new MapPosition { PositionX = 0, PositionY = 0 };
}
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;
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<MapPosition> MapPositions { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
And in my controller I have method that I call from #Ajax.ActionLink in my view:
public string ChangeXPosition()
{
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
// Get the current logged in User and look up the user in ASP.NET Identity
currentUser = manager.FindById(User.Identity.GetUserId());
currentUser.MapPosition.PositionX++;
//manager.Update(currentUser);
Debug.WriteLine("currentUser.MapPosition.PositionX: " + currentUser.MapPosition.PositionX);
return "currentUser.MapPosition.PositionX: " + currentUser.MapPosition.PositionX;
}
I want to save to database changed value of currentUser.MapPosition.PositionX.
I have found numerous ways to solve this problem, but none of them worked with my project.
I tried those solutions:
how-to-update-identityuser-with-custom-properties-using-mvc5-and-entity-framewor
updating-user-data-asp-net-identity
mvc5-applicationuser-custom-properties
UPDATE
Ok so I tried to do as you said, but still doesn't work:
public class ApplicationUser : IdentityUser
{
public virtual MapPosition MapPosition { get; set; }
public ApplicationUser()
{
}
}
This version works, my controller method does change the value in the table.
But it works only for user, that have been already created. When I create a new User, his new MapPositions record is not created.
public class ApplicationUser : IdentityUser
{
private MapPosition _mapPosition;
public virtual MapPosition MapPosition
{
get { return _mapPosition ?? (_mapPosition = new MapPosition()); }
}
public ApplicationUser()
{
}
}
This version doesn't work at all, doesn't change the value in database and doesn't create record in MapPositions when new user is created.
At least you have to invoke SaveChanges method on the current ApplicationDbContext instance after updating the entity instance's fields/properties (currentUser.MapPosition.PositionX in your case):
public string ChangeXPosition()
{
var dbContext = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(dbContext));
// Get the current logged in User and look up the user in ASP.NET Identity
currentUser = manager.FindById(User.Identity.GetUserId());
currentUser.MapPosition.PositionX++;
//manager.Update(currentUser);
Debug.WriteLine("currentUser.MapPosition.PositionX: " + currentUser.MapPosition.PositionX);
dbContext.SaveChanges();
return "currentUser.MapPosition.PositionX: " + currentUser.MapPosition.PositionX;
}
If it does not help, then you have to explicitly mark the corresponding entry as Modified in the DbContext's change tracker in the following way:
dbContext.Entry(currentUser.MapPosition).State = EntityState.Modified;
After that the change should occur in a database as you can see by manually checking the data in the corresponding table at the database (by using Visual Studio SQL Server Data Tools extension, SQL Server Management Studio or any other database management tool).
Related
READ THE EDIT!
I have two Entities :
public class Principal {
public Guid Id { get; private set; }
public Collection<Dependant> Dependants { get; init; } = new();
public Principal() { }
}
public class Dependant{
public Guid Id { get; private set; }
public Guid PrincpalId { get; private set; }
public Principal Principal{ get; private set; }
public Dependant() { }
}
I access Principal through a repository :
internal class PrincipalsRepository {
private readonly DbSet<Princpal> db;
public PrincipalsRepository (DbSet<Princpal> db) {
this.db = db;
}
public async Task AddAsync(Principal p) {
await this.db.AddAsync(p).ConfigureAwait(false);
}
public async Task<Principal>> GetByIdAsync(Guid id) {
//Notice how there's no Include here!
return await db
.FirstOrDefaultAsync(p => p.Id == id)
.ConfigureAwait(false);
}
}
I configure them like this :
public void Configure(EntityTypeBuilder<Principal > builder) {
builder
.ToTable("Principals")
.HasKey(p => p.Id);
builder
.Navigation(p => p.Dependants)
.AutoInclude(false); //THIS!!!!!
builder
.OwnsMany(p =>
p.Dependants,
navBuilder => {
navBuilder.ToTable("Dependants");
navBuilder.Property<Guid>("Id"); //Important: without this EF would try to use 'int'
navBuilder.HasKey("Id");
navBuilder
.WithOwner(v => v.Principal)
.HasForeignKey(v => v.PrincipalId);
}
);
}
The repo is used in a DbContext:
//PLEASE NOTE: This code might seem a bit broken to you because it's a trimmed down copy-paste from the real code.
public abstract class MyDatabase<TContext> : DbContext
where TContext : DbContext {
public PrincipalsRepository PrincipalsRepository = new PrincipalsRepository (DbPrincipals);
//This is exposed for unit tests
public DbSet<Principal> DbPrincipals { get; set; }
public MyDatabase(DbContextOptions<TContext> options)
: base(options) {
}
}
I configure an in-memory Db :
//PLEASE NOTE: Not everything is detailed here. It's a copy paste from a bigger code base)
private static Database CreateDatabase() {
var _connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
_contextOptions = new DbContextOptionsBuilder<MyDatabase>()
.UseSqlite(_connection)
.Options;
var context = new MyDatabase(_contextOptions);
return context;
}
I run a unit test where I insert an Principal entity with a Dependant:
// Step 1 : Init
using var context = CreateDatabase();
var repo = new PrincipalsRepository(context.DbPrincipals);
// Step 2 : Insertion
var p = new Principal();
p.Dependants.Add(new Dependant());
await context.PrincipalsRepo.AddAsync(p).ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
// Step 3 : Read back
var p2 = context.PrincipalsRepo.GetByIdAsync(p.Id).ConfigureAwait(false);
And then...
Assert.Empty(p2!.Dependants); //The unit test fails because I can see that the Dependant has been loaded
What am I doing wrong? Why is it loaded despite me saying "AutoInclude(false)" ?
Note: After adding AutoInclude(false), creating a new migration changed the Db's model snapshot, but the migration itself was empty. Is that normal???
EDIT:
Like #DavidG and #Gert Arnold suggested in the comments, apparently I need to instantiate a brand new DbContext to do the test, because EF is somehow smart enough to pick up that p2 is the "same" as p, and... populates its navigation links (i.e. does the auto Include) without me asking?!?
I absolutely don't understand what's the logic here (in terms of behaviour consistency).
When I change the test and query p2 from a brand new DbContext instance, it works as I would expect it. I.e. it does find the Principal (p2) but its Dependants collection is empty.
Is this documented anywhere, in one form or another? Even as an implicit sentence that seems obvious on some Microsoft help page?
we are using ef core 3.1
And we want to use dynamic query filter,
I tried sample implementation but did not work correctly we expected, filtering always same tenant id,i tried to explain at below
public class TestDbContext : DbContext
{
public DbSet<TenantUser> TenantUsers { get; set; }
private readonly ITenantProvider _tenantProvider;
private Guid? TenantId => _tenantProvider.TenantId;
public TestDbContext (DbContextOptions<TestDbContext > options, ITenantProvider tenantProvider) : base(options)
{
_tenantProvider = tenantProvider;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TenantUser>()
.HasQueryFilter(p => EF.Property<Guid>(p, "TenantId") == TenantId);
}
}
ITenantProvider returns TenantId from HttpContext headers
this code filtering always same tenant id from coming first request
Update:
public class TenantProvider : ITenantProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Guid? TenantId
{
get
{
if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(HeaderNames.TenantId, out var tenantId) &&
Guid.TryParse(tenantId, out Guid parsedTenantId))
{
return parsedTenantId;
}
return null;
}
}
}
For example
First Request TenantId = 60000000-0000-0000-0000-000000000000
This filter => 60000000-0000-0000-0000-000000000000
Second Request TenantId = 10000000-0000-0000-0000-000000000000
This filter => 60000000-0000-0000-0000-000000000000
We tried something similar like that a few years ago. Main problem is here that OnModelCreating method only triggered once. So HasQueryFilter works once and gets the current tenant id from provider and it applies to all queries the same tenant id.
You should also implement a custom IModelCacheKeyFactory
public class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context)
{
if (context is TestDbContext testDbContext)
{
return (context.GetType(), testDbContext.TenantId);
}
return context.GetType();
}
}
And then, you need to replace like this
var builder = new DbContextOptionsBuilder<TestDbContext>();
builder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
var context = new TestDbContext(builder.Options);
Reference:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.imodelcachekeyfactory
I'm early beginner in ASP.NET, and I'm trying to build user system using Identity framework in ASP.NET-MVC project.
I want to have "Admin" user entity inherit from base entity "applicationUser" which is created by default in my models (with my minor changes):
public class ApplicationUser : IdentityUser
{
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
public class Admin: ApplicationUser
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AdID;
}
I will also add 2 other inheritance from applicationUser, I want to have separeted models for different users to store different information in their database fileds and let all of them login to my site.
In startup I'm trying to initially add admin user using this code (taken somewhere in stackoverflow answers):
public static async Task CreateRoles(SchoolContext context, IServiceProvider serviceProvider)
{
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
// First, Creating User role as each role in User Manager
List<IdentityRole> roles = new List<IdentityRole>();
roles.Add(new IdentityRole { Name = "Student", NormalizedName = "STUDENT" });
roles.Add(new IdentityRole { Name = "ADMIN", NormalizedName = "ADMINISTRATOR" });
roles.Add(new IdentityRole { Name = "Professor", NormalizedName = "PROFESSOR" });
//Then, the machine added Default User as the Admin user role
foreach (var role in roles)
{
var roleExit = await roleManager.RoleExistsAsync(role.Name);
if (!roleExit)
{
context.Roles.Add(role);
context.SaveChanges();
}
}
await CreateUser(context, userManager);
}
private static async Task CreateUser(SchoolContext context, UserManager<ApplicationUser> userManager)
{
var adminUser = await userManager.FindByEmailAsync("alpha#lms.com");
if (adminUser != null)
{
if (!(await userManager.IsInRoleAsync(adminUser, "ADMINISTRATOR")))
await userManager.AddToRoleAsync(adminUser, "ADMINISTRATOR");
}
else
{
var newAdmin = new applicationUser()
{
UserName = "alpha#lms.com",
Email = "alpha#lms.com",
LastName = "Admin",
FirstMidName = "Admin",
};
var result = await userManager.CreateAsync(newAdmin, "password123;");
if (!result.Succeeded)
{
var exceptionText = result.Errors.Aggregate("User Creation Failed
- Identity Exception. Errors were: \n\r\n\r",
(current, error) => current + (" - " + error + "\n\r"));
throw new Exception(exceptionText);
}
await userManager.AddToRoleAsync(newAdmin, "ADMINISTRATOR");
}
}
But when I try to do it, I receive an exception:
"Cannot insert the value NULL into column 'Discriminator', table 'aspnet-University; column does not allow nulls. INSERT fails.
The statement has been terminated."
If I try to change variable Admin type on "new Admin" instead of "new applicationUser", I receive another exception:
The entity type 'Admin' was not found. Ensure that the entity type has been added to the model.
I know that this question is about basics of identity framework; I do not understand them well yet; I've just began to understand it's prinicples and I don't know how to handle my problem.
My guess that I need to create New UserManager, which will be able to manipulate instances which inherit from basic applicationUser model. I will be happy if you recommend me relevant resources on this topic and help me solve my problem.
In case if anyone will have this question - I've decided not to use inheritance from ApplicationUser, because it obviously was wrong approach, instead and inherit all custom user models from IdentityUser class. To make this work I've added to StartUp Configure services this lines:
services.AddIdentity <IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<YourContext>()
.AddDefaultTokenProviders();
My user model declared like this:
public class Admin : IdentityUser
{
}
And in context file I have this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentityUser>(i => {
i.ToTable("Users");
i.HasKey(x => x.Id);
});
modelBuilder.Entity<IdentityRole<>(i => {
i.ToTable("Role");
i.HasKey(x => x.Id);
});
}
With this code I can use Identity's UserManager and RoleManager without additional changes.
I'm trying to populate my database using Entity Framework. I'm using the Seed override and using set initializer from my DbContext. When I create my database from scratch, it doesn't seem to add these values in!
public class PokemonDatabaseInitializer : CreateDatabaseIfNotExists<PkmnContext>
{
protected override void Seed(PkmnContext context)
{
var pkm = new Pokemon
{
Id = 25,
DisplayName = "Pikachu",
RegionId = 1
};
var region = new PokemonRegion
{
Id = 1,
Kanto = true,
};
var location = new PokemonLocation
{
AreaFound = "Viridian Forest",
Id = 1
};
context.Pokemons.Add(pkm);
context.PokemonRegions.Add(region);
context.PokemonLocations.Add(location);
context.SaveChanges();
base.Seed(context);
}
}
public class PkmnContext : DbContext
{
public PkmnContext()
{
Database.SetInitializer(new PokemonDatabaseInitializer());
}
public DbSet<Pokemon> Pokemons { get; set; }
public DbSet<PokemonRegion> PokemonRegions { get; set; }
public DbSet<PokemonLocation> PokemonLocations { get; set; }
}
you need to call
update-database
from package manager console. It also possible to call it from code after any migrations were applied
Please note that it will seed data each time you call it so i would add some checks if items exists already
Edit Is this post lacking sufficient information to get some guidance?
I have this method to insert an entity into the database:
public void Insert(T entity)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
When I inspect entity before adding it to the context, my CustomerRole field is there. Once the add has taken place, the context doesn't seem to have it. Because of this, I am receiving this error:
Entities in 'CcDataContext.Customers' participate in the
'Customer_CustomerRole' relationship. 0 related
'Customer_CustomerRole_Target' were found. 1
'Customer_CustomerRole_Target' is expected.
These images show what I mean:
Inspecting my entity
Inspecting the context
Can anyone explain this behaviour and what I can do about it?
This is the structure of my classes (cut down for brevity):
public class Customer : BaseEntity
{
public CustomerRole CustomerRole { get; set; }
}
class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
HasRequired(t => t.CustomerRole)
.WithMany(t => t.Customers);
}
}
public class CustomerRole : BaseEntity
{
private ICollection<Customer> _customers;
public ICollection<Customer> Customers
{
get { return _customers ?? (new List<Customer>()); }
set { _customers = value; }
}
}
I can confirm that customer map is being added to the configuration and my database is built in line with them.
This is the call I am making which does the insert:
public Customer InsertGuestCustomer()
{
var customer = new Customer();
CustomerRole guestRole = GetCustomerRoleByName("Guest");
if (guestRole == null)
throw new Exception("Customer Role is not defined!");
customer.UserName = "";
customer.EmailAddress = "";
customer.Password = "";
customer.IsAdmin = false;
customer.CustomerRole = guestRole;
_customerRepository.Insert(customer);
return customer;
}
I have no other data in my database, this would be the first customer record and only one CustomerRole. My Customer table has a Foreign Key pointing to my CustomerRole.Id table / column.
Mark your navigation properties as virtual and initialize the collection property in the entity constructor rather than from the property getter.
public class Customer : BaseEntity
{
public virtual CustomerRole CustomerRole { get; set; }
}
...
public class CustomerRole : BaseEntity
{
public CustomerRole()
{
Customers = new List<Customer>();
}
public virtual ICollection<Customer> Customers { get; protected set; }
}
In your Customers property, you were returning a new List in the getter when the backing field was null, but you never assigned this to your backing field.