Fixed: "DbUpdateException: Could not save changes. Please configure your entity type accordingly." with EF Core and MySQL
Four different mistakes, one message: Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly. A duplicate name, a negative price, a missing category and a blocked delete all produced it, through Oracle's MySQL provider for EF Core. Nothing was wrong with any entity type's configuration. The real reason sits one line lower, in the inner MySqlException, and its error number is what your code should handle.
What I Was Doing
In a .NET 10 Web API with EF Core 10, Oracle's MySql.EntityFrameworkCore 10.0.12 provider and MySQL 9.7, the database protects itself with constraints:
- a unique index on
categories.Name, so two categories can't share a name; - a CHECK constraint,
CK_products_UnitPrice, so a price can't be negative; - a foreign key from
products.CategoryIdtocategories.Id, withON DELETE RESTRICT, so a product can't point at a missing category, and a category with products can't be deleted.
The API's endpoints saved through EF Core, with no error handling yet. The one for products:
app.MapPost("/try/products", async (string sku, string name, int categoryId, decimal unitPrice, InventoryDbContext db) =>
{
var product = new Product
{
Sku = sku,
Name = name,
CategoryId = categoryId,
UnitPrice = unitPrice,
CreatedAtUtc = DateTime.UtcNow,
UpdatedAtUtc = DateTime.UtcNow,
};
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Ok(new { product.Id });
});
Then I sent these four requests:
curl -s -X POST "http://localhost:5190/try/categories?name=Chargers"
curl -s -X POST "http://localhost:5190/try/products?sku=CHG-USBC-20W&name=USB-C%20charger,%2020%20W&categoryId=5&unitPrice=-15"
curl -s -X POST "http://localhost:5190/try/products?sku=CHG-USBC-30W&name=USB-C%20charger,%2030%20W&categoryId=99&unitPrice=15"
curl -s -X DELETE "http://localhost:5190/try/categories/5"
A category named Chargers already existed, and it had products; there is no category 99.
What I Expected
For each request, a saved row or an error that says what was wrong with it.
What I Got
Four times the same first line (the stack traces are cut here):
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry 'Chargers' for key 'categories.IX_categories_Name'
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Check constraint 'CK_products_UnitPrice' is violated.
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Cannot add or update a child row: a foreign key constraint fails (`inventory`.`products`, CONSTRAINT `FK_products_categories_CategoryId` FOREIGN KEY (`CategoryId`) REFERENCES `categories` (`Id`) ON DELETE RESTRICT)
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Cannot delete or update a parent row: a foreign key constraint fails (`inventory`.`products`, CONSTRAINT `FK_products_categories_CategoryId` FOREIGN KEY (`CategoryId`) REFERENCES `categories` (`Id`) ON DELETE RESTRICT)

