Do I really have to update every property individually with Entity Framework? - entity-framework-core

I have an update method in my Web API repository that looks like this (in EF Core):
public async Task<Employee> Update(Employee emp)
{
Employee employee = await
_context.Employees.SingleOrDefaultAsync(e => e.ID == emp.ID);
if (employee == null)
{
return null;
}
employee.FirstName = emp.FirstName;
employee.LastName = emp.LastName;
employee.Supervisor = emp.Supervisor;
employee.OfficeBureau = emp.OfficeBureau;
employee.Notes = emp.Notes;
await _context.SaveChangesAsync();
return employee;
}
It works well enough. But do we really have to do this?
I want to do something more like this and update all entity properties in one shot:
public async Task<Employee> Update(Employee emp)
{
Employee employee = await
_context.Employees.SingleOrDefaultAsync(e => e.ID == emp.ID);
if (employee == null)
{
return null;
}
employee = emp;
_context.Update(employee);
await _context.SaveChangesAsync();
return employee;
}
I shouldn't even need this:
employee = emp;
All I should need is this:
_context.Update(emp);
So EF should say, hey, I need to update an Employee object and I know which one it is by the ID of emp you passed me on update.
But I just can't get it to work.
Does anyone know how to do this or am I really supposed to do it like in the first option?
The answer below from Dmitry is not working.
If I put a break point here:
Employee employee = await
_context.Employees.SingleOrDefaultAsync(e => e.ID == emp.ID);
and then try and step though, execution seems to get swallowed up on this line:
_context.Entry(emp).State = EntityState.Modified;
and then this is the response returned:
{}
and the employee in the Database is unchanged.
Also tried this:
public async Task<Employee> Update(Employee emp)
{
Employee employee = await
_context.Employees.SingleOrDefaultAsync(e => e.ID == emp.ID);
if (employee == null)
{
return null;
}
EntityEntry<Employee> entity = _context.Employees.Attach(emp);
entity.State = EntityState.Modified;
_context.Employees.Update(emp);
await _context.SaveChangesAsync();
return employee;
}
But same thing.
Execution gets swallowed up here:
EntityEntry<Employee> entity = _context.Employees.Attach(emp);
Where is the execution going?
Hard to tell with async sometimes.
I got it to work once like this.
Funny, I got to work right off the bat when I put it in a try/catch.
public async Task<Employee> Update(Employee emp)
{
Employee employee = await
_context.Employees.SingleOrDefaultAsync(e => e.ID == emp.ID);
if (employee == null)
{
return null;
}
try
{
_context.Employees.Update(emp);
}
catch(Exception ex)
{
throw ex;
}
await _context.SaveChangesAsync();
return emp;
}
But it only worked once.
Now it keeps throwing this exception.
{System.InvalidOperationException: The instance of entity type 'Employee' cannot be tracked because another instance of this type with the same key
is already being tracked.
When adding new entities,
for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type).
If you are explicitly setting key values for new entities,
ensure they do not collide with existing entities or temporary values generated for other new entities.
"When attaching existing entities,
ensure that only one entity instance with a given key value is attached to the context.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph(EntityEntryGraphNode node, Func2 handleNode)
at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState) at Lerd.Models.Concrete.EmployeeRepository.d__4.MoveNext()}"
How do I get rid of this so dbContext.Update works everytime?

public async Task<Employee> Update(Employee emp)
{
_context.Entry(emp).State = EntityState.Modified;
await _context.SaveChangesAsync();
return employee;
}

I had to change my Repo to be Scoped rather than singleton:
From
services.AddSingleton<IEmployeeRepository, EmployeeRepository>();
to:
services.AddScoped<IEmployeeRepository, EmployeeRepository>();

Related

EFCore.BulkExtensions.BulkInsertAsync giving issues when I try create a Identity user

