cannot convert from 'string' to 'Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole - c#

I'm fairly new with updating databases, and I've constructed the below code to replace a user's role with a new role. I'm getting the error in the subject though.
public void UpdateRole(string id, string newRoleID)
{
var user = Users.FirstOrDefault(u => u.Id == id);
var oldRoleId = user.Roles.FirstOrDefault().RoleId;
if (user != null && oldRoleId != newRoleID)
{
user.Roles.Remove(oldRoleId);
user.Roles.Add(newRoleID);
}
}
Could someone please explain why I am getting this error? I am not trying to convert anything. I am attempting to delete the contents of RoleId for the user id specified, and replace it with the new ID that is sent from my post action.

user.Roles.Add method takes a IdentityUserRole object while you are passing it a string value (i.e. newRoleID). You need the following change in you code:
user.Roles.Add(new IdentityUserRole { RoleId = newRoleID });
Edit
The Remove method, needs an IdentityUserRole object too. But note that it must be attached to the context too. The simplest way you can do it is through the following code:
var user = Users.FirstOrDefault(u => u.Id == id);
var oldRole = user.Roles.FirstOrDefault();
if (user != null && oldRole.RoleId != newRoleID)
{
user.Roles.Remove(oldRole);
user.Roles.Add(new IdentityUserRole { RoleId = newRoleID });
}

Related

EF Core - Modify realations without loading entity

I was trying to implement a function that will let a user like a comment. If the user has already liked it, it can't be liked again and vice versa.
This is what it looks like:
public async Task<ActionResult<CommentResponse>> LikeComment(LikeComment like)
{
if (like.HasNullProperty())
return BadRequest("Missing properties!");
var comment = await commentService.GetCommentWithLikes((int) like.CommentId);
if(comment is null)
return NotFound($"No comment with id {like.CommentId} was found");
try
{
var userId = User.GetUserID();
comment = await commentService.LikeComment(comment, userId, (bool)like.Liked);
return comment is not null ? Ok(comment.GetCommentResponse((bool)like.Liked)) : StatusCode(304);
}
catch(Exception e)
{
return StatusCode(500, $"Error while trying to {((bool)like.Liked ? "like" : "dislike")} comment");
}
}
Relevant functions:
public async Task<Comment> GetCommentWithLikes(int id) => await blogContext.Comments.IncludeLikes().FirstOrDefaultAsync(x => x.Id == id);
public static IQueryable<Comment> IncludeLikes(this IQueryable<Comment> source)
=> source.Select(x => new Comment
{
Id = x.Id,
ArticleId = x.ArticleId,
CreatedById = x.CreatedById,
CreatedAt = x.CreatedAt,
Likes = x.LikedBy.Count,
Text = x.Text,
});
And the main like logic:
public async Task<Comment> LikeComment(Comment comment, string userId, bool liked)
{
var user = new User { Id = userId };
var hasLiked = await blogContext.Comments.Where(x => x.Id == comment.Id && x.LikedBy.Any(x => x.Id == user.Id)).FirstOrDefaultAsync() is not null;
Action action = null;
if (!hasLiked && liked)
{
action = () => comment.LikedBy.Add(user);
comment.LikedBy = new List<User>();
comment.Likes++;
}
else if (hasLiked && !liked)
{
action = () => comment.LikedBy.Remove(user);
comment.LikedBy = new List<User> { user };
comment.Likes--;
}
if (action is null)
return null;
blogContext.Attach(user);
blogContext.Attach(comment);
action();
await blogContext.SaveChangesAsync();
return comment;
}
The idea was to not load the whole likedBy relation, but still notify EF Core that i have added or removed one user. Therefore i modify the Comment, then attach it so EF Core tracks the changes to the likedBy relation. Interestingly, it works fine when liking a comment. However, when disliking, i get an rrror that the comment is already attached. Using .AsNoTracking() in the GetCommentsWithLikes function didn't help.
The instance of entity type 'Comment' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
This is the comment passed to the like func when linking (works):
This is the one when disliking (only diff is the like count...):
And this is it right before the failing attach:
Maybe someone knows the reason for this behaviour and can help me or suggest a different approach :)
Thanks
Using .AsNoTracking() in the GetCommentsWithLikes function didn't help
Due to the used projection, that function is already implicitly no tracking. It is the following call
var hasLiked = await blogContext.Comments
.Where(x => x.Id == comment.Id && x.LikedBy.Any(x => x.Id == user.Id))
.FirstOrDefaultAsync() is not null;
which is adding a Comment instance to the change tracker when the result is not null.
Since you don't need that instance and are just checking for existence, use the following instead which doesn't involve entity instances, but pure server side query:
var hasLiked = await blogContext.Comments
.AnyAsync(x => x.Id == comment.Id && x.LikedBy.Any(x => x.Id == user.Id));

Returning IEnumerable<> for a model in ASP.NET Web API