"Please configure your entity type accordingly" sent me to the entity configuration first, but none of these four had anything to do with it: the configuration was what created the constraints that worked.
What I Checked
The line with the arrow. In .NET's exception text, ---> introduces the inner exception, the one that caused the outer one. Each inner one is a MySqlException with MySQL's own message, and those four messages are four different problems. The API's log showed the same pair, from EF Core's Update category:
fail: Microsoft.EntityFrameworkCore.Update[10000]
An exception occurred in the database while saving changes for context type 'Dewiride.Inventory.Data.InventoryDbContext'.
Microsoft.EntityFrameworkCore.DbUpdateException: Could not save changes. Please configure your entity type accordingly.
---> MySql.Data.MySqlClient.MySqlException (0x80004005): Duplicate entry 'Chargers' for key 'categories.IX_categories_Name'
What DbUpdateException means. Its API reference says only: "An exception that is thrown when an error is encountered while saving to the database." EF Core's cascade delete page describes the wrapping: "SaveChanges then wraps the database exception in a DbUpdateException." So the outer exception says that saving failed; the inner one says why.
Where the misleading sentence comes from. EF Core's own message for this case is different. It's listed in the API reference as RelationalStrings.UpdateStoreException: "An error occurred while saving the entity changes. See the inner exception for details." I searched both assemblies for the two texts with strings, which prints the readable text inside a binary file:
strings ~/.nuget/packages/mysql.entityframeworkcore/10.0.12/lib/net10.0/MySql.EntityFrameworkCore.dll | grep 'Could not save changes'
strings ~/.nuget/packages/microsoft.entityframeworkcore.relational/10.0.14/lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll | grep 'See the inner exception for details'
FCould not save changes. Please configure your entity type accordingly.
WAn error occurred while saving the entity changes. See the inner exception for details.
(The F and W are length bytes that .NET stores in front of each resource string: 70 and 87 characters.) The first text is in Oracle's provider, which uses it instead of EF Core's. EF Core's version gives the right advice: see the inner exception.
Why It Happens
MySQL refused each statement, with an error number that identifies the reason; the MySQL connector raised it as a MySqlException; EF Core wrapped that in a DbUpdateException; and Oracle's provider gave the wrapper a generic message. The number is in the MySQL error reference:
| Inner message | Error number | Symbol in the reference |
|---|---|---|
Duplicate entry … for key … | 1062 | ER_DUP_ENTRY |
Check constraint … is violated. | 3819 | ER_CHECK_CONSTRAINT_VIOLATED |
Cannot add or update a child row: a foreign key constraint fails … | 1452 | ER_NO_REFERENCED_ROW_2 |
Cannot delete or update a parent row: a foreign key constraint fails … | 1451 | ER_ROW_IS_REFERENCED_2 |
The Fix
Catch DbUpdateException only for the MySQL errors you expect, and answer each with a clear message and the right status code. C#'s exception filter, catch (…) when (…), does exactly that: the catch block runs only when the condition is true, and every other error keeps flowing up as before.
db.Products.Add(product);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException ex) when (ex.InnerException is MySqlException { Number: 3819 })
{
// 3819: a CHECK constraint refused the row.
return Results.BadRequest(new { error = "Prices and quantities can't be negative." });
}
catch (DbUpdateException ex) when (ex.InnerException is MySqlException { Number: 1452 })
{
// 1452: the foreign key found no category with this Id.
return Results.BadRequest(new { error = $"There is no category with Id {categoryId}." });
}
return Results.Ok(new { product.Id });
ex.InnerException is MySqlException { Number: 3819 }is a pattern: it's true when the inner exception is aMySqlExceptionwhoseNumberis 3819.MySqlExceptionis inusing MySql.Data.MySqlClient;.- Match the number, not the message. The message contains values and names that change from row to row; the number is the error's identity.
- Write your own message. MySQL's text names tables, constraints and values. That's useful in your logs, but it's a description of your schema that API clients don't need.
The other two endpoints follow the same pattern:
// POST /try/categories
catch (DbUpdateException ex) when (ex.InnerException is MySqlException { Number: 1062 })
{
// 1062: a unique index refused a duplicate value.
return Results.Conflict(new { error = $"A category named '{name}' already exists." });
}
// DELETE /try/categories/{id}
catch (DbUpdateException ex) when (ex.InnerException is MySqlException { Number: 1451 })
{
// 1451: products still point at this category.
return Results.Conflict(new { error = "This category still has products. Move or archive them first." });
}
A duplicate and a blocked delete are conflicts with what's already stored, so they get 409 Conflict; a negative price and an unknown category are bad input, so they get 400 Bad Request.
After the Fix
I ran the same kinds of request against the handled endpoints. The database held different rows by then: two categories, Desks with Id 1 and Shelves with Id 2. The first request gives Desks a product; -i prints the status line and headers:
curl -s -i -X POST "http://localhost:5190/try/products?sku=DSK-STD-01&name=Standing%20desk&categoryId=1&unitPrice=349"
curl -s -i -X POST "http://localhost:5190/try/categories?name=desks"
curl -s -i -X POST "http://localhost:5190/try/products?sku=DSK-STD-02&name=Desk%20mat&categoryId=1&unitPrice=-5"
curl -s -i -X POST "http://localhost:5190/try/products?sku=DSK-STD-03&name=Desk%20lamp&categoryId=99&unitPrice=25"
curl -s -i -X DELETE "http://localhost:5190/try/categories/1"
curl -s -i -X DELETE "http://localhost:5190/try/categories/2"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1}
HTTP/1.1 409 Conflict
Content-Type: application/json; charset=utf-8
{"error":"A category named 'desks' already exists."}
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{"error":"Prices and quantities can't be negative."}
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{"error":"There is no category with Id 99."}
HTTP/1.1 409 Conflict
Content-Type: application/json; charset=utf-8
{"error":"This category still has products. Move or archive them first."}
HTTP/1.1 204 No Content

