Unique Key Constraint Check In Entity Framework - entity-framework

I have a unique key constraint in my SQL Server that is based on two columns (AbsoluteCounter, TimeMfrAudit). If I try to add a list of objects via Entity Framework, how can I check if one of my objects is going to violate this constraint without throwing an exception that will make my entire context.SaveChanges() fail ? I am looking for a best practice here.
Thanks

These would be all objects that violate the unique key constraint:
var violatingObjects = listOfObjectsToAdd
.Where(o => context.Objects.Any(oInDb =>
oInDb.AbsoluteCounter == o.AbsoluteCounter &&
oInDb.TimeMfrAudit == o.TimeMfrAudit))
.ToList();
It is one EXISTS database query per object in the list. If you only want to know the first object that violates the constraint use FirstOrDefault() instead of ToList(). If you only want to know if there is any violating object at all use Any() instead of ToList(). The iteration should stop when the first object has been found.

Related

EF Core - Add Unique Constraint

I'd like to add unique constraint using model builder in ASP.NET Core
.NET 5
EF Core 5..
Here is what I found:
modelBuilder.Entity<Ticker>()
.HasIndex(r => r.Name)
.IsUnique();
However after updating database it adds index instead of constraint.
Is there the way to add constraint?
Thanks for the answer.
Although you use .HasIndex(r => r.Name).IsUnique(),it will create a unique Index instead of Constraint. But the effect are the same. It will prevent you adding duplicated value for Name.
However after updating database it adds index instead of constraint. Is there the way to add constraint?
If you must add Constraint, I suggest you use HasAlternateKey method which enables you to create an alternate key by placing a unique constraint:
modelBuilder.Entity<Ticker>()
.HasAlternateKey(r => r.Name);
Then you can see it adds the unique constraint like below in database:

Efficient way to perform an UPSERT with EFCore.BulkExtensions when data does not include primary key

I need to upload bulk data into a database through an API and I am looking at using EFCore.BulkExtensions. Unfortunately, the data will not include the auto-generated primary key (Id).
However, we have a unique composite key that will be included in the data. I do need to keep the Id as the key attribute in the model because I will need that to use as the foreign key to several children (I have been able to BulkInsert parent and children using IncludeGraph, although I have been unable to set this up for BulkUpdate or BulkInsertOrUpdate - see below).
What is the most efficient way to handle updates when I don't have a primary key? Should I use the BulkRead to get the Id based on the unique composite key, selecting all 3 fields and then map this into the list of records? Or is there another way to handle this (maybe similar to ColumnPrimaryKeyExpression for Entity Framework Extensions).
Will there be an issue if the child records are also an Upsert when the parent is an Update? These records will have the same issue with missing primary keys and unique composite keys as I have for the parent.
Thanks in advance!
I propose to look at alternatives. For example linq2db.EntityFrameworkCore has everything to work with bulk operations. Note that I'm one of the creators.
How to make Insert or Update of million(s) rows:
var items = ...
using var db = context.CreateLinqToDBConnection();
// temporary table will be crated and items will be inserted as fast as possible
using var temp = db.CreateTempTable(items);
var destinationTable = context.SomeTable.ToLinqToDBTable();
// or
var destinationTable = db.GetTable<SomeTable>();
destinationTable
.Merge()
.Using(temp)
.On((target, source) => target.SomeValue == source.SomeValue)
.InsertWhenNotMatched(source => new SomeTable
{
SomeValue = source.SomeValue
... // other fields
})
.UpdateWhenMatched((target, source) => new SomeTable
{
OtherValue = source.OtherValue
... // other fields
})
.Merge();
I have decided to pull back the data I need with a Linq query based on the key using .AsNoTracking() and then match the Id's to the data to be updated. Due to some other restrictions, I need to use a ForEach to run some business rules on each record before running it through the BulkUpdate anyway, so the match can hopefully be done at the same time.

"Cannot insert explicit value for identity column in table 'x' when IDENTITY_INSERT is set to OFF" - inserting record with nested custom object

