How to handle System.Data.Entity.Validation.DbEntityValidationException - entity-framework

I wrote this simple code to update my database column.
using (HRMSEntities context = new HRMSEntities())
{
TBL_EMPLOYEE dataTicketInsert = new TBL_EMPLOYEE();
dataTicketInsert = context.TBL_EMPLOYEE.Where(x => x.Id == inputEmployeeID).FirstOrDefault();
dataTicketInsert.Ticket = ticketT;
context.SaveChanges();
}
Error Message:
An exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
How can I resolve the problem?

Add the following code to your DbContext class, then in the validation error message, you will be able to see the details of the validation problem:
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Reference: https://stackoverflow.com/a/15820506/1845408

Related

Transaction already exists error

I'm using EF Core and Devart's data provider library. I've hit an issue I can't figure out with handling user input errors smoothly. The error seems to be limited to adding a new entity to the context.
Scenario
User inputs an invalid value in a field.
Save changes is called and throws then displays error.
Prompt user to fix the error.
After this if the error is fixed and save is called again (this is good data now), I get an exception "Transaction already exists" from the Devart data provider library.
StackTrace
at Devart.Data.Oracle.OracleConnection.BeginTransaction(IsolationLevel il)
at Devart.Data.Oracle.OracleConnection.BeginDbTransaction(IsolationLevel isolationLevel)
at System.Data.Common.DbConnection.BeginTransaction(IsolationLevel isolationLevel)
at  .BeginDbTransaction(IsolationLevel )
at System.Data.Common.DbConnection.BeginTransaction(IsolationLevel isolationLevel)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransactionWithNoPreconditions(IsolationLevel isolationLevel)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransaction(IsolationLevel isolationLevel)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransaction()
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.BeginTransaction()
at
I tried to break out the transaction and handle it manually MSDN Transactions but I still get the same error.
public bool SaveAllChanges()
{
var result = false;
using (var transaction = _context.Database.BeginTransaction())
{
try
{
_context.Database.AutoTransactionsEnabled = false;
_context.SaveChanges(true);
transaction.Commit();
result = true;
}
catch (Exception exc)
{
InvokeError(exc, "Error saving changes.");
result = false;
}
}
_context.Database.AutoTransactionsEnabled = true;
_context.Database.CloseConnection();
return result;
}
How do I recover from a db error without scrapping all of the user's input? I would hate for that to be practice. I could be validating all the data going in but recovering from simple errors would be better.
After fussing around with this I found the magic sauce. This type of error only seems to come up when adding an object to the DB. It's as if the context doesn't dispose of the transaction on fail.
public bool SaveAllChanges()
{
var result = false;
_context.Database.AutoTransactionsEnabled = false;
using (var transaction = _context.Database.BeginTransaction())
{
try
{
_context.SaveChanges(true);
transaction.Commit();
result = true;
}
catch (Exception exc)
{
transaction.Rollback(); <-------- Here.
InvokeError(exc, "Error saving changes.");
result = false;
}
}
_context.Database.AutoTransactionsEnabled = true;
_context.Database.CloseConnection();
return result;
}
If someone has a solution to where I don't need to handle the transaction in this way please post it.
We cannot reproduce the "Transaction already exists" exception with the following code:
using (var _context = new MyContext())
{
var entity = new MyEntity() { ID = 10, Name = "entry exceeds max length of the field" };
_context.MyEntities.Add(entity);
try
{
_context.SaveChanges(true); // error
}
catch (Exception ex)
{
//InvokeError(exc, "Error saving changes.");
}
entity.Name = "correct input";
_context.SaveChanges(); // success
}
Please localize the issue in a small application and send us this project for reproducing.

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.

Crm plugin update fails

