Identity UserManager.AddToRole throws exception - c#

As the title says I am using the new C# MVC 5 Identity doing a simple call:
UserManager.AddToRole(User.Id, User.RequestedRole);
I am doing this in a method of my ViewModel that is called from the Controller
The UserManager is created in the Base Class of my ViewModel like this:
UserManager = new UserManager<TMUser>(new UserStore<TMUser>(new TMContext()));
When I make the above call to AddToRole method, I get this Inner Exception (The outer one is generic/useless):
{"A relationship from the 'Ledger_User' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'Ledger_User_Source' must also in the 'Deleted' state."}
I'm obviously not deleting anything at all but only adding a role to my user. I've had this exception before when I am trying to mix objects from multiple contexts...but I'm not doing that here...please help.
EDIT:
I've gotten rid of the model in case it was interfering and added the following code to my controller:
public ActionResult UpdateRoles(string id)
{
if (ModelState.IsValid)
{
var userManager = new UserManager<TMUser>(new UserStore<TMUser>(new TMContext()));
var userRequestingRole = userManager.FindById(id);
if (!userManager.IsInRole(userRequestingRole.Id, userRequestingRole.RequestedRole))
userManager.AddToRole(userRequestingRole.Id, userRequestingRole.RequestedRole);
// It is still crashing with the same error in the above AddToRole
}
For further information, here is the structure of my TMUser and Ledger objects:
public class TMUser : IdentityUser
{
public TMUser()
{
Ledger = new Ledger();
OrderHistory = new List<Order>();
Clients = new List<Client>();
IsActive = true;
}
[DisplayName("Full Name")]
public string FullName { get; set; }
[DisplayName("Notification Email")]
public string NotificationEmail { get; set; }
public virtual Ledger Ledger { get; set; }
public virtual List<Order> OrderHistory { get; set; }
public virtual List<Client> Clients { get; set; }
public string RequestedRole { get; set; }
public virtual TMUser RoleApprovedBy { get; set; }
public bool IsActive { get; set; }
}
public class Ledger
{
public Ledger()
{
Transactions = new List<Transaction>();
}
public long Id { get; set; }
[Required]
public virtual TMUser User { get; set; }
public virtual List<Transaction> Transactions { get; set; }
public decimal GetBalance()
{
// ...
}
internal void AddTransaction(decimal amount, string description, Order order)
{
// ...
}
}
Another Edit:
Today was another frustrating day. After making some changes in my Context it initially seemed like I fixed the problem. Here is the change I made:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TMUser>().HasOptional(c => c.RoleApprovedBy);
modelBuilder.Entity<TMUser>().HasOptional(c => c.Ledger);
}
I added the above to the DB Context class, mine is: public class TMContext : IdentityDbContext<TMUser>
This worked the first time, I must have broken some kind of an association? However, when I tried again with a different user, a similar, but slightly different Exception happened:
{"A relationship from the 'TMUser_Ledger' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'TMUser_Ledger_Target' must also in the 'Deleted' state."}
So it feels like I am back to square one...I can keep going by removing the Ledger from the User object, but that would be cheating...I really don't want to get hacky with it...please help...

The problem is that you create a new Ledger in the constructor of the TMUser, when you do that you will remove the current ledger for the TMUser and replace it with a new empty one. And then, EF will handle the new Ledger as new object that needs to be inserted in the database. Thats why you are getting the validation error about an entity that is en deleted state.
Another thing with creating an new Ledger in the constructor of TMUser causes the effect that every TMUser has a ledger but in your database model you have set it to nullable (bacause of the HasOptional).

Related

Violation of PRIMARY KEY constraint Cannot insert duplicate key in object

