I have a typical scenario where users enter data that is inserted into a SQL database using Entity Framework 6.0. However, some rows that are part of the entity need to be unique (already enforced with unique key constraints in the database).
To avoid possible concurrency or performance issues I favour these checks to be left done by SQL Server.
When attempting to save a new entity that holds a duplicate row, a DbUpdateException is thrown by Entity Framework. The inner exception is a SqlException with its Number equal to 2627, and a message that reads:
"Violation of UNIQUE KEY constraint 'UK_MyTable_MyRule'. Cannot insert duplicate key in object 'dbo.MyTable'".
Considering that there are several tables involved, which may each have their own unique constraints defined, is there no better way to conclude a friendlier message to the user that reads:
"A MyEntity with the name 'MyEntity1' already exists."
...without having to infer this through the Number and Message properties from the SqlException?
For example:
try
{
...
context.SaveChanges();
}
catch (DbUpdateException exception)
{
var sqlException = exception.InnerException as SqlException;
bool isDuplicateInMyTable3 =
sqlException != null &&
sqlException.Number = 2627/*Unique Constraint Violation*/ &&
sqlException.Message.Contains("'UK_MyTable3_");
if (isDuplicateInMyTable3)
{
return "A MyTable3 with " + ... + " already exists.";
}
throw exception;
}
Is there a "cleaner" way to achieve the same that does not involve looking through the error message string?
You may like to enjoy the AddOrUpdate method.
Research it first. I have noted experts warning of over zealous use.
Context.Set<TPoco>().AddOrUpdate(poco);
can still throw other EF\DB exceptions.
But Duplicate primary key should not be one of them.
Other constraint issues are as before.
Related
I have the following scenario:
A business logic function that uses EF Core2 checks if a record already exists. If the record does not exists, it is inserted on the database.
I have multiple threads calling this business logic function. What happens is:
If the function is called simultaneous with the same parameters, both instances checks if the record exists - and it does not exists. So both instances inserts the same record.
When context.SaveChanges() is called on the first instance, all goes ok. But the second SaveChanges() throws an exception because the record already exists (there is an unique index on the database).
If i catch that exception and try to insert with new value for UNIQUE_KEY, still it's throwing an exception since previously added entity still in track.
How can I implement this to avoid the exception?
Instead of simply adding a new entity inside your catch-block, you could modify the existing entity that caused the exception.
foreach (var entry in _dbContext.ChangeTracker.Entries<YourEntity>().Where(e => e.State == EntityState.Added)))
{
entry.Entity.YourProperty = newvalue;
}
Just iterate through all your entities of given type and update your property that has a unique constraint.
I'm using Hibernate 5.2 with Postgre 9.5.
My DB schema has several constraints. Is there any "easy" possibility to expose constraint name to end user? Imagine I have unique constraint
ALTER TABLE JobTable
ADD CONSTRAINT JobTableJobStatusEnum
CHECK (jobStatus IN ('JobSubmitted', 'JobRunning', 'JobFailed', 'JobKilled', 'JobSucceeded'));
I want to re-throw something like
ConstraintViolationException("Can't execute operation. Your query breaks JobTableJobStatusEnum")
Yeah, It won't make any sense to business users, but other developers would understand the problem. I can grab constraint def from DB and put it into exception message also.
Basically you have constraints information in exception - stacktrace.
If you need to wrap original exception and add something custom (it's not so custom it's just change exception message), you can catch ConstraintViolationException (or exception and check that it's caused by ConstraintViolationException with apache commons-lang ExceptionUtils util)
catch(ConstraintViolationException e){
Strign message = e.getCause().getMessage();
.....
work with message
String detailMessage = String.format("Can't execute operation. Your query breaks %" , message);
throw new ConstraintViolationException(detailMessage , e);
}
For mysql (for Postgre should be something similar) it looks like
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Duplicate entry 'testemail#gmail.com' for key
'UK_n7ihswpy07ci568w34q0oi8he'
If you know format message for ConstraintViolation in your db , you can get constraint name from exception message method.
I have a database with three tables: Word, Idiom, WordIdiom that stores many to many relation between this two tables. WordItem includes only foreign keys for Word and Idiom tables.
After that, I have created Entity model, based on database. I have filled two tables with relevant content, and now I want to add cross-links between these tables.
So, I have written this code:
using (var db = new IdiomsDictionaryEntities())
{
var allIdioms = from idiom in db.Idioms select idiom;
foreach (var idiom in allIdioms)
{
string[] words = idiom.IdiomText.Split(new[] { " ", "-" }, StringSplitOptions.None);
foreach (var word in words)
{
var wordItem = db.Words.SingleOrDefault(exWord => exWord.WordString.ToLower().Equals(word));
if (wordItem == null)
{
Console.WriteLine("Idiom: " + idiom.IdiomText + ", missing word: " + word);
continue;
}
idiom.Words.Add(wordItem);
db.SaveChanges();
}
}
}
But when I run this code, I'm getting following error:
An unhandled exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll
Additional information: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
Inner-inner exception:
Unable to update the EntitySet 'WordIdiomMatch' because it has a DefiningQuery and no element exists in the element to support the current operation.`
As it is my first time with Entity Framework, I'm really don't know how to fix this. I have tried to add [ForeignKey()] property to Entity Framework models, but probably have done it wrong. I have also tried to add a primary key for WordIdiom, but it brakes even more things, as in this case I cannot even add items to Word and Idiom tables.
I have solved the problems, with help of #KerryRandolph and #AntoinePelletier
I was trying to update entities derived from a many-to-many relationship using Pure Join Table - meaning no other columns except foreign keys are allowed.
If you add a Primary Key column to a Join Table, you lose all of the entity framework advantages and have to implement insertion operation manually.
Proper solution was to alter the join table on the DB to make a PK that includes BOTH of the foreign ID columns.
First of all, i see that you have 2 add() for the same purpose. Witch is wrong. Imagine what it would look like in the data base :
wordItem.Idioms.Add(idiom);
ok now X and Y are linked by the link table as "X-Y" record.
idiom.Words.Add(wordItem);
And now... it would create another record that link these as "Y-X" witch is useles, if there is already an "X-Y" record then X is linked to Y with this single record and the other way around too.
And i'd say... usualy the primary key of a link table is the combination of the two foreign keys it contain, so the double add would crash anyway.
Since EF doesn't support unique key contraints, it seems that we need to catch exception during the Save method, and display error message to user.
The problems with this approach are:
how do we know which record threw an exception
how do we know what kind of problem threw an exception (ex I could have two unique constraints on same record, so I need to tell the user which one is broken)
DBMS is SqlServer 2008.
How to resolve these problems?
If you allow that a user can enter values that must be unique in the database you should validate this input before you save the changes:
if (context.Customers.Any(c => c.SomeUniqueProperty == userInput))
// return to user with a message to change the input value
else
context.SaveChanges();
This isn't only the case for values with unique constraints in the database but also for entering foreign key values that must refer to existing target records or primary key values if the primary key isn't autogenerated in the database. EF doesn't help you in the latter situation either because a context doesn't know the content of the whole database table but only about the entities that are currently attached to the context. It is true that EF will forbid to attach two objects with the same primary key but allows two objects with the same unique key constraint. But this doesn't guard you completely against primary key constraint violations when you save changes to the database.
In the unlikely case that between the Any check and SaveChanges another user has entered a record with the same value, I would consider the occuring exception as not possible to handle and just tell the user "An expected error occurred, please try again...". If the user tries again the Any check happens again and he will get the more useful message to change the input value from the code above.
The exception returned for such unique key constraint or primary key constraint violations is a general DbUpdateException and one of the inner exceptions will be a SqlException that contains as one of its properties a SQL Server error code. Any other details can be found only in the exception message "Violation of UNIQUE KEY constraint IX_SomeUniqueProperty_Index..." or similar. If you expect that the user can understand and react accordingly to this information you could display it. Otherwise you could log this message for an administrator or developer to check for possible bugs or other problems.
I just started to play with the entity framework, so I decided to connect it to my existing SQL Server CE database. I have a table with an IDENTITY(1, 1) primary key but when I tried to add an entity, I've got the above-mentioned error.
From MS Technet artice I learned that
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework.
When using the Entity Framework, an entity’s keys may be marked as server generated. This enables the database to generate a value for the key on insertion or entity creation. Additionally, zero or more properties of an entity may be marked as server-generated values. For more information, see the Store Generated Pattern topic in the Entity Framework documentation.
SQL Server Compact does not support entities with server-generated keys or values when it is used with the Entity Framework, although the Entity Framework allows you to define entity types with server-generated keys or values. Data manipulation operation on an entity that has server-generated values throws a "Not supported" exception.
So now I have a few questions:
Why would you mark key as server-generated if it is not supported and will throw an exception? It's hard to make sence from the quoted paragraph.
When I've tried to add StoreGeneratedPattern="Identity" to my entity's property, Studio complained that it is not allowed. What I'm doing wrong?
What is the best workaround for this limitation (including switching to another DB)? My limitations are zero-installation and using entity framework.
When I hit this limitation, I changed the type to uniqueidentifier
Use uniqueidentifier or generate a bigint/int key value manually is your best option.
Something like this perhaps ...
private static object lockObject = new object();
private static long nextID = -1;
public static long GetNextID()
{
lock (lockObject)
{
if (nextID == -1) nextID = DateTime.UtcNow.Ticks; else nextID++;
return nextID;
}
}
This assumes that you don't generate more than one record per tick during an application run (plus the time to stop and restart). This is a reasonable assumption I believe, but if you want a totally bullet proof (but more complex) solution, go read the highest ID from the database and increment from that.
SQL CE version 4.0 fixed this problem with its Entity Framework provider.
I just hit this issue too... mostlytech's answer is probably the best option, GUIDs are very easy to use and the risk of key collision is very low (although not inexistant).
Why would you mark key as server-generated if it is not supported and will throw an exception? It's hard to make sence from the quoted paragraph.
Because SQL Server (not Compact) supports it, and other third parties may support it too... Entity Framework is not only for SQL Server Compact ;)
In my case, all of my classes have the primary key named "ID"
I created an interface
public class IID
{
public Int32 ID { get; set; }
}
Then I create an extension method
public static Int32 GetNextID<T>(this ObjectSet<T> objects)
where T : class, IID
{
T entry = objects.OrderByDescending(u => u.ID).FirstOrDefault();
if (entry == default(T))
return 1;
return entry.ID + 1;
}
Then when I need a new ID, I just do this:
MyObject myobj = new MyObject();
myobj.ID = entities.MyTable.GetNextID();
the other option is to use SqlCeResultSet on the tables that have the identity column.
i have a primary key named ID with data type of INT32 and have Identity Column
Just do this
MyEntity Entity = new MyEntity();
String Command;
command = "Insert into Message(Created,Message,MsgType)values('12/1/2014','Hello World',5);
Entity.ExecuteStoreCommand(command);
--Exclude the primary key in the insert Statement
--Since the SQLCE do not support system generated keys
--Do not use LINQ because it supplies a default value to 0 for Primary keys that has a
data type of INT