I am trying to get a list whose type is a model(called ItemDetail). However, I get this error: "Object reference not set to an instance of an object."
public class ItemDetail
{
public decimal sum { get; set; }
public string username { get; set; }
public string units { get; set; }
public string remarks { get; set; }
}
The API code is as follows:
[HttpGet]
[Route("api/items/details/{id}")]
public IEnumerable<ItemDetail> ItemDetails(int id)
{
using (ShopEntities entities = new ShopEntities())
{
var itemDetails = entities.vwItemDetails.ToList();
var userIds = from data in itemDetails
select data.Userid;
var item_Details = new List<ItemDetail> ();
foreach (int userId in userIds)
{
int totals = (int)entities.vwItemDetails
.Where(i => i.UserID == userId & i.item_id == id)
.Select(i => i.quantity)
.DefaultIfEmpty(0)
.Sum();
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id & i.Userid == userId);
item_Details.Append(new ItemDetail
{
username = itemRecord.username,
units = itemRecord.units,
remarks = itemRecord.Remarks,
sum = totals
});
}
return item_Details;
}
}
Thanks in advance for your help.
EDIT: The error occurs inside the foreach loop where I'm trying to append the new ItemDetail (item_Details.Append(new ItemDetail)
I think I see the problem...
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id & i.Userid == userId);
Your filtering for the SingleOrDefault() is using a bitwise AND operator instead of boolean one. The value of itemRecord as it's written right now is almost certainly always null. Try changing that line to this:
var itemRecord = entities.vwItemDetails.SingleOrDefault(i => i.item_id == id && i.Userid == userId);
EDIT:
I just realized you do the same thing in this LINQ area:
int totals = (int)entities.vwItemDetails
.Where(i => i.UserID == userId & i.item_id == id)
.Select(i => i.quantity)
.DefaultIfEmpty(0)
.Sum();
Again, totals is probably coming up as 0.
EDIT 2:
There is more wrong here than I original anticipated. I created a semi-complete working sample and you've got problems beyond the use of the bitwise operator.
As orhtej2 pointed out in the comments, you are setting yourself up to return null, but you don't check for it. So that is the immediate cause of your exception. You're probably iterating through a list of user IDs where some of the IDs aren't linked to the item ID you're working with. That will return a null because of SingleOrDefault.
The fix for that is to check if itemRecord is null, and if so, do continue; to move onto the next user ID. Or if you want to stop processing and return an error, do that. Either way the situation should be handled.
Related to that is another consequence of using SingleOrDefault. A friend of mine pointed out that if you end up with more than one result in your where clause there, you will get an exception as well. Unless you can guarantee that no single user ID will have more than one instance of a given item ID in that collection of item details, you need to use a different method. The most straightforward would be to use Where() instead, and handle the IEnumerable<> that it returns. You'll have another loop, but that's showbiz.

c# how to find an id from an element in database

How i get the id from the value english, searching all time without any answer.
Thanks for helping
test.CourseId = await _context.Course.FirstOrDefaultAsync(m => m.Schulfach == english);
Your code retrieves the whole object. You need to access a single property of this object:
var course = await _context.Course.FirstOrDefaultAsync(m => m.Schulfach == english);
if (course != null)
{
test.CourseId = course.CourseId;
}

Non-static method requires a target in webapi

I want to send a HTTP POST request in a controller.
This is my code:
[HttpPost]
public IHttpActionResult login([FromBody] ClubAPI.Models.AllRequest.login userid)
{
using (DAL.ClubEntities db = new ClubEntities())
{
DAL.AspNetUser q = db.AspNetUsers.Single(t=>t.Id.Equals(userid.id.ToString()));
if (q == null)
{
return Ok(new ClubAPI.Models.AllResponse.loginResponse
{
msg = "bad",
state = false,
});
}
return Ok(new ClubAPI.Models.AllResponse.loginResponse
{
msg = "good",
state = true,
token = q.UserName,
});
}
}
But now I get the error below:
"Non-static method requires a target in webapi" i cant solve that. the
error is related to this line:
db.AspNetUsers.Single(t=>t.Id.Equals(userid.id.ToString()));
When I change the ID to a real ID, like "t.Id.Equals(2342)", the error is resolved; but I do not want to use the real ID.
Try this code instead of
DAL.AspNetUser q = db.AspNetUsers.SingleOrDefault(t=>t.Id == userid.id.ToString());
into
DAL.AspNetUser q = db.AspNetUsers.SingleOrDefault(t=>t.Id == userid.id);
It seems your problem is in the following line which probably does not find the user:
DAL.AspNetUser q = db.AspNetUsers.Single(t=>t.Id.Equals(userid.id.ToString()));
write it like (depending on the type of id) which will return null when the user is not found:
DAL.AspNetUser q = db.AspNetUsers.SingleOrDefault(t=>t.Id == userid.id.ToString());
If t.Id.Equals(2342) works, then t.Id must not be a string and so you shouldn't be using .ToString()
Try
db.AspNetUsers.Single(t=>t.Id.Equals(userid.id));
or
db.AspNetUsers.Single(t=>t.Id == userid.id);
Make sure this userid not null and userid.id.ToString() is not return null
DAL.AspNetUser q = db.AspNetUsers.Single(t=>t.Id.Equals(userid.id.ToString()));
when any lambda variable return null then this type of error happen.
To prevent this you can do like this
DAL.AspNetUser q = db.AspNetUsers.SingleOrDefault(t=>t.Id == userid.id.ToString());

linq statement if users.Claims contains claims equal to parameters (LINQ)

My db table structure is
ClaimsTable
Id (int) UserId (FK) ClaimType (string) ClaimValue (string)
1 1 Role Administrator
I'm not tracking claims from users side, so when I need users claim I load manually.
RIght now I have simple linq question but I cannot see way out from here
I'm loading users claims and I want to check does that claim match with one passed as an argument
public bool HasClaim(User user, string type, string value)
{
var claimsRepository = ... claimsrepository init ....
var userClaims = claimsRepository.FindAll().Where(usr => usr.User == user).ToList();
if (userClaims.Count() > 0)
{
// linq statement to select those claims which has type and value
// equal to method parameters
bool containsClaim = ?????
if (containsClaim == true)
return true;
}
return false;
}
I believe you are looking for:
return claimsRepository.FindAll().Any(user => user.User == user &&
user.ClaimType == type &&
user.ClaimValue == value);

Categories

Resources