I have a problem I am using lazy loading and virtual, but when using this it generates the following error
Violation of PRIMARY KEY constraint Cannot insert duplicate key in object
How can I solve it?
These are my classes:
public class Producto
{
[Key]
public Guid ProductoId { get; set; }
public Guid InquilinoId { get; set; }
public string Nombre { get; set; }
public decimal Precio_Publico { get; set; }
public string Detalle_producto { get; set; }
public DateTime? Fecha_publicacion { get; set; }
public bool Activo { get; set; }
public string Img_Producto { get; set; }
public string CodigoBarras { get; set; }
public virtual Concepto VigenciaPrecio { get; set; }
public virtual ICollection<Precio> Precios { get; set; }
public bool Es_Almacenable { get; set; }
public int Dias_de_Garantia { get; set; }
public bool Es_Importado { get; set; }
public virtual List<Categoria> Categoria { get; set; } = new List<Categoria>();
public virtual Impuesto Impuesto { get; set; }
public virtual Precio Precio { get; set; }
}
public class Categoria
{
public Guid CategoriaId { get; set; }
public string Nombre { get; set; }
public virtual Producto Producto { get; set; }
}
[HttpPost]
public async Task<ActionResult<Guid>> Post(Producto producto)
{
var user = await userManager.GetUserAsync(HttpContext.User);
var usercontodo = context.Users.Where(x => x.Id == user.Id).Include(x => x.InquilinoActual).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(producto.Img_Producto))
{
var imgProducto = Convert.FromBase64String(producto.Img_Producto);
producto.Img_Producto = await almacenadorDeArchivos.GuardarArchivo(imgProducto, "jpg", "productos");
}
producto.InquilinoId = usercontodo.InquilinoActual.ClienteId;
context.Add(producto);
await context.SaveChangesAsync();
return producto.ProductoId;
}
This is the table categorias:
This is the table productos:
This is the error:
Error:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of
PRIMARY KEY constraint 'PK_Categorias'. Cannot insert duplicate key in
object 'dbo.Categorias'. The duplicate key value is
(1737b24b-93a4-4ad9-4d8b-08d85a75ca8e)
This error is usually the result of passing detached entity graphs, or entity graphs that are constructed in a client to a server to be added/updated.
Producto has a set of categories. If EF is trying persist a category, this means that the Producto you sent it and added to the context had at least one Category in it.
Lets say for example in my client code (JavaScript or some other server code calling the API) I create a new Producto:
// C# Pseudo
var producto = new Producto
{
// set up product fields...
// I have a list of categories pre-loaded, so I want to assign one of them...
Categorias = (new [] { new Categoria { CategoriaId = 3, Nombre = "Three" }}).ToList()
}
Now I pass that Producto to my service. That request has its own scoped DbContext that I set up some info on the Producto and Add it to the context's Producto DbSet. We can assume that the database already has a Category with an ID of 3, but the context isn't aware of it because Producto.Categorias has a reference to a new entity that just happens to have the ID of 3. EF will treat that Category as a new entity and try to insert it along-side the Product. Hence, FK violation as EF tries to insert another Category ID=3.
The complex solution is to attach entities to the current DbContext. As a simple example, with the above Producto coming in:
foreach(var categoria in producto.Categorias)
context.Attach(categoria); // which will treat the category as unmodified, but expect it to be an existing record.
context.Producto.Add(producto);
context.SaveChanges();
This would have to be done for every existing entity associated with and referenced by the Product. This gets complicated because it assumes that each associated entity is unknown by the DbContext. If you have a scenario where you are dealing with multiple Producto objects, or the Category could be referenced by the Producto and another new row under the Producto, or it's possible that the categories could have been read by the DbContext prior to saving the Producto, attempting to Attach the category could fail if EF is already tracking one with the same ID. This can lead to intermittent errors.
To be safer, before attaching an entity, you should test that the Context isn't already tracking it. If it is already tracking a reference, then you need to replace the reference in Producto:
foreach(var categoria in producto.Categorias)
{
var existingCategoria = context.Categorias.Local.SingleOrDefault(x => x.CategoriaId == categoria.CategoriaId);
if (existingCategoria != null)
{ // Context is tracking one already, so replace the reference in Producto
product.Categorias.Remove(categoria);
product.Categorias.Add(existingCategoria);
}
else
context.Attach(categoria); // context isn't tracking it yet.
context.Producto.Add(producto);
context.SaveChanges();
Again, that needs to be done for EVERY reference to safely save a detached entity.
The better solution is to avoid passing entity structures between client and server, and instead pass View Models or DTOs which are POCO (Plain Old C# Objects) containing just the details needed to build an entity.
Given a Producto ViewModel like this:
[Serializable]
public class NewProductoViewModel
{
public string Nombre { get; set; }
public ICollection<Guid> CategoriaIds { get; set; } = new List<Guid>();
// ... Other details needed to create a new Producto
}
When we go to add this new Producto:
[HttpPost]
public async Task<ActionResult<Guid>> Post(NewProductoViewModel viewModel)
{
var user = await userManager.GetUserAsync(HttpContext.User);
var usercontodo = context.Users.Where(x => x.Id == user.Id).Include(x => x.InquilinoActual).FirstOrDefault();
var producto = new Producto(); // Here is our new, fresh entity..
// TODO: Here we would copy across all non-reference data, strings, values, etc. from the ViewModel to the Entity...
if (!string.IsNullOrWhiteSpace(viewModle.Img_Producto))
{
var imgProducto = Convert.FromBase64String(viewModel.Img_Producto);
producto.Img_Producto = await almacenadorDeArchivos.GuardarArchivo(imgProducto, "jpg", "productos"); // This is fine, we get our object from the DbContext.
}
producto.InquilinoId = usercontodo.InquilinoActual.ClienteId;
// Now handle the Categorias....
foreach(var categoriaId in viewModel.CategoriaIds)
{
var categoria = context.Categorias.Single(categoriaId);
producto.Categorias.Add(categoria)
}
context.Add(producto);
await context.SaveChangesAsync();
return producto.ProductoId;
}
Much of your code is pretty much left as-is, but what it accepts is a deserialized view model, not to be confused with a deserialized block of data that can be confused with an entity. The method constructs a new Entity, then would copy across any details from the view model, before performing a lookup against the Context for any references to associate to the new entity. In the above example I use .Single() which would throw an exception if we passed a CategoriaId that didn't exist. Alternatively you could use .SingleOrDefault() and ignore CategoriaIds that don't exist.
The added benefit of using ViewModels is that you can minimize the amount of data being sent to just the fields needed. We don't send entire Categoria classes to the server, just the IDs that were associated. Most cases where I've seen people passing entities around, the reason is to avoid re-loading the entities more than once. (once when the categorias were read to send to the client, and again when the Producto is saved) This rationale is flawed because what gets sent back to the server may "look" like the entity that the server would have sent the client, but it is not an entity. It is a deserialized block of JSON with the same signature of an entity. It is also "stale" in the sense that any data sent to the server may be many minutes old. When updating entities, the first thing you should check is whether the row version of the data coming from the client matches what is in the server. (Has the server data been updated since?) This means touching the database anyways. We also shouldn't trust anything coming from the server. It is tempting to Attach entities, set a Modified State and call SaveChanges but this will overwrite every field on that entity. Clever people can intercept requests from their browsers and modify the data that is serialized into the request which means that data you don't intend to allow to be updated can be replaced if you merely attach that entity.
Your classes mean that, using the standard conventions, a Category can only belong to 1 Product, and a Product has many Categories.
You didn't include the code that builds up a Product (before it is Posted) but the error means that you try to add an existing Category to a new Product.
I think you want to use Categoria as a link table, this should more or less work:
public class Categoria
{
[Key]
public Guid CategoriaId { get; set; }
[Key]
public Guid ProductoId { get; set; }
public string Nombre { get; set; }
public virtual Producto Producto { get; set; }
}
but you get more control by mapping it in OnModelCreating of the DbContext class.

What's the correct way to reference tables using Code First with EF.Core for searching efficiently

Fairly new to EF.Core and I'm having some issues as my tables start getting more complex. Here's an example of what I have defined for my classes. Note ... there are many more columns and tables than what I have defined below. I've paired them down for brevity.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Active { get; set; }
}
Followed by
public class JournalEntry
{
public int Id { get; set; }
public int UserId { get; set; }
public string Details { get; set; }
public DateTime DateEntered { get; set; }
public virtual User User { get; set; }
}
I want to be able to issue the following query and INCLUDE the User Table so that I can then populate a ViewModel with columns from the User Table without having to do another lookup and also to sort the data while retrieving it:
public IQueryable<JournalEntry> GetByUser(int userId)
{
return _DbContext.JournalEntries.Where(j => j.UserId == userId)
.Include(u => u.User)
.OrderBy(u=> u.User.FirstName)
.ThenBy(j => j.DateEntered);
}
My controller would then have something similar to the following:
public IActionResult List(int userId)
{
var journalEntries = new _dbRepository.GetByUser(userId);
var myViewModel = new MyViewModel();
myViewModel.UserName = ($"{journalEntries.User.FirstName} {journalEntries.User.LastName}");
myViewModel.Entries = journalEntries;
etc ....
return View(myViewModel);
}
I'm loading the user's first and last name in the View Model and whatever other attributes from the various tables that are referenced. The problem that I'm having is that I'm getting errors on the Migration creation "Foreign key constraint may cause cycle or multiple cascade paths." And of course, if I remove the line reading public virtual User User { get; set; } from the JournalEntry class then the problem goes away (as one would expect).
I believe that the way I'm doing the models is incorrect. What would be the recommended way that I should code these models? I've heard of "lazy loading". Is that what I should be moving towards?
Thanks a bunch.
--- Val
Your query returns an IQueryable<JournalEntry> not a JournalEntry.
Change the code to get the user details from the first object:
var myViewModel.UserName = ($"{journalEntries.First().User.FirstName} {journalEntries.First().User.LastName}");
In the line above I'm calling First() on your journal entries collection and that would have a User. Then I can access FirstName and LastName.
Also, don't bother with LazyLoading since you are learning. It could cause select n+1 issues if used incorrectly

How to make a list and a property pointing to the same entity

I'm new to entity framework and even if i know how to do it in Merise, i can't do it using code first.
In an entity User, i should have a foreign Key 'Promotion_Id'
In an entity Promotion, i should have a foreign key 'Pilote_Id' that points out to the User entity.
Here is the thing : i also have a List in Promotion which is a list of all users in a promotion. Pilote_Id is the Id of the pilote of that formation, who's, of course, a user.
I tried the following :
public class User : EntityWithId
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Phone { get; set; }
public virtual Promotion Promotion { get; set; }
}
public class Promotion : EntityWithNameAndId
{
//Site is another entity, the place where the promotion is
public virtual Site Site { get; set; }
public List<User> Users { get; set; }
public virtual User Pilote { get; set; }
}
(Note : EntityWithId only contains an Id and EntityWithNameAndId inherits from EntityWithId and only contains a name)
But it only results in having 2 foreign keys in User named Promotion_Id and Promotion_Id1.
I already maked the whole thing work by changing
public virtual User Pilote { get; set; }
with
public virtual Guid PiloteId { get; set; }
But i want some consistency in my entities so.. Is there a correct way to achieve this ?
You will probably need to use explicit mapping to achieve this:
In the OnModelCreating() for your context:
modelBuilder.Entity<User>()
.HasOptional(u => u.Promotion)
.WithRequired(p => p.Pilote)
.Map(u => u.MapKey("PiloteId"); // EF6
// .HasForeignKey("PilotId") // EF Core
This assumes that a user may, or may not have a Promotion, but all promotions have a Pilot.
The Promotion.Users will probably map ok by convention using a UserId on the promotion table, but if there is any issue there:
However, there is a big caveat with this approach which relates to the schema, not EF. There is no restriction/guard that will ensure that the Pilot is one of the Users associated with the promotion. A PiloteId could point to any user, and that user's promotionId may be different.
In any case, the logic around managing who is the pilot will need to be done by code, but this means either checking IDs for valid combinations, or something like:
If a User can only be associated to 1 Promotion, and one user on that promotion can be the Pilot, then you could consider adding a flag to User called "IsPilot".
Then in Promotion:
public virtual ICollection<User> Users { get; set; } = new List<User>();
[NotMapped]
public User Pilote
{
get { return Users.SingleOrDefault(u => u.IsPilote); }
set
{
var newPilote = Users.Single(u => u.UserId == value.UserId); // Ensure the user nominated for Pilote is associated with this Promotion.
var existingPilote = Pilote;
if (existingPilote != null)
existingPilote.IsPilote = false;
newPilote.IsPilote = true;
}
}
If users can be assigned to multiple promotions then you will want to update the schema and mappings to support a many-to-many relationship between user and promotions, such as a UserPromotions table containing UserId and PromotionId. In this case I would consider assigning the IsPilote in this table / linking entity, but again this would need logic to ensure that rules around 1 pilot per promotion, and whether a user can be pilot for more than one promotion.

Include property on ASP.Net Identity 2.0 UserManager.Users.ToListAsync and UserManager.FindByIdAsync

I am trying to implement Asp.Net Identity 2.0. I have managed so far very well with the help of this blog. But I went a slightly different road. I want some data not to be part of the User object, but rather of a newly created Customer object. I want to keep the authentication and authorization data separate from my business data.
So in my case I modified the ApplicationUser class by adding a customer property:
public Customer Customer { get; set; }
The Customer class looks like this:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Insertion { get; set; }
public Gender Gender { get; set; }
public DateTime Birthdate { get; set; }
...etc.
public virtual ApplicationUser User { get; set; }
}
I also added the relationship in de the ApplicationDbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.HasRequired(u => u.User);
base.OnModelCreating(modelBuilder);
}
So now after some modifications to the register viewmodel and the controller I am able to register as a user and have both a User record created in the AspNetUsers table and a Customer record in the Customer table with a reference to the User record. So far so good.
But now when I retrieve the user for example for display purposes the Customer property is empty. Which makes sense, since the underlying record is not loaded. Normally I would use the .Include keyword to load the related records. But I don't know how to do that when using the default UserManager that comes with Asp.Net identity 2.0.
Does anybody know how to achieve this?
I found the answer. Probablu need more searching before answering. The answer is based on the answer in this SO Question
I have added a class called ApplicationUserStore.
public class ApplicationUserStore : UserStore<ApplicationUser>
{
public ApplicationUserStore(DbContext context)
: base(context)
{
}
public override System.Threading.Tasks.Task<ApplicationUser> FindByIdAsync(string userId)
{
return Users.Include(u=>u.Customer).Include(u => u.Roles).Include(u => u.Claims).Include(u => u.Logins).FirstOrDefaultAsync(u => u.Id == userId);
}
}
In this class I have overriden the FindByIdAsync to include the customer record.
Then I changed in IdentityConfig.cs the first line in the method Create which returns the ApplicationUserManager so that it passes the newly created ApplicationUserStore instead of the UserStore.
var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<ApplicationDbContext>()));
I probably also have to override some other fetching methods to include the customer records.