I am busy merging two projects and with that there are certain methods that I now have to use. When I am creating a new user it fails complaining about Primary key constrains. The weird thing is that the primary key is in a different table from the one that it is complaining about. So my thinking is that it has to do with EFCore.BulkExtensions.BulkInsertAsync(Third party tool by borisdj) and how it saves and UserManager.CreateAsync and how it saves.
This is the error that I am getting:
Innermost exception Microsoft.Data.SqlClient.SqlException :
Violation of PRIMARY KEY constraint 'PK_dbo.UserDetail'. Cannot insert
duplicate key in object 'dbo.UserDetail'.
I have a method with this if statement:
if (await CreateUser.CreateUserDetailAsync(model))
{
await CreateAccountUser(model.Email, model.Password, model.Role, model.Fullname);
return RedirectToAction("Index", "ShowUser");
}
I get the error in the CreateAccountUser user method but it complains about the table in CreateUser.CreateUserDetailAsync.
Th error complains about my UserDetail here is how this is saved. This part works because even with the error, the data get saved to the database.
private async Task<boolean> CreateUserDetailAsync(_Models.Settings.User user)
{
var userDetail = new UserDetail
{
Code = user.ERPCode,
Email = user.Email,
Name = user.Name,
Surname = user.Surname
};
//Create the
await UserDetail.BulkInsertAsync(new List<UserDetail>{userDetail});
return true;
}
This is my method for bulk inserts using EFCore.BulkExtensions.BulkInsertAsync:
public Task BulkInsertAsync(IEnumerable<T> entities)
{
if (entities == null)
throw new ArgumentNullException("entity");
foreach (var item in entities)
{
//Attempt to set the Create value of the entity
var created = typeof(T).GetProperty("Created");
if (created == null) created = typeof(T).GetProperty("CreatedOn");
created?.SetValue(item, DateTime.Now, null);
//Attempt to set the Modified value of the entity
var modified = typeof(T).GetProperty("Modified");
if (modified == null) modified = typeof(T).GetProperty("ModifiedOn");
modified?.SetValue(item, DateTime.Now, null);
Entities.Add(item);
}
return Throw(Db).BulkInsertAsync(entities.ToList());
}
I am including this code just for clarity:
private static DbContext Throw(IDataContext context) => context as DbContext;
Then when I get to this method, it fails on await UserManager.CreateAsync.
private async Task CreateAccountUser(string email, string password, string role,
string userFullName = null)
{
//Register User with identity
var accountUser = new Core.Data.Models.User();
accountUser.Email = accountUser.UserName = email;
var result = await UserManager.CreateAsync(accountUser, password);
if (result.Succeeded)
{
return true;
}
else
{
return true;
}
}
There are no linking between my User and UserDetail, they are completely separate.

What should my repository return on a http post when the posted id (foreign key) is wrong

