Exception when comparing Guid - c#

I am trying to get a list of all categories that contain a site with a specific ID. Below is the scaffold generated method i slightly modified.
[HttpGet("Categories/{id}")]
public async Task<IActionResult> GetCategoriesSite([FromRoute] Guid id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// This line throws the error
var categories = await _context.Categories.Where(o => o.Site.Id.Equals(id)).ToListAsync();
if (categories == null)
{
return NotFound();
}
return Ok(categories);
}
Unfortunately when i run this it throws the following error:
System.ArgumentException: Expression of type 'System.Nullable`1[System.Guid]' cannot be used for parameter of type 'System.Guid' of method 'Boolean Equals(System.Guid)'
Entities are very simple:
public class Category
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Site Site { get; set; }
}
public class Site
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Category> Categories { get; set; }
}
What could i be doing wrong?

Seems like a bug in EF Core, check this issue on github. Workaround with replacing of Equals() with == operator worked fine for me:
var categories = await _context.Categories.Where(o => o.Site.Id == id).ToListAsync();

Related

Issues with GET method (Web API c# .NET)

I'm working on my first API and I've had some issues so far.
I have a User that has a Profession.
The model for User is:
namespace Sims.Models
{
public partial class User
{
public User()
{
DataUsages = new HashSet<DataUsage>();
}
public long IdUser { get; set; }
public int UserProfessionId { get; set; }
public int UserProfessionFieldId { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public byte[]? UserPicture { get; set; }
public virtual Profession UserProfession { get; set; } = null!;
public virtual ProfessionField UserProfessionField { get; set; } = null!;
public virtual ICollection<DataUsage> DataUsages { get; set; }
}
}
The model for Profession is:
namespace Sims.Models
{
public partial class Profession
{
public Profession()
{
ProfessionFields = new HashSet<ProfessionField>();
Users = new HashSet<User>();
}
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
public virtual ICollection<ProfessionField> ProfessionFields { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
I've tried many solutions but either the methods doesn't return what I want, either it cannot compile.
For example, the automatically generated EntityFrameworkCore GET method returns information only about the user but the profession is null:
{
"idUser": 1,
"userProfessionId": 1,
"userProfessionFieldId": 1,
"userName": "user_test",
"userMail": "mail#test.com",
"userCompany": "TestCompany",
"userPicture": null,
"userProfession": null,
"userProfessionField": null,
"dataUsages": []
}
The thing is the foreign key isn't null in the MySQL database.
In case, here is the method:
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
I also tried to include the Profession model like with the following code but I get an object cycle:
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.Include(u => u.UserProfession)
.ToListAsync();
}
I also tried to Select the data I wanted but with the two following methods, I either got a compiling error stating that "Cannot convert anonymous type to model user", or I got compiling errors about "cannot initialize type "User" with a collection initializer because it does not implement 'System.Collections.IEnumerable'"
Here are the respective codes:
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users
Select(u => new {
u.IdUser,
u.UserName,
u.UserMail,
u.UserCompany,
u.UserProfessionId,
u.UserProfession
})
.ToListAsync();
}
and
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users
.Select(u => new User {
u.IdUser,
u.UserName,
u.UserMail,
u.UserCompany,
u.UserProfessionId,
u.UserProfession
})
.ToListAsync();
}
If you have any ideas, recommendations or advice, I will gladly hear from you.
Thanks for reading !

Return a parent entity together with its child entities using LINQ method syntax

NET Core web API project, and there is one Get action method that returns an entity(Keyfield) when passing in an id. I would like to modify this method so that it returns the same entity but now together with its children entities(Referencefields). How can I get this within the same call to the database using LINQ method syntax inside this Get method?
My get method:
[HttpGet("{id}")]
public async Task<ActionResult<KeyField>> GetKeyField(int id)
{
var keyField = await _context.KeyFields.FindAsync(id);
if (keyField == null)
{
return NotFound();
}
return keyField;
}
My two classes:
public class KeyField
{
public int KeyFieldId { get; set; }
public string KeyName { get; set; }
public string ShortDesc { get; set; }
public List<ReferenceField> ReferenceFields { get; set; }
The child entity:
public class ReferenceField
{
public int ReferenceFieldId { get; set; }
public int KeyFieldId { get; set; }
public virtual KeyField KeyField { get; set; }
public string FieldName { get; set; }
public string Instructions { get; set; }
}
Use Eager loading
[HttpGet("{id}")]
public async Task<ActionResult<KeyField>> GetKeyField(int id)
{
var keyField = await _context.Set<KeyField>()
.Where(x => x.KeyFieldId == id)
.Include(x => x.ReferenceFields)
.FirstOrDefaultAsync();
if (keyField == null)
{
return NotFound();
}
return keyField;
}
Or enable Lazy Loading as another option.

Relational Database SQL Query in Asp.NET Core

public async Task<List<Note>>ShowAssigned()
{
return await _context.Notes
.Where(x => x.List.OwnerId != x.OwnerId)
.ToListAsync()
}
I get no syntax εrrors, but it seems you can't access attributes from related Data in this way.
Basically the goal is: A user creates a List, then some Notes for this List. Then he should be able to assign one of that Notes to another User. When that other User logs on, he should be able to see that new Note that was assigned to him.
Can anyone help me out with this?
public class List
{
public Guid ListId { get; set; }
public string OwnerId { get; set; }
public List<Note> Notes { get; set; }
}
public class Note
{
public Guid ID { get; set; }
public string OwnerId { get; set; }
[ForeignKey("ListId")]
public Guid ListId { get; set; }
public List List { get; set; }
}
And the context class:
public DbSet<Note> Notes { get; set; }
public DbSet<List> Lists { get; set; }
When i try to access Data the same way in a view like that:
#model List<Project.Models.Note>
#foreach (var item in Model)
{
if (item.List.OwnerId == item.OwnerId)
i get this error when running the web app (no syntax errors):
NullReferenceException: Object reference not set to an instance of an object
First write your model classes as follows:
public class List
{
public Guid ListId { get; set; }
public string OwnerId { get; set; }
public virtual List<Note> Notes { get; set; }
}
public class Note
{
public Guid ID { get; set; }
public string OwnerId { get; set; }
[ForeignKey("List")] // Not ListId, its List
public Guid ListId { get; set; }
public virtual List List { get; set; }
}
If your project is on ASP.NET Core < 2.1
Then write your query as follows:
await _context.Notes.Include(n => n.List).ToListAsync()
If your project is on ASP.NET Core >= 2.1
Then in the ConfigureServices() method in Startup class:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseLazyLoadingProxies().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
Don't forget to install appropriate version of Microsoft.EntityFrameworkCore.Proxies nuget package because UseLazyLoadingProxies() resides in this package.
Then write your query as follows:
await _context.Notes.ToListAsync()
So I found the answer to my problem, in some parts with the help of TanvirArjel (but i basically did it differently)
public async Task<List<Note>> GetAssignedItemsAsync(ApplicationUser user)
{
var lists = await _context.Lists.Include(l => l.Notes).Where(x => x.OwnerId != user.Id).ToListAsync();
var notesListe = new List<Note>();
foreach (List l in lists)
{
foreach (Note n in l.Notes)
{
if (n.OwnerId == user.Id)
{
notesListe.Add(n);
}
}
}
return notesListe;
}

Return DbSet based on selection

I'm writing an ASP.NET Core MVC web app that is a tool for handling a parts database. What I want is for the user to select a Part and then that will do some action, like delete that part from it's DB. However, I want this to be a generic action used by all the parts.
I have a class hierarchy which is:
Part
PartA
PartB
What I need is some method that I can call that will get the DbSet that my part belongs to. This is an example of what I'm looking to do:
Models
public class Part
{
public Nullable<int> ID { get; set; }
public string Brand { get; set; }
}
public class PartA : Part
{
public int Length { get; set; }
public List<Image> Images { get; set; }
}
public class PartB : Part
{
public int Durability { get; set; }
}
public class Image
{
public Nullable<int> ID { get; set; }
public string ImagePath { get; set; }
}
PartsDbContext
public class PartsDbContext : DbContext
{
public DbSet<PartA> PartAs { get; set; }
public DbSet<PartB> PartBs { get; set; }
}
PartsController
public IActionResult DeletePart (string partType, int id)
{
var partSet = GetDbSet(partType);
var part partSet.FirstOrDefault(e => e.ID == id);
if (part != null)
{
partSet.Remove(part);
_context.SaveChanges();
}
}
//function to find and return DbSet of the selected type
private DbSet<Part> GetDbSet (string partType)
{
switch (partType)
{
case "PartA":
return _context.PartAs;
case "PartB":
return _context.PartBs;
}
return null;
}
Now obviously this doesn't work because the compiler will complain that:
You can't convert type DbSet<PartA> to type DbSet<Part>
Anyone know how I might go about doing this?
This is really hacky, but sort of works.
public IActionResult DeletePart (string partType, int id)
{
Type type = GetTypeOfPart(partType);
var part = _context.Find(type, id);
var entry = _context.Entry(part);
entry.State = EntityState.Deleted;
_context.SaveChanges();
}
However, you really should just use polymorphism and generic abstract Controllers.
EDIT You can also use Explicit Loading for this.
private void LoadRelatedImages(IPart part)
{
_context.Entry(part)
.Collection(p => p.Images)
.Load();
}

MVC 5 Create Validation error but valid ModelState

I am trying to create within MVC 5 and am getting a validation error even though the ModelState is coming back valid.
Error message
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
and when I look at the message, it shows....
The name 'e' does not exist in the current context
When I look at the POST data, the model that was created has all required fields filled in. I did notice that the model ID was assigned 0. I'm not sure if that is the error or if it is supposed to pass a zero for the ID.
What might the problem be?
WosController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "id,woNumber,woDescription,dueDate,qty,item_id,releaseDate,parentWO_id,wip_id")] Wo wo)
{
if (ModelState.IsValid)
{
db.Wos.Add(wo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wo);
}
Wo.cs
public partial class Wo
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Wo()
{
this.WoParts = new HashSet<WoPart>();
this.WoStatuses = new HashSet<WoStatus>();
}
public int id { get; set; }
public string woNumber { get; set; }
public string woDescription { get; set; }
public Nullable<System.DateTime> dueDate { get; set; }
public string qty { get; set; }
public Nullable<int> item_id { get; set; }
public Nullable<System.DateTime> releaseDate { get; set; }
public string status { get; set; }
public Nullable<int> parentWO_id { get; set; }
public int wip_id { get; set; }
public Nullable<int> part_id { get; set; }
public virtual Item Item { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoPart> WoParts { get; set; }
public virtual Wo woParentWO { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoStatus> WoStatuses { get; set; }
public virtual Part Part { get; set; }
public virtual Wip Wip { get; set; }
}
Wrap your call to SaveChangesAsync in a try...catch like so:
try
{
await db.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
var errorMessages = e.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(e.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, e.EntityValidationErrors);
}
That will show you the actual properties causing the validation issues. Then, update your question with the results, if you still need assistance.
Likely, your database is out of sync with your entities. The status property is not required on your entity, and by default properties of type string are nullable. That would explain why you're passing validation on post, but failing on actually saving the entity.
Generally, it's best not to rely on the database setting a default value in the first place. Instead, have the property itself have a default value, and then it will always be fine, regardless of what's going on at the database level:
private string _status;
public string status
{
get { return _status ?? "Default Value"; }
set { _status = value;
}
Short of that, if status is truly not required, then you should ensure that the status column on your table is nullable.

Categories