Prevent EF from adding new child objects instead of just association when inserting new parent object?

I have the following:
public int CreateExercise(Exercise newExercise) {
db.Exercises.Add(newExercise);
db.SaveChanges();
return newExercise.ExerciseId;
}
Where Exercise is the following:
public class Exercise {
public Exercise() {
Users = new Collection<User>();
}
[Key]
[Required]
public int ExerciseId { get; set; }
[StringLength(300, ErrorMessage = "The value cannot exceed 300 characters. ")]
public string Title { get; set; }
public virtual ICollection<User> Users{ get; set; }
}
When I create a new Exercise, sometimes pre-existing Users come with it, Id included. Even with an existing UserId specified for these Users, new User entries are getting added here. I only want to add associations, not new users - I'd imagine EF is smart enough to figure this out? What am I doing wrong?
I'm just trying to avoid having to loop through all Users attached to a new Exercise, clearing them out before Adding, making sure they exist, adding the exercise, and then adding each individual user, finally calling SaveChanges.
When disconnected entities are reattached they are in Added state by default.
You can change the state after attaching the exercise.
public int CreateExercise(Exercise newExercise) {
db.Exercises.Add(newExercise);
// set all attached users to unchanged.
db.ChangeTracker.Entries<User>().ToList().ForEach(p => p.State = EntityState.Unchanged);
db.SaveChanges();
return newExercise.ExerciseId;
}

Categories