Given the user sends a valid token to an api endpoint via fiddler/postman, he could post a resource (pupil) for a related resource (schoolclass).
When the schoolclass id
does not exist yet in the database
does exist already in the database but this schoolclass Id belongs to another user.
does exist in the database and belongs to the passed userId
Then
What would you change in the Controller and Repository class to make it work for all 3 cases using a REST api + repository pattern.
Controller:
[HttpPost("~/api/pupils")]
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
return Ok(pupil.Id);
}
Repository:
public async Task CreatePupil(Pupil pupil, int schoolclassCodeId, string userId)
{
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode != null)
{
schoolclassCode.Pupils.Add(pupil);
await context.SaveChangesAsync();
}
}
NOTE
At the moment the last of the 3 use cases is implemented!
From REST prospective you need to return 400 or 404 depending on your design.
If your route need to be like /classes/{id}/users/{id}/pupil I thing you need to use 404 in case user or class is wrong.
In case of separate route (as I can see in your question) I think this should be 400 code as request URL is pointing to valid resource but payload is invalid.
In both cases I think the batter error handling strategy here is to write some set of custom exceptions (like EntityNotFondException, EntityInvalidException, BusinessLogicException) and throw them from repository in case something is wrong. Then you can create some global action filter or OWIN middleware to catch those exceptions and translate them to correct response status codes with appropriate messages
Example:
public class NotFoundException : Exception
{
public NotFoundException(Type entityType)
: base($"Entity {entityType.Name} was not found")
{
}
}
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
public ApiExceptionFilterAttribute()
{
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
if (exception == null)
return;
if (exception is HttpResponseException)
return;
var entityNotFoundException = exception as NotFoundException;
if (entityNotFoundException != null)
{
actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, entityNotFoundException.Message);
return;
}
}
}
Usage:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if(schoolclassCode == null)
throw new NotFoundException(typeof(Schoolclass));
You can throw validation exceptions in the same way. E.g:
var schoolclassCode = await context.Schoolclasses.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId);
if(schoolclassCode == null)
throw new InvalidModelStateException("Schoolclass was not found.")
if(schoolclassCode.UserId != userId)
throw new InvalidModelStateException("Schoolclass is owned by different user.")
... etc.
I always use Result classes for returning state from a service class (wouldn't implement that in Repository as it shouldn't contain business logic):
public class QueryResult
{
private static readonly QueryResult success = new QueryResult { Succeeded = true };
private readonly List<QueryError> errors = new List<QueryError>();
public static QueryResult Success { get { return success; } }
public bool Succeeded { get; protected set; }
public IEnumerable<QueryError> Errors { get { return errors; } }
public static QueryResult Failed(params QueryError[] errors)
{
var result = new QueryResult { Succeeded = false };
if (errors != null)
{
result.errors.AddRange(errors);
}
return result;
}
}
public class QueryResult<T> : QueryResult where T : class
{
public T Result { get; protected set; }
public static QueryResult<T> Suceeded(T result)
{
if (result == null)
throw new ArgumentNullException(nameof(result));
var queryResult = new QueryResult<T>
{
Succeeded = true,
Result = result
};
return queryResult;
}
}
public class QueryError
{
public string ErrorId { get; set; }
public string ErrorMessage { get; set; }
}
And use it like
var schoolclassCode = await context.Schoolclasses
.SingleOrDefaultAsync(s => s.Id == schoolclassCodeId && s.UserId == userId);
if (schoolclassCode == null)
return QueryResult.Failed(new QueryError
{
ErrorId = 1,
ErrorMessage = "Invalid User Id"
});
Edit:
Just as an addition and rule of thumb
Services which operate on one or multiple entities and perform user input validation should return Result classes
Domain Models (which you don't seem to use, since you use a repository and Repository + Rich Domains doesn't work out well in real life applications) should throw exception (i.e. InvalidOperationException or ArgumentException, ArgumentNullException). Doing Result-types her will pollute the model and mix the separation of responsibility (Domain Model will suddenly also do validation instead only guarding against invalid state)
Using XxxResult type classes gives you an easy way to transport one or multiple errors back to the user, where an exception should act as an guard against your domain model getting into invalid state.
Edit 2
In response to the comments:
public async Task<IActionResult> Post([FromBody]CreatePupilRequestDto dto)
{
var userId = User.GetUserId();
var pupil = dto.ToPupil();
var result = await repository.CreatePupil(pupil, dto.SchoolclassId, userId);
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok(pupil.Id);
}
Edit 3
Example with 3 parameters for let's say /api/schoolclasses/1/students/2/lessons/2 (Update an existing lesson to the student with the id 2 for the school class with id 1).
// on SchoolClasses Controller
[HttpPost("{schoolClassId:int}/students/{studentId:int}/lessons/{lessonId:int}")]
public async Task<IActionResult> Post([FromBody]Lessons lessonDto)
{
// rough input validation, do first to avoid db hits
if(!ModelState.IsValid)
return BadRequest(ModelState);
// best put logic into service classes i.e. SchoolClassService
var result = schoolClassService.UpdateLessonFor(schoolClassId, studentId, lessonDto)
// If you want to suppress the error messages, just call return BadRequest() instead
if(!result.Succeeded)
return BadRequest(result.Errors);
return Ok();
}
Content of UpdateLessonsFor
List<ErrorMessage> errors = new List<ErrorMessage>();
// with .Include to include both student and all of his lessons
// does student exist?
// Hits db once and gets both, student and all lessons in a single query
var student = _context.SchoolClasses
.Include(sc => sc.Students)
.ThenInclude(s => s.Lessons)
.Where(sc => sc.SchoolClassId == schoolClassId)
.SelectMany(sc => sc.Students)
FirstOrDefault(s => s.StudentId == studentId);
if(student==null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 1, ErrorMessage = "Student or School Class not found" } );
// Doesn't hit the database, since lessons have been loaded with the above call
var lesson = student.Lessons.Any(l => l.LessonId = lessonId))
if(lesson == null)
return QueryResult.Failed( new ErrorMessage { ErrorId = 2, ErrorMessage = "Lesson not found. " } );
// modify it
lesson.SomeValue = dto.SomeValue;
try
{
} catch(Exception ex) {
return QueryResult.Failed(new ErrorMessage { ErrorId = 3, ErrorMessage = "Couldn't update the lesson. Try again and if the error appears again, contact the administrator." } );
} finally {
return QueryResult.Suceeded;
// or if you also want to return a result
return QueryResult.Suceeded(lesson);
}
Also from the comments of the other answer: Don't put logic into your repository, that's what services are for when you use anemic domain (models have no logic, all in services) or have thin service layer and put most logic into domain service. But that's out of the scope.

