How to track identity changes in EF Core? - c#

I have a service in my application which creates a User, and saves it to the database. Here's a method of my service:
public async Task<UserDTO> CreateUserAsync(User newUser)
{
var result = (UserDTO)await _userRepository.CreateUserAsync(newUser);
if (result != null)
{
await _userRepository.SaveChangesAsync();
}
return result;
}
And a method from a UserRepository:
public async Task<User> CreateUserAsync(User newUser) => (await _dbContext.AddAsync(newUser)).Entity;
Here's a User class:
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The problem is when the user is being added via service, UserId is not known yet. It has default value 0, then ef core saves it to a database, finding a proper UserId. But value returned by my methods has no UserId updated - it is still 0, and i would like to return updated value. How to achieve that in a proper way?

newUser WILL have an Id read from the database.
Your code is casting from User to UserDTO, which is unlikely to work.

Related

ASP.NET Core WebApi Return All Values by Attribute

My WebApi has a table for applications with the following class:
namespace Models.Public
{
[Index(nameof(UUID), nameof(UID), IsUnique = true)]
public class Application
{
public Application()
{
this.UUID = new Guid();
}
public int ID { get; set; }
public Guid UUID { get; set; }
[Required]
public string UID { get; set; }
public string Publisher { get; set; }
public string Name { get; set; }
public string Version { get; set; }
}
}
The field UUID and ID are unique, so I was able to generate the required HttpGet command to obtain the results matching for that.
However, I am trying to obtain an IEnumerable object of all the items that match the Publisher field. That is, return all object that have "Google" as their Publisher.
My attempts have not been successful and I am hoping for some advise to fix my code:
// GET: api/Application/<publisher>
[HttpGet("{publisher}")]
public async Task<ActionResult<IEnumerable<Application>>> GetApplication(string publisher)
{
var application = await _context.Application.ToListAsync(publisher);
if (application == null)
{
return NotFound();
}
return await _context.Application.ToListAsync();
}
Publisher is not a unique value, so I'd like to be able to return all items as a JSON object that have whatever Publisher I type in the list. If no matches, error handle with NotFound();.
You will need to filter using .Where, .Contains
// GET: api/Application/<publisher>
[HttpGet("{publisher}")]
public async Task<ActionResult<IEnumerable<ApplicationData>>> GetApplication(string publisher)
{
var applications = _context.Application.Where(a=>a.Publisher.Contains(publisher)));
/* You could also use == for exact match
var applications = _context.Application.Where(a=>a.Publisher == publisher));
*/
if (applications.Count() == 0)
{
return NotFound();
}
return await applications.ToListAsync();
}

Entity Framework won't add children to parent object

The api response returns as if the function ran without problems but when i look the database the row simple is not there.
I don't get any error messages.
Even if I try to add one exam directly through _context.Exames it won't add.
I'm getting really frustrated because I don't even know where or what I should look for as the api returns that the method run successfully
I'm using ASP.NET Core 5 and EF Core with MySQL.
Here is the code:
public class Account
{
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
public List<Exame> Exames { get; set; }
public List<Consulta> Consultas { get; set; }
}
public class Exame
{
[Key]
public int ExameID { get; set; }
public long Image { get; set; }
}
public class DataContext : DbContext
{
public DbSet<Account> Accounts { get; set; }
public DbSet<Exame> Exames { get; set; }
}
[HttpPost("exame")]
public IActionResult CreateExame(ExameRequest exame)
{
_accountService.CreateExame(exame);
return Ok(new { message = "Exame added successfully" });
}
public void CreateExame(ExameRequest model)
{
var account = _context.Accounts.SingleOrDefault(x => x.Id == model.AccountId);
var exame = new Exame();
exame.ExameID = model.Id;
exame.Image = model.Image;
if (account.Exames == null)
{
account.Exames = new List<Exame>();
}
account.Exames.Add(exame);
_context.Accounts.Update(account);
_context.SaveChangesAsync();
}
It looks like it's returning before the Save has finished, try altering it to something like:
[HttpPost("exame")]
public Task<IActionResult> CreateExame(ExameRequest exame)
{
var result = await _accountService.CreateExame(exame);
// check the value and return the appropriate message.
return Ok(new { message = "Exame added successfully" });
}
public async Task<int> CreateExame(ExameRequest model)
{
// removed other code
return await _context.SaveChangesAsync();
}
Since the call to SaveChangesAsync() is async, the code is making the call and moving straight back to the api call and returning OK. The result of the SaveChangesAsync is as follows.
A task that represents the asynchronous save operation. The task
result contains the number of state entries written to the underlying
database. This can include state entries for entities and/or
relationships. Relationship state entries are created for many-to-many
relationships and relationships where there is no foreign key property
included in the entity class (often referred to as independent
associations).
I have the same problem as I am following a course.
it is probably a versioning problem, cause my code is exactly the same as the instructor but I can't get the result I want.
I think it is because of Microsoft.EntityFrameworkCore.InMemory version.
the 5th version apparently don't have this problem but 7 does.

