I'm developing an API on .Net Core 6 and i'm having a little confusing about all the requests im doing to the database.
For example i have a Company class and Employee Class
public class Company
{
public int Id { get; set; }
public string name { get; set; }
public List<Employees> employees { get; set; }
public User CreatedBy { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Company Company { get; set; }
public bool IsAdmin { get; set; }
public User user { get; set; } // This is the IdentityUser
}
I have this JSON object i send to api to add an employe
{
"name": "First Last",
"userId": "1E715D09-D1A4-4853-960B-08D9DED34E35",
"isAdmin": false
}
A user can add a company and immediately is inserted as an employee (Admin employee) because he created that Company. Another thing is that, to be able to add another employee, the Employee (User logged in) needs to be an admin on that company.
I have this endpoint for adding employees: api/company/{companyId}/employees -> POST
and this is the logic i have to add the employee
// Verifies that the company sent is a valid one
var company = await _repository.Company.GetCompany(companyId, trackChanges);
if (company == null)
throw new NotFoundException("Company could not be found");
// Verifies that the user (getting the userId from the JWT) logged in is an employee in that company so he can add a new employee
var userEmployee = await _repository.Employee.GetEmployeeByUserId(userId, companyId, trackChanges);
if (userEmployee == null)
throw new NotFoundException("Employee was not found");
// Validates the user object fetched that he's an admin
if (userEmployee.IsAdmin)
{
// Validates that the sent user Id in the DTO is valid to add
var userToAdd = GetUserById(employeeCreationDto.userId, trackChanges);
if (userToAdd == null)
throw new NotFoundException("User trying to be added as emmployee is not found");
// Verify that the userId from the DTO doesn't exists in the DB already
var userToAddAsEmployee = await _repository.Employee.GetEmployeeByUserId(employeeCreationDto.userId, companyId, trackChanges);
if (userToAddAsEmployee != null)
throw new ConflictException("User is already an employee in this company");
//Proceed to add employee logic
}
Now my question is, should i do all of this Database calls to validate every single step or should i try to add and if a foreign key error returns from the db just handle that error as response
I just think i am doing to many calls and almost all of my endpoints have lot's of validations calling the db. Is it okay all of those calls?
Related
I have the following Models:
public interface Item
{
public int Id { get; set; }
}
public class ComponentOwner : Item
{
public int Id { get; set; }
public Component Component { get; set; }
public AppUser? User { get; set; }
public DateTime ModifiedDate { get; set; }
public AppUser? UpdatedBy { get; set; }
}
public class AppUser : IdentityUser
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
}
and the following async Task that saves the item to the database:
private async Task<Item> SaveItem(Item item)
{
Item updatedItem = null;
using var context = _dbContextFactory.CreateDbContext();
try
{
if (item.Id == 0)
{
context.Add(item);
await context.SaveChangesAsync();
}
When I Save a new ComponentOwner, context.Add(Item) adds the item, but also tries to add a new 'AppUser' at the same time. This causes issues because the AppUser is already created.
Is there a way that I can specify to add the ComponentOwner but not the AppUser?
as soon as 'Context.Add(item)' is hit, it wants to add an AppUser as well as the Component. I only want it to add the ComponentOwner however..
EF Core relies on tracking to determine what to do with entities. In this case it seems that item.User is not tracked, so EF tries to add it. There are multiple possible solution to this. To name a few:
If you are sure that user exists, you can just attach the entity:
if(item.User is not null)
context.Users.Attach(item.User); // or just context.Attach(item.User);
Fetch user from database and assign it to the root entity:
if (item.User is not null)
{
var user = context.Users.FirstOrDefault(u => u.Id == item.User.Id); // TODO: handle null
item.User = user;
}
Use Find:
Finds an entity with the given primary key values. If an entity with the given primary key values is being tracked by the context, then it is returned immediately without making a request to the database. Otherwise, a query is made to the database for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found, then null is returned.
if (item.User is not null)
{
var user = context.Users.Find(item.User.Id); // TODO: handle null
item.User = user;
}
Since adding logging functionality to my Entity Framework project, I have been unable to delete any records from the table.
Here are the data classes for the objects which are added to the database, the user to the user table and the log to the logs table:
public class User
{
public string UserName { get; set; }
[Key]
public string ApiKey { get; set; } //unique database key and API key for user
public ICollection<Log> Logs { get; set; }
public User() { }
}
public class Log
{
[Key]
public int logID { get; set; }
public string logString { get; set; }
public string logDateTime { get; set; }
public string userAPIKey { get; set; }
public Log() { }
}
Here is how logs are added to the table, as since adding logging I've been having the issue:
public void addLogToUserWithApiKey(string logMessage, string apiKey)
{
Log newLog = new Log();
newLog.logID = makeLogID();
newLog.logString = logMessage;
newLog.logDateTime = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToString("h:mm:ss tt");
newLog.userAPIKey = apiKey;
using (var context = new UserContext())
{
User logUser = checkIfUserExistsWithApiKeyandReturnUser(apiKey);
if (logUser.Logs == null)
{
logUser.Logs = new Collection<Log>();
}
logUser.Logs.Add(newLog);
context.Logs.Add(newLog);
context.SaveChanges();
}
}
And finally, this is the code to delete a record:
public void deleteUserFromDatabase(string mApiKey)
{
using (var context = new UserContext())
{
try
{
User userToDelete = checkIfUserExistsWithApiKeyandReturnUser(mApiKey);
if (userToDelete != null)
{
context.Users.Attach(userToDelete);
context.Users.Remove(userToDelete);
context.SaveChanges();
}
}
catch (Exception e) { }
}
}
There were no exceptions being called when the delete method was like that however it still wasn't working.
I changed the delete method to this:
User userToDelete = checkIfUserExistsWithApiKeyandReturnUser(mApiKey);
if (userToDelete != null)
{
if (userToDelete.Logs != null)
{
userToDelete.Logs.ToList().ForEach(log => userToDelete.Logs.Remove(log));
}
context.Users.Attach(userToDelete);
context.Users.Remove(userToDelete);
context.SaveChanges();
}
And I got this error message:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.Logs_dbo.Users_User_ApiKey". The conflict occurred in database "SecuroteckWebApplication.Models.UserContext", table "dbo.Logs", column 'userAPIKey'. The statement has been terminated.
If you want to delete a User, you would have to delete all Log entries associated with that user first.
var apiKey = String.Empty; // The id of the user you want to delete
using (var context = new UserContext())
{
User userToDelete = checkIfUserExistsWithApiKeyandReturnUser(apiKey);
if (userToDelete != null)
{
var userLogs = context.Logs.Where(l => l.userAPIKey == apiKey);
if (userLogs.Any())
{
context.Logs.RemoveRange(userLogs);
}
context.Users.Attach(userToDelete);
context.Users.Remove(userToDelete);
context.SaveChanges();
}
}
This error is returned from SQL Server. As it says, you cannot delete the User because there are records from the dbo.Logs table which are related to the deleted user and there is a foreign key defined linking the userAPIKey column with this deleted User.
Seeing your entity code, I can't tell why the foreign key was created in the first place, if you are using Entity Framework Code First. If this is your case, probably you are falling in an Entity Framework convention rule.
Anyways, there are some ways to solve this.
If you are using EF Code First. Delete all the logs pointing to the deleted user or update them setting them to NULL, depending on how much you need to preserve the logs for a deleted user.
EDIT: As the OP is using Code First, then the relationship between Log and User is not completely defined. This is the proper entity code if a strong relationship is what is intended by the OP.
public class User
{
public string UserName { get; set; }
[Key]
public string ApiKey { get; set; } //unique database key and API key for user
[InverseProperty("User")]
public virtual ICollection<Log> Logs { get; set; }
public User() { }
}
public class Log
{
[Key]
public int logID { get; set; }
public string logString { get; set; }
public string logDateTime { get; set; }
public string userAPIKey { get; set; }
[ForeignKey("userAPIKey")
public virtual User User {get; set; }
public Log() { }
}
With the strong relationship, logs should be deleted or set to null before being able to the delete the user if cascade conventions are not configured.
I am creating an employee scheduling site in ASP.net MVC 6. I have an employee table, shift table and a shiftEmployee table to handle the many to many relationship.
It's configured so that each employee logs into the site using their employee ID number and a password. Then they can see each future shift they are scheduled to. They must acknowledge each assigned shift in a process known as "pulling their pin".
So far everything is working as expected. My goal and my question is this:
When an employee pulls their pin for each shift, I would like them to have to confirm this action by entering their password again, keeping in mind the user is already signed into the site. What is the easiest/correct/most secure way to accomplish this?
The Pull GET/POST methods are basically the same as a standard MVC edit action, simply renamed Pull.
// GET: PullPin/Pull/5
public IActionResult Pull(int? id)
{
if (id == null)
{
return HttpNotFound();
}
var shiftEmp = _context.ShiftEmployees.Single(m => m.ShiftEmployeeID == id);
if (shiftEmployee == null)
{
return HttpNotFound();
}
}
// POST: PullPin/Pull/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Pull(ShiftEmployee shiftEmployee)
{
var user = GetCurrentUserAsync();
pullPin.PinStatusID = 3; // Status ID #3 = Pulled
if (ModelState.IsValid)
{
_context.Update(shiftEmployee);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(shiftEmployee);
}
And here is my ShiftEmployee class
public class ShiftEmployee
{
public int ShiftEmployeeID { get; set; }
public int ShiftID { get; set; }
public int EmployeeID { get; set; }
public int PinStatusID { get; set; }
public virtual Shift Shift { get; set; }
[JsonIgnore]
public virtual Employee Employee { get; set; }
public virtual PinStatus PinStatus { get; set; }
}
In the standard MVC6 template, it uses ASP.NET Core Identity for the login functionality. Part of that package is the UserManager object (you also get a SignInManager among other things.)
The UserManager object has a method specifically for checking passwords called CheckPasswordAsync and is used like this:
_userManager.CheckPasswordAsync(user, password)
I'm wondering what the best practice for modelling by using references would be given situation under. I'm using MongoRepository library.
public class User : Entity
{
publis string Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
public class Post : Entity
{
public string Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public DateTime Added { get; set; }
public User Owner { get; set; }
}
When storing the Post I want only reference to Owner (User) object instead of whole object underlying.
Currently I'm doing it like this, not knowing of better way...
var post = new Post
{
Title = "Example title",
Summary = "asd asd",
Added = DateTime.Now,
Owner = new User { Id = "someExistingUserId" }
};
postRepository.Update(post); //Save
..
//Then to get the post
var post = postRepository.GetById("previouslySavedPostId");
post.Owner = userRepository.GetById(post.Owner.Id);
return post;
userRepository and postRepository are of MongoRepository type.
Is this the correct approach to solving my problem using MongoDB with C#/MVC(4)?
You can use MongoDBRef object instead of User object.
public class Post : Entity
{
public string Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public DateTime Added { get; set; }
public MongoDBRef Owner { get; set; }
}
Then you can:
var mongo = new Mongo(config.BuildConfiguration());
mongo.Connect();
var DB = mongo.GetDatabase(_dataBaseName)
var post = new Post();
post.Owner = new MongoDBRef("User", userId); // First parameter is a mongoDB collection name and second is object id
// To fetch object referenced by DBRef you should do following
var owner = DB.FollowReference<User>(post.Owner);
Mongo is a document database and if you are used to using sql server it requires a slightly different way of thinking.
As you don't want the user password details in every single post, the way i would probably do it is to create a new class to contain any user info that might be required to display a post.
public class PostOwnerInfo
{
public string UserId { get; set; }
public string Name { get; set; }
}
Update your post entity, replacing the Owner property with an OwnerInfo property, of type PostOwnerInfo.
Then when you create a new post, do the following.
var user = userRepository.GetById(someExistingUserId);
var post = new Post
{
Title = "Example title",
Summary = "Example summary",
Added = DateTime.Now,
OwnerInfo = new PostOwnerInfo
{
UserId = user.Id,
Name = user.Name
}
};
postRepository.Update(post);
This way when you query for a post it will have all the user info that you require to display the post in it's OwnerInfo property with no further queries required.
var post = postRepository.GetById(previouslySavedPostId);
// post.OwnerInfo will contain user info
There is a certain amount of data redundancy, but in an document database this is how i would do it.
If you need the full user info for any reason just do a seperate query for it as you were doing before.
The idea is that you store all the user info you need for a post in a child document of the post, so you shouldn't need to do a seperate query for the user.
If the user data chages, just update the UserInfo field on all posts made by your user.
Your user data will rarely change, but you will query for posts very often.
I am pretty new to c#, EF Code First and all that, so my question might be an easy one.
I am trying to create a very simple login. Each user must have a type (admin or client). How can I bind the usertype to my user table without generating a new type each time I insert a new user in the DB?
Here are my code first class:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool Active { get; set; }
public UserType TypeId { get; set; }
}
public class UserType
{
public int Id { get; set; }
public string Type { get; set; }
}
public enum TypeEnum
{
Admin,
Client
}
When I launch the app, I want to be able to create 2 tables:
Users which is empty
UserTypes which must contain 2 types (Admin and Client).
Then, everytime I register a new user, so everytime I add a user to the Users table, I want to be able to use the UserTypes table.
When I launch the app, I want to be able to create 2 tables...
Not sure if I understand this correctly but you can seed initial data into the database with EF Code-First. An example how to do that is here:
Entity Framework Inserting Initial Data On Rebuild
If you really want to recreate the tables with every launch of your application you can use the DropCreateDatabaseAlways<T> initializer as mentioned in the example.
Then, everytime I register a new user, so everytime I add a user to
the Users table, I want to be able to use the UserTypes table.
For this you would load the existing UserType you want to assign from the database so that it is attached to the context and then create the new user:
using (var context = new MyContext())
{
var userType = context.UserTypes
.Single(u => u.Type == TypeEnum.Admin.ToString());
var newUser = new User { TypeId = userType, Username = ... etc. };
context.Users.Add(newUser);
context.SaveChanges();
}
Attaching to the context - by either loading from the database or calling Attach explicitely - makes sure that the UserType is not duplicated, i.e. no INSERT command will be sent to the database for the UserType.
With new version of EF (currently beyond the offical 4.1 version: Entity Framework June 2011 CTP) you can also do that:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool Active { get; set; }
public UserType Type { get; set; }
}
public enum UserType
{
Admin = 1,
Client = 2,
}
The data will be saved as integer in your database but within your application you can use your enum like this:
var newUser = new User { Type = UserType.Admin, Username = ... };
context.Users.Add(newUser);
context.SaveChanges();