I get the error "Cannot insert explicit value for identity column in table 'UserPermission' when IDENTITY_INSERT is set to OFF" trying to insert a record as follows:
dbContext.User.Add(someUser);
dbContext.SaveChanges();
That being said, the User file has the custom class UserPermission as one of its parameters, and someUser's UserPermission is not null and has a set ID parameter. Why does this happen and is it possible to avoid getting this error without having to explicitly add a UserPermissionID foreign key parameter in my User model and setting the UserPermission parameter to null?
Thanks in advance.
This issue typically happens when deserializing entities that have related entities in the object graph then attempting to add them. UserPermission is likely an existing record that in the DB is set up with an identity PK, but EF doesn't appear to recognize that in the entity definition. (I.e. set to DatabaseGenerated(DatabaseGeneratedOption.Identity). If it had been you would most likely be seeing a different problem where a completely new duplicate UserPermission was being created.
If someUser, and it's associated someUser.UserPermission are deserialized entities then you need to do a bit of work to ensure EF is aware that UserPermission is an existing row:
void AddUser(User someUser)
{
var existingPermission = _context.UserPermissions.Local
.SingleOrDefault(x => x.UserPermissionId == someUser.UserPermission.UserPermissionId);
if (existingPermission != null)
someUser.UserPermission = existingPermission;
else
_context.Attach(someUser.UserPermission);
_context.Users.Add(someUser);
_context.SaveChanges();
}
In a nutshell, when working with detached entities that a DbContext may not be tracking, we need to check the Local state for any existing tracked instance for that ID. If we find one, we substitute the detached reference for the tracked one. If we don't find one, we attach the detached one before Adding our user.
This still isn't entirely safe because it assumes that the referenced UserPermission will exist in the database. If for any reason a non-existent UserPermission is sent in (row deleted, or fake data) you will get an exception on Save.
Passing detached entity references around can seem like a simple option at first, but you need to do this for every reference within a detached entity. If you simply call Attach without first checking, it will likely work until you come across a scenario where at runtime it doesn't work because the context happens to already be tracking an instance.

How to add entries for many to many relation table with Entity Framework

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.

Entity Framework object graph deletion with Breeze