How to correctly pass a string id to API controller in Xamarin.Forms

I'm writing a mobile app using Xamarin Forms where I am going to consume an REST API.
At the moment, I have a user model
{
public string UserId { get; set; }
public string UserDisplayName { get; set; }
public int UserRoleId { get; set; }
public string UserFirstName { get; set; }
public string UserLastName { get; set; }
public string UserEmail { get; set; }
public string UserPostcode { get; set; }
public DateTime UserCreatedAt { get; set; }
public DateTime UserModifiedAt { get; set; }
public bool UserDeletedAt { get; set; }
}
And I have defined a GetUser method on my controller
// GET: api/Users/5
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(string id)
{
var user = await _context.User.FindAsync(id);
if (user == null)
{
return NotFound();
}
return user;
}
If I test the API using Postman and parse the string id without quotes(edit) on the route, it works fine. E.g. https://localhost:5051/api/Users/Example. However, if I parse the id within qutoes(edit) it doesn't work: https://localhost:5051/api/Users/"Example"
My problem is, on my mobile client, when it calls the web service that calls the API, it needs to parse a string, which goes with the quotes(edit)- matching the second example.
Does any of you know a solution or a workaround for this?
Thanks in advance
EDIT:
My service method is as follows
public static async Task<IEnumerable<User>> GetUserById(string id)
{
var json = await client.GetStringAsync($"api/users/{id}");
var users = JsonConvert.DeserializeObject<IEnumerable<User>>(json);
return users;
}
And my service call is
var users = await UserService.GetUserById("Example");
EDIT2: Fixed
Service method changed to
public static async Task<User> GetUserById(string id)
{
var json = await client.GetStringAsync($"api/users/{id}");
var users = JsonConvert.DeserializeObject<User>(json);
return users;
}
It turns out the issue was caused by the IEnumerable type on the task definition, which makes sense since I was trying to retrieve a single instance.
Service method changed to
public static async Task<User> GetUserById(string id)
{
var json = await client.GetStringAsync($"api/users/{id}");
var users = JsonConvert.DeserializeObject<User>(json);
return users;
}

ASP.NET MVC ObjectDisposedException when trying to pass id

