Entity Framework Core: issue with Contains method - asp.net

There is my simplified model of db:
public class Chat
{
public ICollection<ApplicationUser> Users {get; set;} //nav property - represents participants of a chat
}
public class ApplicationUser : IdentityUser // it represents a net-identity user; it does not have any references to chats
{...}
So, in controller's class I try to get chats such as contain current user as a participant:
var user = GetUser();
_context.Chats.Where(chat => chat.Users.Contains(user)).ToList();
This code throws exception:
You can not use the type of expression ...ApplicationUser for
parameter's type
"Microsoft.EntityFrameworkCore.Storage.ValueBuffer" of method "Boolean
Contains[ValueBuffer](System.Collections.Generic.IEnumerable`1[Microsoft.EntityFrameworkCore.Storage.ValueBuffer],
Microsoft.EntityFrameworkCore.Storage.ValueBuffer)"
What is the problem here?

You need use Any(), like this
var chatsList =_context.Chats.Where(chat => chat.Users.Any(u => u.id== user.id)).ToList();

Related

Not able to model data using Microsoft.EntityFrameworkCore.Cosmos

I am following Microsoft's guide on how to model data in Cosmos Db. I am trying to implement something based on the second JSON snippet in this section of the guide.
It says:
This model has the three most recent comments embedded in the post
container, which is an array with a fixed set of attributes. The other
comments are grouped in to batches of 100 comments and stored as
separate items. The size of the batch was chosen as 100 because our
fictitious application allows the user to load 100 comments at a time.
From the implementation point of view, posts and comments are separate classes but the partition key is id for posts and postId for comments. The container "post" is the same.
I am using Microsoft.EntityFrameworkCore.Cosmos in a .NET 5.0 app. We have one container called "Users". In my scenario, the user has many logs. Users data is represented by the AppUser entity and the logs data is represented by the Logs entity. I want to store three recent logs in the user document and the rest of the logs of the user in a separate document, just like it is represented in the above example from Microsoft's guide.
So here are my models:
public class AppUser : IdentityUser
{
public AppUser()
{
Logs = new List<Used>();
}
public List<Logs> Logs { get; set; }
}
public class Logs
{
public DateTime Date { get; set; }
public string Details { get; set; }
}
public class LogDocument : Entity
{
public LogDocument() : base("LogDocument")
{
Logs = new List<Logs>();
}
public string UserId { get; set; }
public List<Logs> Logs { get; set; }
}
public abstract class Entity
{
public Entity(string type)
{
this.Id = Guid.NewGuid().ToString();
this.Type = type;
}
/// <summary>
/// Object unique identifier
/// </summary>
[Key]
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Object type
/// </summary>
public string Type { get; private set; }
}
The entity configuration in the database context is:
builder.Entity<AppUser>().HasKey(_ => _.Id);
builder.Entity<AppUser>().HasPartitionKey(_ => _.Id);
builder.Entity<AppUser>().Property(_ => _.ConcurrencyStamp).IsETagConcurrency();
builder.Entity<AppUser>().OwnsMany(p => p.Logs);
builder.Entity<LogDocument>().HasKey(e => e.Id);
builder.Entity<LogDocument>().HasPartitionKey(_ => _.UserId)
builder.Entity<LogDocument>().OwnsMany(_ => _.Logs);
builder.Entity<AppUser>().ToContainer("Users");
builder.Entity<LogDocument>().ToContainer("Users");
Whenever I run the application I get the following exception:
InvalidOperationException: The partition key property 'Id' on
'AppUser' is mapped as 'Id', but the partition key property 'UserId'
on 'LogDocument' is mapped as 'UserId'. All partition key properties
need to be mapped to the same store property.
What am I doing wrong? Why should I specify Id as a partition key for LogDocument? If Id is specified as a partition key in the entity configuration of LogDocument, then the error goes away but it is confusing based on how data is represented in the JSON example from Microsoft's guide. There is no key in the comments item. It is just postId and the array of comments.
How would Microsoft's example or my scenario be modelled and configured in a real-world app that uses Microsoft.EntityFrameworkCore.Cosmos?

How do you add a new user and claim in a single transaction using ASP.NET Core 2.1 Identity?

I am trying to add a new user and some other associated entities including a claim as one transaction. My classes are basically defined as below. Note that I am using int primary keys on my User class and that it is an identity column (value is provided by the database on insertion).
public class User : IdentityUser<int>
{
// custom props here
}
public class UserClaim : IdentityUserClaim<int>
{
// i actually haven't added anything to this class, it's mainly here so
// i don't have to keep specifying the user's primary key type
}
public class OtherEntity
{
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
// other stuff
}
I then want to add the user etc. to the database something like this:
User user = new User(){ /* set props */ };
OtherEntity other = new OtherEntity()
{
User = user
};
UserClaim claim = new UserClaim()
{
/* this is where the problem is */
UserId = user.Id,
ClaimType = "my-claim-type",
ClaimValue = "my-claim-value"
};
context.AddRange(user, other, claim);
context.SaveChanges();
I can easily link the User to the OtherEntity because I have set up the navigation property so I can just add the User to it and entity framework takes care of the filling in the UserId column. I cannot do this with UserClaim because it doesn't have the navigation property. I could call context.SaveChanges() after adding the User and entity framework would get the User.Id created by the database for me which I could use to set UserId on the UserClaim, but that would mean two transactions.
I have tried adding the navigation property to my definition of UserClaim as follows:
public class UserClaim : IdentityUserClaim<int>
{
[ForeignKey(nameof(UserId))]
public User User { get; set; }
}
But I get following runtime error:
InvalidOperationException: The relationship from 'UserClaim.User' to 'User' with foreign key properties {'UserId' : int} cannot target the primary key {'Id' : int} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.
Is there a way of creating both the user, and the claim in the same transaction?
My question should have been: "How do I add navigation properties between ASP.NET Identity classes?"
If I had looked for the answer to that I would have found the microsoft docs explaining how to do it!
The docs linked above tell you how to add the User.UserClaims navigation property:
public class User : IdentityUser<int>
{
public virtual ICollection<UserClaim> Claims { get; set; }
}
public class DataContext : IdentityDbContext</* Identity classes */>
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>(e =>
{
e.HasMany(u => u.Claims)
.WithOne()
.HasForeignKey(c => c.UserId)
.IsRequired();
});
}
}
But it doesn't show how to make the reverse UserClaim.User navigation property. I worked out that this can be done like this:
public class UserClaim : IdentityUserClaim<int>
{
public virtual User User { get; set; }
}
public class DataContext : IdentityDbContext</* Identity classes */>
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<User>(e =>
{
e.HasMany(u => u.Claims)
.WithOne(c => c.User) // <- this line is different
.HasForeignKey(c => c.UserId)
.IsRequired();
});
}
}
You can then create a new user and claim at the same time as per my question:
User user = new User(){ /* set props */ };
UserClaim claim = new UserClaim()
{
User = user, // <- this is the bit you can't do without the nav. property
ClaimType = "my-claim-type",
ClaimValue = "my-claim-value"
};
context.AddRange(user, claim);
context.SaveChanges();
I guess it's common sense, though I didn't realise until I inspected the actual SQL hitting the database, but this will still require two trips to the database even though we are only calling .SaveChanges() once! Entity Framework will first save the User and get the ID for the inserted row which it will then use when inserting the UserClaim.
Inserting related data is documented in Entity Framework: https://learn.microsoft.com/pl-pl/ef/core/saving/related-data
And it is also well described in other topics, for example: Entity Framework Foreign Key Inserts with Auto-Id
Every of you entites need to be set correctly for relations (foreign keys) in your entites models (without them, EF don't know what to do) and when you are adding it, you need to start from beginning, so UserClaim must be set from your User entity, for example
var user = new User(){
//properites
OtherEntity = new OtherEntity()
{
Id = 0, /*will be set automatically*/
UserId = 0 /*will be set automatically*/
/* other properites */
};
Claim = new UserClaim(){
Id = 0, /*will be set automatically*/
UserId = 0 /*will be set automatically*/
/* other properites */
}
}
ontext.Add(user);
context.SaveChanges();
You didn't provide all the information about your relations, I've just assumed this from your code.
PS. AddRange has only one parameter.
EDIT:
To clarify my answer, for everything to work, AddRange/Add need to be called with your base entity and relations inside, in tree manner.
But first you need to configure your models, something like that.
public class User : IdentityUser<int>
{
[ForeignKey("UserClaimId")]
public virtual UserClaim Claim {get;set;}
}
public class UserClaim : IdentityUserClaim<int>
{
[ForeignKey("UserId")]
public virtual User User {get;set;}
}
public class OtherEntity
{
public int UserId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
// other stuff
}
You can also use OnModelCreating to set up entites, as described in documentation: https://learn.microsoft.com/en-us/ef/core/modeling/relationships
Entity Framework right now don't know anything about relations in Database, data types etc.
Updated code example
Edit, don't have enough reputation to add comment to your answer
Well, EF still needs to track entities. I would need to write and run some examples to check if there is some way to improve trips to database. Maybe someone could tell you more about mechanism that is behind adding related-data and if there is a way to improve it.
There are some libraries that helps with improving performance of saving changes to database, for example: https://entityframework-extensions.net
You could try it.

ASP.NET Identity 2.0 UserManager.FindByIdAsyc not returning Roles

I am building a website using ASP.NET MVC v5.2, Entity Framework v6.0 (Code First) and Identity 2.0.
I have the following code in my UserAdmin controller:
// GET: /UserAdmin/Details/5
public async Task<ActionResult> Details(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var user = await UserManager.FindByIdAsync(id);
return View(user);
}
My problem is that while I can populate a user's roles with the UserManager, it is not picking up the roles associated with that user when using the FindByIdAsync method.
Here is data from the IdentityUserRole table which shows for the highlighted user assigned to two roles:
Here is the debug info showing the same user as above but the Roles count is zero:
Why are the roles for this user not being returned?
Edit #1
I am not using the default implementations for UserManager.
My ApplicationUser extends IdentityUser to allow me to add custom properties. My ApplicationDbContext extends IdentityDbContext.
Here's where I set up my primary keys for Identity using Fluent API:
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
As you are extending the IdentityDbContext and IdentityUser, you don't need to define your relationships for Logins, Roles and UserRoles as they are already defined in the base classes. You need to remove the lines like modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId }); as they are taken care of.
As for not using the default UserManager There is a possibility here I see based on the code you have provided.
The default implementation for UserManager takes an IUserStore<TUser> as a parameter in it's constructor. If you are using (or deriving from) the UserStore<TUser> implementation in the Microsoft.AspNet.Identity.EntityFramework Library, the things like Roles, Claims and Logins are being included in the queries being made to the database. If you are creating your own class implementing IUserStore<TUser> then you will need to include the related data yourself. an example is shown below.
public class ApplicationUser : IdentityUser
{
//Add your Custom Properties here..
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
//Add your Custom Properties here..
}
public class ApplicationUserStore : IUserStore<ApplicationUser>
{
private readonly ApplicationDbContext _context;
public ApplicationUserStore(ApplicationDbContext context)
{
_context = context;
}
public async Task<ApplicationUser> FindByIdAsync(string userName)
{
return await _context.Users.Include(x => x.Roles).FirstOrDefaultAsync(n => n.UserName == userName);
}
}

Multiple object sets per type are not supported. The object sets 'ApplicationUsers' and 'Users' can both contain instances of type ApplicationUser

I am stuck at an odd issue.
I am learning MVC 5 and almost everything is created by the built-in template.
I only added a class History
public class History
{
[Key]
public DateTime Date { get; set; }
public virtual ApplicationUser User { get; set; }
public string ItemName { get; set; }
}
And inside the built-in ApplicationUser:
public class ApplicationUser : IdentityUser
{
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 virtual ICollection<History> Histories { get; set; }
}
Here is the error message:
Multiple object sets per type are not supported. The object sets 'ApplicationUsers' and 'Users' can both contain instances of type 'MyOnlineShopping.Models.ApplicationUser'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Multiple object sets per type are not supported. The object sets 'ApplicationUsers' and 'Users' can both contain instances of type 'MyOnlineShopping.Models.ApplicationUser'.
Source Error:
Line 124: {
Line 125: var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
Line 126: IdentityResult result = await UserManager.CreateAsync(user, model.Password);
Line 127: if (result.Succeeded)
Line 128: {
Source File: f:\Workplace\MyOnlineShopping\MyOnlineShopping\Controllers\AccountController.cs Line: 126
The above is just a work around, There are many answers to this same problem all around SO.
Here
Here
and Here
Look inside IdentityModel.cs, you will find
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
Inside of this context, VS sometimes adds DbSet<ApplicationUser> ApplicationUsers
THIS IS A DUPLICATE
Inside of the IdentityDbContext class is a DbSet User, when you subclass from IdentityContext, inheritance converts that line to DbSet<ApplicationUser> User.
The fix? Remove the DbSet<ApplicationUser> User from public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
This error happens when your ApplicationDbContext class changed. just do this thing into your Models Folder and go to the IdentityModels.cs class and remove the Last line
public System.Data.Entity.DbSet < Project.Models.ApplicationUserProject.Models.ApplicationUser> ApplicationUsers { get; set; }
This line is generated automatically by scaffold.
I ended up renaming ApplicationUser to User, rescaffolding and everything magically started working.
Looks like the templates are a bit out of wack.

What Type (class) do i use for a property in my POCO for Asp.net Identy User

I have a codefirst POCO, and i want to specify a LastEditUser from my current ASP.NET IDENTITY user. I tried type ApplicationUser that gets generated with the new project. but it just saves as null.
Here is my current Attempt
public class SomeClass
{
public string SomeProperty { get; set; }
public ApplicationUser LastEditMember { get; set; }
}
And here is how i try to save it in my controller.
string currentUserId = User.Identity.GetUserId();
ApplicationUser currentUser = db.Users.FirstOrDefault(x => x.Id == currentUserId);
instannceOfSomeClass.LastEditMember = currentUser;
After loading this again. the LastEditMember property is null.
I would suggest using the UserManager class to get the user object instead of working with the DbContext object. You can use UserManager.FindByIdAsync(currentUserId) to get the user. Also I am considering that this code is hit only after a user logs into the application else the currentUserId will be null

Resources