I am encountering a recurring problem that just makes no sense, and hoping someone (in the Breeze team?) can shed some light.
The following model illustrates the entities in question.
As you can see, I'm adhering pretty strictly to Entity Framework conventions in my property names, and as a result, if I check in SQL the cascade on delete rules are set by EF code first when it creates the db.
Now, when I try to delete a BusUnit manually in SQL, the delete cascades correctly and the corresponding BusUnitDimensions are also deleted, as it should be. Likewise, if I delete a Dimension in SQL, the corresponding BusUnitDimensions are also deleted.
However, in my application, if I mark a BusUnit as setDeleted with Breeze and then try saveChanges, I get the following error.
The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.
Strangely though, if I mark a Dimension for deletion and then save (within Breeze), the cascaded delete works correctly and both the Dimension and its corresponding BusUnitDimensions are deleted.
So, why the inconsistency? Why are the cascaded delete rules in SQL not being applied for BusUnits but yet they're working for Dimensions? I've read elsewhere that Breeze does not support cascaded deletes, but then why is my Dimensions case working?
EDIT:
I've removed my previous edits as they weren't relevant. The changes below follow on from Ward's answer...
My model now looks like this, and BusUnitDims now uses BusUnitId and DimId as a compound key, and I've added a bool, IsBud for the purposes of payload.
I haven't yet implemented deletes for BusUnits, but already if I try delete a Dim, I'm getting the same error message:
The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.
I have noticed that cascaded deletes is no longer enabled, and in fact, to get EF to build the database I to add the following configuration:
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.BusUnit)
.WithMany(bu => bu.BusUnitDims)
.HasForeignKey(bud => bud.BusUnitId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.Dim)
.WithMany(d => d.BusUnitDims)
.HasForeignKey(bud => bud.DimId)
.WillCascadeOnDelete(false);
So, with cascading now explicitly not in place, I can understand why the error occurs. Does that imply that in the controller, one has to specifically mark each map for deletion when deleting a parent Dim or BusUnit and before saveChanges is called, or is there some way to configure EF to take advantage of cascaded deletes as this would hugely simplify the code in my controller?
(PS: it gets even more complex, because BusUnitDims ends up having a further join table of its own, MetricBusUnitDims to accommodate yet another entity in the model and their relationships. This is why I'm trying to get the principles right early on)
EDIT: (A CONTROLLER SOLUTION FOR BUSUNITS)
So, the following approach works for BusUnits:
function deleteBusUnit(busUnitVm) { // note that you pass in the item viewmodel, not the entity
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimMapVms;
var dimHash = createBusUnitDimHash(busUnit);
mapVms.forEach(function (mapVm) {
var map = dimHash[mapVm.dim.id];
if (map) {
datacontext.markDeleted(map);
}
});
datacontext.markDeleted(busUnit);
save().then(function() { getDBoardConfig(); });
}
}
Is this the correct approach? if so, I'll still have to figure out the following:
How to approach Dims. These are different becuase the item viewmodel is defined for BusUnits.
How to approach the situation where there is a join tabel one level down, e.g. MetricBusUnitDIm.
EDIT: (A CONTROLLER SOLUTION FOR DIMS)
function deleteDim(dim) {
return bsDialog.deleteDialog(dim.name, true)
.then(function () {
vm.busUnitVms.forEach(function (busUnitVm) {
busUnitVm.busUnit.busUnitDims.forEach(function (bud) {
if (bud.dimId === dim.id) {
datacontext.markDeleted(bud);
}
});
});
datacontext.markDeleted(dim);
save().then(function () { getDboardConfig(); });
});
}
I believe your problems are traceable to the fact that your mapping table BusUnitDimension has its own primary key, Id, as opposed to the more typical approach in which the BusUnitId and DimensionId FK properties together comprise the compound primary key of BusUnitDimension.
Observe that OrderDetails in Northwind and the HeroPoweMap in the Breeze many-to-many example have compound keys.
Your choice creates complications.
First, it becomes possible to create multiple BusUnitDimension entities representing the same association between BusUnit and Dimension (i.e., they all have the same pair of FKs). The database may be able to prevent this (it's been a long time since I looked) but whether it does or doesn't, it won't prevent you from creating those duplicates in Breeze ... and maybe not in EF either.
Secondly, it opens you up to the problem you're currently facing. If those mapping entities are in the DbContext when you perform the delete, EF may (apparently does) try to null their FK properties as it sets either BusUnit or Dimension to the deleted state.
You can get around this, as has been suggested, by making both the BusUnitId and DimensionId FK properties nullable. But that is contrary to the semantics as a BusUnitDimension must link a real BusUnit to a real Dimension; they aren't optional. The practical consequence may be that you don't get cascade delete from the EF perspective if you do this (not sure if the DB will enforce that either). That means you'd have orphaned BusUnitDimension rows in your database with one or both FKs being null. I speculate because I'm not used to getting into this kind of trouble.
Another approach would be to set their FK values to zero (I think Breeze does this for you). Of course this implies the existence of BusUnit and Dimension table rows with Id == 0, if only during the delete operation.
Btw, you could actually have such "sentinel entities" in your DB.
You must make sure that these BusUnitDimension are in the deleted state or EF (and the DB) will either reject them (referential integrity constraint) or orphan them (you'll have BusUnitDimension rows in your database with one or both FKs being zero).
Alternatively, if you know that the DB will cascade delete them, you can simply remove them from the DbContext (remove from the EntityInfoMap in the EFContextProvider). But now you have to tell the Breeze client to get rid of them too if it happens to have them hanging around.
Enough Already!
These wandering thoughts should tell you that you've got yourself in a jam here with way too much bookkeeping ... and all because you gave BusUnitDimension its own Id primary key.
It gets a lot easier if you give BusUnitDimension the compound key, {BusUnitId, DimensionId}. You must also give it a payload property (anything will do) to prevent EF from hiding it in its "many-to-many" implementation because Breeze doesn't handle that. Adding any nonsense property will do the trick.
HTH
That has nothing to do with Breeze.. The originating message is from Entity Framework..
inside BusUnitDimension Model update BusUnitId property to:
public Nullable<int> BusUnitId { get; set; }
Notice the Nullable struct..