What I am trying to do is create a function that will pass through the id of the user and then will load up a page that displays all the applications that a user has made to each driving instructor.
I have already made a function that will display all applications from the database regardless of who created the application. I have also made a function that allows users to create applications. I have also created a field in the application table named User_UserId so when an application is created and commited to a database, it will add the specific userId that created the application.
My question is, how would I make it so that when you clicked on show all applications on a specific users account, it would only pull up applications that have that User_UserId attached. Could I add UserId to the class that stores the input form and set the UserId in there to the UserId that is creating the application?
My attempts so far all end up with this error in the User Controller on line - IList<Application> applications = user.Applications.ToList();:
An exception of type 'System.ObjectDisposedException' occurred in EntityFramework.dll but was not handled in user code
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I have a function that allows me to view applications by Driving instructors and that works fine, it uses a similar method where it asks for the specific driving instructor in the create form. Then I have a function that lists the application by driving instructor.
Here is the code. Some main points to make are;
Primary key UserId in user class is set as a string
In the class that stores the input data, there is no UserId field as that is passed in the method.
The field in Applications table that stores the user Id is named User_UserId
Please inform me if there is any more pieces of code you require.
User Controller that opens the view for GetApplications:
public ActionResult GetApplications(string id)
{
User user = userService.GetUser(id);
IList<Application> applications = user.Applications.ToList();
return View(applications);
}
IDAO/DAO Files:
IList<Application> GetApplications(string id, XContext context);
public IList<Application> GetApplications(string id, XContext context)
{
User user = context.Users.Find(id);
return user.Applications.ToList();
}
IService/Service Files:
IList<Application> GetApplications(string id);
public IList<Application> GetApplications(string id)
{
using (var context = new XContext())
{
IList<Application> Applications;
Applications = userDAO.GetApplications(id, context);
return Applications;
}
}
HTML view for GetUser that leads to GetApplications, Model uses User class:
#Html.ActionLink("Show Applications", "GetApplications", "User") ||
Context class:
public class XContext : DbContext
{
public XContext() : base("XContext")
{
Database.SetInitializer(new XInitializer());
}
public DbSet<User> Users { get; set; }
public DbSet<Application> Applications { get; set; }
public DbSet<Instructor> Instructors { get; set; }
}
UserService GetUser function:
public User GetUser(string id)
{
using (var context = new XContext())
{
return userDAO.GetUser(id, context);
}
}
UserDAO GetUser function:
public User GetUser(string id, XContext context)
{
return context.Users.Find(id);
}
User Class:
using System.ComponentModel.DataAnnotations;
public class User
{
[Key]
public string UserId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public virtual ICollection<Application> Applications { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
Application Class:
public class Application
{
[Key]
public int ApplicationId { get; set; }
public string Level { get; set; }
public string Manual { get; set; }
public string InstructorName { get; set; }
}
I have set breakpoints on the controller function that breaks and here are the results:
User user = userService.GetUser(id);
id = "1"
user = null
IList<Application> applications = user.Applications.ToList();
id = "1"
applications = null
When I continue and get the error thrown, I am given
id = "1"
applications = null
user.Application = null
Your Application class is incorrect. It doesn't have UserId
public class Application
{
[Key]
public int ApplicationId { get; set; }
public string UserId { get; set; }
public string Level { get; set; }
public string Manual { get; set; }
public string InstructorName { get; set; }
}
And I think you have the same bug in your Instructor class
You have the bugs in your DAO.
Replace
public User GetUser(string id, XContext context)
{
return context.Users.Find(id);
}
with
public User GetUser(string id, XContext context)
{
return context.Users.Include(i=> i.Applications).FirstOrDefault(i=>i.UserId==id);
}
And replace
public IList<Application> GetApplications(string id, XContext context)
{
User user = context.Users.Find(id);
return user.Applications.ToList();
}
with
public IList<Application> GetApplications(string id, XContext context)
{
return context.Applications.Where(i=>i.UserId==id).ToList();
}
User service code:
public List<Application> GetApplications(string id)
{
using (var context = new XContext())
{
return userDAO.GetApplications(id, context);
}
}
Your controller code:
public ActionResult GetApplications(string id)
{
var applications = userService.GetApplications(id);
return View(applications);
}

Model validation, database constraints

I'm trying to add some architecture to my projects and enrich my models.
I started with CQS (implementation similar to that one: CQS-Sample) and here's my first problem.
Let's say I have two classes like these below:
public class Network
{
public int Id { get; set; }
public User User { get; set; }
private IQueryFactory _queryFactory { get; set; }
public Network(IQueryFactory queryFactory)
{
_queryFactory = queryFactory;
}
public void AddUser(User user)
{
if(this.User == null && user != null)
{
userHasUniqueEmail(user);
this.User = user;
}
}
private void userHasUniqueEmail(User user)
{
bool isUnique = _queryFactory.ResolveQuery<INewUserUniqueQuery>().Execute(user.Email);
if (!isUnique)
{
throw new ArgumentException("E-mail is not unique");
}
}
}
public class User
{
public int Id { get; set; }
public string Email { get; set; }
}
Network object can have User, but I need to check first in database that given e-mail doesn't already exists or do some other checkings, so my commands will be executed successfully.
By adding user I mean adding completely new User to database.
Would it be correct way to do this?
You can do it the way you do it now and it's ok.
Another option is to make this Validation in Contoller. Then you should use Remote attribute. And Move your IsEmailUnique(string mail) method to Controller.
If you want to know how you can do it with email check - this question will help you.

Categories

Resources