Each mistake now gets its own answer, a valid product is still saved, and deleting Shelves, which has no products, still works. desks was a duplicate of Desks because the column's collation ignores case; the 1062 post covers that.
Common Mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| Changing the entity configuration because the message says so | Nothing improves; the cause is in the data | Read the inner exception |
catch (DbUpdateException) with no filter | Unexpected errors are hidden behind a friendly message | Filter on the error numbers you expect |
Matching ex.InnerException.Message.Contains("Duplicate") | Breaks when the wording or language changes | Match MySqlException.Number |
| Returning MySQL's message to the client | It exposes table, index and constraint names | Return your own message; keep MySQL's for the logs |
| Checking for duplicates with a query before saving, without a unique index | Two requests at the same moment both pass the check | Keep the index, and handle 1062 |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
DbUpdateException: Could not save changes. Please configure your entity type accordingly. | MySQL refused the statement; Oracle's provider uses this text for every failed save | Read the inner MySqlException and its Number |
The inner message starts with Duplicate entry | A unique index refused a duplicate value (1062) | Return 409 Conflict, or update the existing row |
The inner message is Check constraint … is violated. | A CHECK constraint refused the row (3819) | Validate the input, and return 400 Bad Request |
The inner message starts with Cannot add or update a child row | The foreign key found no parent row (1452) | Check that the Id exists, and return 400 Bad Request |
The inner message starts with Cannot delete or update a parent row | Child rows still point at the row (1451) | Move or archive the children first, and return 409 Conflict |
FAQ
Is something wrong with my entity configuration?
Not for these errors. The configuration created the index, the CHECK constraint and the foreign key that refused the data; they worked. Look at the inner exception's message and number.
Should I validate input before saving instead?
Both. Validation gives better messages for the common cases, such as a negative price, before anything reaches MySQL. The constraints and these catches cover what validation can't see, such as a duplicate created by another request a millisecond earlier.
Where do I find the number for another error?
In the inner exception's message and in the MySQL server error reference, which lists every error as "Error number: …; Symbol: …; SQLSTATE: …".
Conclusion
"Could not save changes. Please configure your entity type accordingly." is the generic message that Oracle's MySQL provider puts on every DbUpdateException; EF Core's own wording, "See the inner exception for details", is the better advice. The inner MySqlException carries MySQL's real message and an error number. Catch DbUpdateException with a when filter on MySqlException.Number for the errors your constraints produce, 1062, 3819, 1452 and 1451 here, and answer each with your own message and the right status code.
Additional Resources
- DbUpdateException Class — EF Core API reference.
- Cascade Delete — when EF Core wraps database errors in
DbUpdateException. - MySQL 9.7 Server Error Message Reference
Related
- Fixed: "ERROR 1062 (23000): Duplicate entry 'adapters' for key 'categories.IX_categories_Name'" in MySQL
- Fixed: "ERROR 3819 (HY000): Check constraint 'CK_products_UnitPrice' is violated." in MySQL
- Fixed: "ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails" in MySQL
- Fixed: "ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails" in MySQL