How to concatenate value in database column on update in Entity Framework

I am trying to add last name on update with first name; first name is already in column but I want on update first name+last name
This is my event handler code
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Record REC = new Record();
REC.ID = Convert.ToInt32(txtID.Text);
int id = REC.ID;
REC.Name= TXTNAME.Text;
String updatedname=REC.Name;
RecordFactory FAC = new RecordFactory();
if (FAC.Update1(id,updatedname))
{
MessageBox.Show("update");
}
else
MessageBox.Show("not");
}
}
This is connection code between handler and query
public bool Update(int id, String name)
{
return rd.Update(id, name);
}
This is query code
AS UPDATE call concatenate last name with first name
before update
public bool Update(int id ,String name)
{
Record REC = red.Records.Where(X => X.ID == id).FirstOrDefault();
if (REC != null)
REC.Name =String.Concat(REC.Name+name);
return red.SaveChanges() >0;
}
To update a record, you need to set the Entity state to modified.
public bool Update(int id ,String name)
{
Record rec= red.Records.Where(x => x.ID == id).FirstOrDefault();
if (rec!= null)
{
rec.Name =String.Concat(REC.Name+name);
db.Entry(rec).State = EntityState.Modified;
return red.SaveChanges() >0;
}
return false;
}
EntityState is defined in Microsoft.Data.Entity namespace. You need to import that namespace to your class.with using Microsoft.Data.Entity statement.

Update only scalar properties in Code First, EF 5

God day!
I have a tree of entities and at specific point of time i need to update only scalar properties of one entity. Classic update rise entire graph lookup, but relations not need to update.
The trouble in Category entity what one category have another categories in children. My method generate exceptions when saving changes about duplicate key. I think EF try to add children to database.
Static method of my data context listed below:
public static void Update<T>(T item) where T : KeyedObject
{
if (item == null)
throw new ArgumentNullException("Item to update is null");
item.ValidateIsNotNew();
using (DataContext db = new DataContext())
{
T original = GetOriginalWithException<T>(db, item);
DbEntityEntry entry = db.Entry(original);
entry.CurrentValues.SetValues(item);
entry.State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (Exception ex)
{
throw new DatabaseException(
"Cant update list item. See inner exception for details.",
ex);
}
}
}
I tries another method: attaching object. This method does not throw exception, but it rise entire graph update and take many resources. Code listed below:
public static void Update<T>(T item) where T : KeyedObject
{
if (item == null)
throw new ArgumentNullException("Item to update is null");
item.ValidateIsNotNew();
using (DataContext db = new DataContext())
{
db.Set<T>().Attach(item);
db.Entry(item).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (Exception ex)
{
throw new DatabaseException(
"Cant update list item. See inner exception for details.",
ex);
}
}
}

Inserting record instead of updating

I am developing an MVC app.
When I try to updated record it showing error of DBEntityValidation exception,
( beacuse its trying to add record in DB. This is my code)
public JsonResult SavePassword(int EmpId, string Password)
{
try
{
Employee e1 = db.Employees.First(i => i.Id == EmpId);
db.Entry(e1).State = EntityState.Modified;
e1.Password = Password;
db.SaveChanges();
return Json(EmpId);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
}
In exception, it shows that validation msgs, which I have checked while adding new record.
So , I think its trying to add in DB insted of updating.