I have created two new fields named "Price" for quote and quote product and I want to update the second every time I update the first.
Here is my code:
protected void ExecutePostAccountUpdateContacts(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
string oldPrice = "";
string newPrice = "";
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
var ServiceContext = new OrganizationServiceContext(service);
ITracingService tracingService = localContext.TracingService;
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
// get the post entity image
Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
if (preImageEntity.Attributes.Contains("Price"))
{
oldPrice = (string)preImageEntity.Attributes["Price"];
}
if (postImageEntity.Attributes.Contains("Price"))
{
newPrice = (string)postImageEntity.Attributes["Price"];
}
if (newPrice != oldPrice)
{
try
{
//Create query to get the related contacts
var res = from c in ServiceContext.CreateQuery("Products")
where c["parentQuoteid"].Equals(entity.Id)
select c;
foreach (var c in res)
{
Entity e = (Entity)c;
e["Price"] = newPrice;
ServiceContext.UpdateObject(e);
}
ServiceContext.SaveChanges();
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}
Although you haven't asked a question, your query isn't quite right. So I am assuming your plugin fails when querying for product with a parentquoteid.
Not all linq operators are implemented, also , pass the entity logical name to the create query as a parameter, so instead of Products, just product. There is no out of the box field called parentquoteid, are you missing your custom attribute prefix?
var res = from c in ServiceContext.CreateQuery("product")
where c.GetAttributeValue<Guid>("new_parentquoteid") == entity.Id
select c;

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.

Dynamics CRM 4.0 plugin fails when triggered by API

I have a plugin registered for when an account is created or updated, this is registered for the post stage.
The plugin works fine when a user creates or updates an account through the CRM interface, however when an account is created uging the API the plugin fails with the ever helpful 'server was unable to process the request' message. if an account is updated through the api the plugin also works correctly.
anyone have any ideas why?
UPDATE:
here is the create code
account = new CrmService.account();
account.ownerid = new CrmService.Owner();
account.ownerid.Value = new Guid("37087BC2-F2F0-DC11-A856-001E0B617486");
account.ownerid.type = CrmService.EntityName.systemuser.ToString();
account.name = model.CompanyName;
account.address1_line1 = model.Address1;
account.address1_line2 = model.Address2;
account.address1_stateorprovince = model.County;
account.address1_country = model.Country;
account.address1_city = model.TownCity;
account.address1_postalcode = model.PostCode;
account.new_companytype = new CrmService.Picklist();
switch (model.SmeType)
{
case SmeType.Micro:
account.new_companytype.Value = 1;
break;
case SmeType.Small:
account.new_companytype.Value = 2;
break;
case SmeType.Medium:
account.new_companytype.Value = 3;
break;
default:
break;
}
account.new_balancesheettotal = new CrmService.CrmMoney();
account.new_balancesheettotal.Value = preQualModel.BalanceSheetGBP;
account.revenue = new CrmService.CrmMoney();
account.revenue.Value = preQualModel.SalesTurnoverGBP;
if (model.Website != null)
{
account.websiteurl = model.Website.ToString();
}
account.numberofemployees = new CrmService.CrmNumber();
account.numberofemployees.Value = (int)preQualModel.NumEmployees;
accountGuid = svc.Create(account);
account.accountid = new CrmService.Key();
account.accountid.Value = accountGuid;
Here is the plugin code:
public void Execute(IPluginExecutionContext context)
{
DynamicEntity entity = null;
// Check if the InputParameters property bag contains a target
// of the current operation and that target is of type DynamicEntity.
if (context.InputParameters.Properties.Contains(ParameterName.Target) &&
context.InputParameters.Properties[ParameterName.Target] is DynamicEntity)
{
// Obtain the target business entity from the input parmameters.
entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];
// TODO Test for an entity type and message supported by your plug-in.
if (entity.Name != EntityName.account.ToString()) { return; }
// if (context.MessageName != MessageName.Create.ToString()) { return; }
}
else
{
return;
}
if (entity!=null && !entity.Properties.Contains("address1_postalcode"))
{
return;
}
if (context.Depth > 2)
{
return;
}
try
{
// Create a Microsoft Dynamics CRM Web service proxy.
// TODO Uncomment or comment out the appropriate statement.
// For a plug-in running in the child pipeline, use this statement.
// CrmService crmService = CreateCrmService(context, true);
// For a plug-in running in the parent pipeline, use this statement.
ICrmService crmService = context.CreateCrmService(true);
#region get erdf area from database
string postCode = entity.Properties["address1_postalcode"].ToString();
postCode = postCode.Replace(" ", ""); //remove spaces, db stores pcodes with no spaces, users usually enter them, e.g b4 7xg -> b47xg
string erdfArea = "";
SqlConnection myConnection = new SqlConnection(#"REDACTED");
try
{
myConnection.Open();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
try
{
SqlDataReader myReader = null;
SqlCommand myCommand = new SqlCommand("select ErdfAreaType from dim.Locality WHERE PostCode = '" + postCode+"'",
myConnection);
myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
erdfArea = myReader["ErdfAreaType"].ToString();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
try
{
myConnection.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
#endregion
entity.Properties["new_erdfarea"] = erdfArea;
crmService.Update(entity);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
throw new InvalidPluginExecutionException(
String.Format("An error occurred in the {0} plug-in.",
this.GetType().ToString()),
ex);
}
}
Sometimes it can be difficult to see the actual source of an error in plugins. In moments like this tracing is your friend. You can use this tool to enable tracing. When you have the trace files, try to search them for the error you got in your exception. This should tell you more details about what is failing.
Turns out this was due to me expecting data that was not there due to some weird behaviour in CRM.
I was taking the dynamicEntity passed to the plugin like so
entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];
But this was missing critical things like accountid. fixed it by using the PostEntityImage entity instead, which had all of the expected data like so
entity = (DynamicEntity)context.PostEntityImages[ParameterName.Target];