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 !
Related
I'm trying to update an entity but when I do saveChangesAsync() I'm getting this error message:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
Before updating, I'm getting this entity like this in my repository class:
public IQueryable<Profile> getProfileByUserEmail(string userEmail)
{
return _context.Profiles
.Include(pr => pr.Photos)
.AsQueryable()
.Where(p => p.UserEmail == userEmail);
}
In my service, I call my repository class and materialize the query with the FirstOrDefaulAsync() like this:
public async Task<Profile> getProfileByUserEmail(string userEmail)
=> await _repository.getProfileByUserEmail(userEmail).FirstOrDefaultAsync();
In my controller I get the profile, add a new photo and update it, like this:
var profile = await _service.getProfileByUserEmail(userEmail: model.UserEmail);
Photo newPhoto = new Photo();
newPhoto.Description = model.Description;
newPhoto.Id = Guid.NewGuid();
profile.Photos.Add(newPhoto);
var updated = await _service.update(profile);
This is my Profile entity:
public class Profile
{
[Key]
public Guid Id { get; set; }
public DateTime CreationDate { get; set; }
public string ProfilePhoto { get; set; }
public HashSet<Photo> Photos { get; set; }
}
and my Photo entity:
public class Photo
{
[Key]
public Guid Id { get; set; }
public string Uri { get; set; }
[StringLength(1000)]
public string Description { get; set; }
public Guid ProfileId { get; set; }
public virtual Profile Profile { get; set; }
}
I have found on the internet the reason but not a possible solution, if anybody could help that would be great, thanks in advance.
I have a many-to-many relationship between AppUser : IdentityUser, and TaxAccount, which is an abstract class that separates into Household and Business. I want to query how many TaxAccounts a User has access to on load.
Here's the AppUser class.
public class AppUser : IdentityUser
{
public string FullName { get; set; }
public virtual List<TaxAccount> Accounts { get; set; }
}
Here's the TaxAccount classes.
public abstract class TaxAccount
{
[Key]
public int ID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public virtual List<AppUser> Users { get; set; }
}
public class Household : TaxAccount
{
[MaxLength(1000)]
public string HomeAddress { get; set; }
}
public class Business : TaxAccount
{
[EmailAddress, MaxLength(500)]
public string Email { get; set; }
}
However, when I attempt to query the AppUser object in my Razor page, AppUser.Accounts is null! As a result, I always return to the "NoAccount" page.
public async Task<IActionResult> OnGetAsync()
{
AppUser = await _manager.GetUserAsync(User);
// Checks how many TaxAccounts the user has.
if (AppUser.Accounts == null)
{
return RedirectToPage("NoAccount");
}
else if (AppUser.Accounts.Count == 1)
{
return RedirectToPage("Account", new { accountID = AppUser.Accounts[0].ID });
}
else
{
return Page();
}
}
I've found that if I use a .Include() statement to manually connect TaxAccounts to AppUsers, the connection sticks! I just have to insert a .ToList() call that goes nowhere. My question is: why do I need this statement?
AppUser = await _manager.GetUserAsync(User);
_context.Users.Include(X => X.Accounts).ToList();
// Now this works!
if (AppUser.Accounts == null)
EDIT: I've tested removing virtual to see if it was a lazy loading thing, there's no difference.
I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?
I have to return a single property of a model inside GetAll() method in ASP.NET Core API.
Here is my Model:
using System;
using System.Collections.Generic;
namespace ProjectWayneAPI.Models
{
public partial class Consignment
{
public Guid Id { get; set; }
public DateTime? DateCreated { get; set; }
public bool? Deleted { get; set; }
public string CustomerReference { get; set; }
public string ConsignmentNote { get; set; }
public Guid AccountId { get; set; }
public Guid BookedByUser { get; set; }
public string DescriptionToLeave { get; set; }
public virtual Account Account { get; set; }
public virtual User BookedByUserNavigation { get; set; }
public virtual User CheckedByUserNavigation { get; set; }
public virtual User ModifiedByUserNavigation { get; set; }
public virtual User QuotedByUserNavigation { get; set; }
}
}
I have to return only the ConsignmentNote(but I have to pull all of them) in my controller's method.
Here is my Controller:
[Route("api/[controller]")]
[ApiController]
public class ConsignmentsController : ControllerBase
{
private readonly WayneContext _context;
public ConsignmentsController(WayneContext context)
{
_context = context;
}
//GET: api/Consignment Notes
[Route("[connote]")]
[HttpGet]
public async Task<ActionResult<IEnumerable<Consignment>>> GetConsignmentNote()
{
var connote = await _context.Consignment
.Include(x => x.ConsignmentNote)
.ToListAsync();
return connote;
}
// GET: api/Consignments
[HttpGet]
public async Task<ActionResult<IEnumerable<Consignment>>> GetConsignment()
{
var consignments = await _context.Consignment
.Where(c => c.Deleted == false)
.Include(bu=> bu.BookedByUserNavigation)
.ToListAsync();
return consignments;
}
}
I have to return all the connotes with this method public async Task>> GetConsignmentNote(), if you check the linq query inside the method, that query is returning exception. And also how do I override the [HttpGet] in this case?
Include is used to include related properties (ie your navigation properties, such as Account) and not to select a subset of normal properties like ConsignmentNote.
If all you want is the ConsignmentNote property then you should Select a new Consignment and only populate that specific property:
var connote = await _context.Consignment
.Select(x => new Consignment
{
ConsignmentNote = x.ConsignmentNote
})
.ToListAsync();
return connote; // List<Consignment>
But note that this is still going to select a mostly empty object. If all you want is a list containing the string values only, then you could achieve that by selecting the property directly and changing the return type:
public async Task<ActionResult<IEnumerable<string>>> GetConsignmentNote()
{
var connote = await _context.Consignment
.Select(x => x.ConsignmentNote)
.ToListAsync();
return connote; // List<string>
}
Hi What if you try something like this:
//[Route("[connote]")] < --remove route and call your method GET to verride httpget .. it will match the api/Consignments [GET]
//[HttpGet]
public async Task<ActionResult<IEnumerable<ConsignmentNote>>> Get()
{
var connote = await _context.Consignment
.Include(x => x.ConsignmentNote)
.ToListAsync();
return connote.Select(xx=>xx.ConsignmentNote).ToList();
}
Hope it helps you!!
I am writing an Entity Framework Command for selecting only one record (books) from the database. It needs to be shown via api where user should provide id via url. Also, since table is related to other tables (writers, genres), it needs to include relevant records from these tables. Book can have more genres and it stores it in BookGenres table which is then related to Genre table. Also, Book has one Writer from table with the same name. BookDto is an object which returns only relevant data for end-user. Command looks like this:
EfGetBookCommand : IGetBookCommand
private readonly LibraryContext _context;
public EfGetBookCommand(LibraryContext context)
{
_context = context;
}
public BookDto Execute(int request)
{
//var book = _context.Books.AsQueryable();
var book = _context.Books.AsQueryable();
if (book == null)
{
throw new Exception();
}
var result = book
.Include(b => b.Writer)
.ThenInclude(w => w.Name)
.Include(b => b.BookGenres)
.ThenInclude(bg => bg.Genre)
.Where(b => b.Id == request)
.First();
return new BookDto
{
Id = result.Id,
Title = result.Title,
Writer = result.Writer.Name,
Description = result.Description,
AvailableCount = result.AvailableCount,
Count = result.Count,
BookGenres = result.BookGenres.Select(bg => bg.Genre.Name)
};
When I try this in postman via GET request like this: http://localhost:55666/api/books/4, i get 404. I am unable to use find since I am unable to combine it with Include. I will show relevant classes.
I already have similar command for returning all books and it works so I am not able to find what is wrong with this.
BooksController (in API application)
public class BooksController : ControllerBase
{
private IGetBooksCommand _command;
private IGetBookCommand _command2;
public BooksController(IGetBooksCommand command, IGetBookCommand command2)
{
_command = command;
_command2 = command2;
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
try
{
var BookDto = _command2.Execute(id);
return Ok(BookDto);
}
catch(Exception e)
{
return NotFound();
}
}
BookDto
public class BookDto
{
public int Id { get; set; }
public string Title { get; set; }
public string Writer { get; set; }
public string Description { get; set; }
public int AvailableCount { get; set; }
public int Count { get; set; }
public IEnumerable<string> BookGenres { get; set; }
}
Book (Domain Class for Entity Framework code first approach)
public class Book : BaseEntity
{
public string Title { get; set; }
public int WriterId { get; set; }
public Writer Writer { get; set; }
public string Description { get; set; }
public int AvailableCount { get; set; }
public int Count { get; set; } reserved or not
public ICollection<BookGenre> BookGenres { get; set; }
public ICollection<BookReservation> BookReservations { get; set; }
}
BookGenre (DomainClass)
public class BookGenre
{
public int BookId { get; set; }
public int GenreId { get; set; }
public Book Book { get; set; }
public Genre Genre { get; set; }
}
Genre (DomainClass)
public class Genre : BaseEntity
{
public string Name { get; set; }
public ICollection<BookGenre> BookGenres { get; set; }
}
Writer(DomainClass)
ICommand
public interface ICommand<TRequest>
{
void Execute(TRequest request);
}
public interface ICommand<TRequest, TResult>
{
TResult Execute(TRequest request);
}
IGetBookCommand
public interface IGetBookCommand : ICommand<int, BookDto>
{
}
I should get an BookDto object returned in json, but I Get an 404 error.
I suspect something in the var BookDto = _command2.Execute(id); line
when I check your codebase, I could see a class with the same name already exists and you are trying to re-create it again.
Since you return return NotFound(); just try to make sure your routing is properly done, and the Action is getting the triggered.
I suggest using AttributeRouting like,
[Route("~/api/books/{id}")] [HttpGet]
public IActionResult Get(int id)
{
try
{
var result= _command2.Execute(id);
return Ok(result);
}
catch(Exception e)
{
// Try returning some other exception other than 404.
}
}
try something along these lines. Your initial query is calling AsQueryable() which will cause the sql to execute and you loose lazy loading. You can combine all your sql logic into the main query and get all your joining tables at once. This may have a few syntax errors since i dont have all the code.
public BookDto Execute(int request)
{
var book = _context.Books
.Single(b => b.Id == request)
.Select(s => new BookDto() {
Id = s.Id,
Title = s.Title,
Writer = s.Writer.Name,
Description = s.Description,
AvailableCount = s.AvailableCount,
BookGenres = s.BookGenres.ToList()
});
}