Entity Framework Error Deleting Entity with Foreign Key Relationship - entity-framework

I am having a problem deleting some entities due to a foreign key relationship. I understand the following error message and have been doing everything I can think of to delete the entities without incurring this error:
The DELETE statement conflicted with the REFERENCE constraint
"FK_QuizUserAnswer_QuizWithQuestion". The conflict occurred in
database "SomeDatabase", table "dbo.QuizUserAnswer", column
'idQuizQuestion'. The statement has been terminated.
Here is an image of the two tables in question:
I am trying to delete QuizWithQuestion entities. I have made the idQuizQuestion column nullable. So, the foreign key is nullable on the QuizUserAnswer side.
In the mapping files, I have specified that the relationship is optional:
HasMany(t => t.QuizUserAnswers)
.WithOptional(t => t.QuizWithQuestion)
.HasForeignKey(t => t.idQuizQuestion);
HasOptional(t => t.QuizWithQuestion)
.WithMany(t => t.QuizUserAnswers)
.HasForeignKey(d => d.idQuizQuestion);
I have tried many, many snippets of code, so I will post the current state of the code in the hope that my intention is clear:
public void RemoveQuestionsFromQuiz(IEnumerable<int> deletedQuestions, int quizId)
{
var quiz = // code which retrieves quiz
foreach (var deletedQuestion in deletedQuestions)
{
var quizWithQuestion = quiz.QuizWithQuestions.FirstOrDefault(q => q.Id == deletedQuestion);
if (!ReferenceEquals(null, quizWithQuestion))
{
db.Entry(quizWithQuestion).State = EntityState.Deleted;
}
}
db.SaveChanges();
}
Another attempt looks like this:
public void RemoveQuestionsFromQuiz(IEnumerable<int> deletedQuestions, int quizId)
{
var quiz = // code which retrieves quiz
foreach (var deletedQuestion in deletedQuestions)
{
var quizWithQuestion = quiz.QuizWithQuestions.FirstOrDefault(q => q.Id == deletedQuestion);
if (!ReferenceEquals(null, quizWithQuestion))
{
foreach (var quizUserAnswer in quizWithQuestion.QuizUserAnswers)
{
quizUserAnswer.idQuizQuestion = null; // nullable
quizWithQuestion.QuizUserAnswers.Remove(quizUserAnswer);
db.Entry(quizUserAnswer).State = EntityState.Modified;
}
quiz.QuizWithQuestions.Remove(quizWithQuestion);
db.Entry(quizWithQuestion).State = EntityState.Deleted;
}
}
_db.SaveChanges();
}
How can I delete these darn entities (I'm so close to writing a stored procedure)?

Since you already have the question ids to delete, something like this should work:
// assuming db is your DbContext
var questions = db.QuizWithQuestions
.Where(q => deletedQuestions.Contains(q.Id))
.Include(q => q.QuizUserAnswers);
// assuming this is your DbSet
db.QuizWithQuestions.RemoveRange(questions);
db.SaveChanges();
If the QuizUserAnswer entities are loaded into the context (which is what include should do), Entity Framework should handle setting the foreign keys to null.

Related

The instance of entity type cannot be tracked with a composite primary key

I'm getting this error, but I can't seem to find a proper resolution.
The instance of entity type 'Assignment' cannot be tracked because
another instance with the same key value for {'OwnerUserId'} is
already being tracked. When attaching existing entities, ensure that
only one entity instance with a given key value is attached.
Functionally all I'm trying to do is insert some records with a composite primary key. Basically I just need to assign users to each other. I'm telling the context (afaik) to not do tracking, yet the error still seems to happen.
using (var context = new MyDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
//Get students with no assignments
var missingAssignments = model.Students.Where(x => x.AssignedToRead.Count < 2);
foreach (var s in missingAssignments)
{
var need = 2 - s.AssignedToRead.Count();
List<Guid> assigned = new List<Guid>();
assigned.AddRange(s.AssignedToRead.Select(x => x.UserId));
try
{
for (var i = 0; i < need; i++)
{
//Get a random reader (who's not them)
var shuffle = model.Students.Where(x => x.UserId != s.UserId).Where(x => !assigned.Contains(x.UserId)).ToList();
shuffle.Shuffle();
var rando = shuffle.FirstOrDefault();
var newAssignment = new Assignment() {
OwnerUserId = s.UserId,
ReaderUserId = rando.UserId
};
assigned.Add(rando.UserId);
context.Add(newAssignment);
context.SaveChanges();
}
}catch(Exception ex)
{
//Handle
}
}
}
I've validated I'm not inserting duplicate records (like 2 objects with the same PKs)
Is the problem in the mapping maybe?
modelBuilder.Entity<Assignment>(entity =>
{
entity.HasKey(e => new { e.OwnerUserId, e.ReaderUserId });
entity.ToTable("assignments");
entity.Property(e => e.OwnerUserId).HasColumnName("ownerUserId");
entity.Property(e => e.ReaderUserId).HasColumnName("readerUserId");
entity.Property(e => e.ModifiedDate)
.HasColumnName("modifiedDate")
.HasDefaultValueSql("(getdate())");
entity.HasOne(d => d.Owner)
.WithOne()
.HasPrincipalKey<Assignment>(d => d.OwnerUserId)
.HasForeignKey<AspnetUsers>(d => d.UserId)
.HasConstraintName("FK_users_owner");
entity.HasOne(d => d.Reader)
.WithOne()
.HasPrincipalKey<Assignment>(d => d.ReaderUserId)
.HasForeignKey<AspnetUsers>(d => d.UserId)
.HasConstraintName("FK_users_reader");
});
I don't understand why it would be tracking when I tell it not to
EDIT It appears the issue is with my Navigation Property mapping on the context? If i remove that entirely the Insert works fine, and I still have functioning Nav Properties on the object... I'm so confused, I thought it had to be told what to do?
I believe any entity added using Add will be tracked, the tracking behaviour is for entities that are queried from the DB. Does the error happen on the first instance you add, or on the second, or later?
I suspect the issue is probably with the HasOne.WithOne mapping between Assignment and User. I think this should be a HasOne.WithMany as a One-to-One typically defaults to being between PKs. (Though /w EF Core I believe you can nominate a non-PK 1-to-1) This mapping may be overruling the PK definition or otherwise adding a unique requirement that the tracking is considering. One User can have many assignments, but each assignment has one owner.
Another thing to check: In your entity class definition for Assignment, do you happen to have any [Key] attributes defined that might be tripping up the EF mapping? Your modelBuilder key mapping otherwise looks fine.

Entity Framework WebAPI Model UpSerts

I'm having problems with duplicate data during migration with Code First.
A new foreign key record is duplicated each time the migration creates the master record.
The schemas in the database are being created correctly. Namely the Primary Keys and Foreign Key values (the latter being automatically generated)
Can someone please advise thanks about how I detach the foreign key record during migration to prevent it recreating the record or any configuration I need to implement? I've tried updating the state of the foreign key obects before inserting master data. to both modified and detached.
For example I see multi records for the same priority where there should only be 3.
I'm using Entity Framework 6.0.
public class VeloPointDbConfiguration : DbMigrationsConfiguration<VeloPointDbContext>
{
public VeloPointDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(VeloPointDbContext context)
{
context.TaskPriorities.AddOrUpdate(EventTaskPriority.Migrations.All());
context.TaskStatuses.AddOrUpdate(TaskStatus.Migrations.All());
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.Priority).State == EntityState.Modified);
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.TaskStatus).State == EntityState.Modified);
context.Tasks.AddOrUpdate(EventOrganiserTask.Migrations.All());
}
}
The following examples of the instances i'm using for the data.
I create the following methods for the foreign key objects
public static EventTaskType[] All()
{
return new[]
{
GetDeadline(),
GetEmail(),
GetTelephone(),
GetAppointment(),
GetSnailMail(),
};
}
internal static EventTaskType GetDeadline()
{
return new EventTaskType("09974722-D03E-4CA3-BF3A-0AF7F6CA1B67", 1, "Deadline")
{
Icon = ""
}
}
I call the following methods the create the master data.
public static EventOrganiserTask[] All()
{
return new EventOrganiserTask[]
{
GetBookHQ(1, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetFindSponsor(2, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetRegisterEvent(3, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetBookFirstAid(4, Event.Migrations.EventDate.AddMonths(-6))
};
}
NOTE: When creating the master record, I call the method in the foreign key classes each time - which is the crux of the problem where I need to instruct the migration to detach this item.
public static EventOrganiserTask GetRegisterEvent(int id, DateTime date)
{
return new EventOrganiserTask
{
id = id,
Title = "Register event",
Summary = "Register the road race with the region",
DueDate = date,
Priority = EventTaskPriority.Migrations.GetHighPriority(),
Person = Person.Migrations.GetRaceOrganiser(1),
TaskType = EventTaskType.Migrations.GetDefault(),
TaskStatus = TaskStatus.Migrations.GetDefault(),
};
}
NOTE: When I do make changes to the data from the application, the foreign keys are not being updated. This must be related and indicates my entities are not configured correctly.
LATEST:
I'm still tearing my hair out. I've investigated this further and read about the migrations being multi threaded (It was another thread on stackoverflow but I can't find it again). Indeed running the Seed method I supposed is what it says on the tin and is purley for seeding data, so the data is only being added (regardless of AddOrUpdate - what's that all about then) So I've looked at the behaviour regarding the records being created. First of all I called context.SaveChanges() after creating the look up tables. At this point it doesn't created any duplicates as the items are only referenced once. I then let the seed method run the master data - but argggh - I see duplicates (when the instances are called on the master data). But this did flag something up with regard to the order in which it creates the records.
My next step was to create two migrations, but without any success.
I'm hoping somebody picks up this thread soon. I'm tearing my hair out.
Ok so i've finally found my answer. It was clever enough to create the foreign key relationships from the model, but i needed to be explicitly set the foreign key id field. I chose the Fluent API to explicitly set the relationships and I set the value of the id field in the mapping of the object.
modelBuilder.Entity<Task>()
.HasRequired(x => x.Priority)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.Priority_id);
Here it is in the seed method
public class VeloPointDbConfiguration : DbMigrationsConfiguration<VeloPointDbContext>
{
public VeloPointDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(VeloPointDbContext context)
{
context.TaskPriorities.AddOrUpdate(EventTaskPriority.Migrations.All());
context.TaskStatuses.AddOrUpdate(TaskStatus.Migrations.All());
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.Priority).State == EntityState.Modified);
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.TaskStatus).State == EntityState.Modified);
context.Tasks.AddOrUpdate(EventOrganiserTask.Migrations.All());
// Foreign Key relationships
modelBuilder.Entity<EventOrganiserTask>()
.HasRequired(x => x.TaskStatus)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.TaskStatus_id);
modelBuilder.Entity<Task>()
.HasRequired(x => x.TaskType)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.TaskType_id);
modelBuilder.Entity<Task>()
.HasRequired(x => x.Priority)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.Priority_id);
}
}

Delete data sith Breeze.js without loading it to client

I am using Breeze.js with Entity Framework WebAPI backend, and I need to delete a large set of data that is not loaded to client. I would really like to do it on the server and not load it.
Is there a "breeze way"? By that I mean a method in a BreezeController.
EDIT
I have to delete all rows from one table that belong to the user, whose date field is in future, and all their child rows.
public override int SaveChanges()
{
foreach (
var entry in
this.ChangeTracker.Entries()
.Where((e => (e.State == (EntityState) Breeze.WebApi.EntityState.Deleted))))
{
if (entry.Entity.GetType() == typeof(User))
{
var entity = entry.Entity as User;
var childEntitiesInFuture = ChildEntities.Where(c => c.DateField > DateTime.Now);
foreach (var child in childEntitiesInFuture){
var grandchildrenForDeletion = Grandchildren.Where(c => c.ChildId == child.Id);
foreach (var g in grandchildrenForDeletion) Grandchildren.Remove(g);
ChildEntities.Remove(child);
}
}
}
}
Assuming you are deleting User, one User has many ChildEntity saved in ChildEntities and each ChildEntity has many Grandchild saved in Grandchildren. A bit messy names, but that's what you get with no real names :)
This method goes into your Context class. Good luck.

EF6.0 "The relationship could not be changed because one or more of the foreign-key properties is non-nullable"

If I try to delete a "child" row I always get an exception. Here is a snipset:
using (var context = new CompanyContext())
{
ItemType itemType = context.ItemTypes.FirstOrDefault(i => i.Name == "ServerType");
ItemTypeItem itemTypeItem = itemType.Items.FirstOrDefault(i => i.Name == "DatabaseServer");
itemType.Items.Remove(itemTypeItem);
context.SaveChanges(); <=== exception!
}
The following exception is thrown on the SaveChanges() method.
"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."
Entity Configuration
public class ItemTypeConfiguration : NamedEntityConfiguration<ItemType>
{
public ConfigurationColumn ParentIDColumn;
public ConfigurationColumn ValidationPatternColumn;
public ItemTypeConfiguration() : base()
{
ParentIDColumn = new ConfigurationColumn() { Name = "ParentID", Ordinal = base.LastOrdinalPosition + 1 };
ValidationPatternColumn = new ConfigurationColumn() { Name = "ValidationPattern", Length = 1024, Ordinal=base.LastOrdinalPosition + 2};
this.Property(t => t.ParentID)
.HasColumnName(ParentIDColumn.Name)
.HasColumnOrder(ParentIDColumn.Ordinal);
this.HasOptional(t => t.Parent).WithMany().HasForeignKey(u => u.ParentID).WillCascadeOnDelete(false);
this.Property(t => t.ValidationPattern)
.HasColumnName(ValidationPatternColumn.Name)
.HasColumnOrder(ValidationPatternColumn.Ordinal)
.HasMaxLength(ValidationPatternColumn.Length);
}
...
public class ItemTypeItemConfiguration : NamedEntityConfiguration<ItemTypeItem>
{
public ConfigurationColumn ItemTypeIDColumn;
public ItemTypeItemConfiguration() : base()
{
ItemTypeIDColumn = new ConfigurationColumn(){Name="ItemTypeID", IsRequired=true, Ordinal= base.LastOrdinalPosition+1};
this.Property(t => t.ItemTypeID)
.HasColumnName(ItemTypeIDColumn.Name)
.HasColumnOrder(ItemTypeIDColumn.Ordinal);
this.HasRequired(t => t.ItemType).WithMany(t=>t.Items).HasForeignKey(u => u.ItemTypeID).WillCascadeOnDelete(true);
}
...
I found the blog but I don't have the "DeleteObject" method.
http://blog.clicdata.com/2013/07/04/the-operation-failed-the-relationship-could-not-be-changed-because-one-or-more-of-the-foreign-key-properties-is-non-nullable/
Any ideas? Thank you.
You need to delete the ItemTypeItem.
It is not possible to just remove it from the Items list as it cannot exist by itself, because it has a non-nullable foreign key referencing ItemType (ItemTypeID).
To delete the ItemTypeItem add
context.Entry(itemTypeItem).State = EntityState.Deleted;
In the entity framework 6.0 if you remove the entity from the main context set it will work. For example to remove an investment entity you would do the following:
context.Investments.Remove(entity);
context.SaveChanges();
This is different than attempting to remove the entity from its parent/owner, as the following:
bankAccount.Investments.Remove(entity);
context.SaveChanges();
This will throw the relationship could not be changed exception listed above. Hope this helps.
In entity 6.0 there is a difference between:
context.Investments.Remove(entity);
and
context.Entry(entity).State = EntityState.Deleted;
When using the first and cascading deletes are enabled, EF will internally perform the necessary deletes of child collections.
When using the second option, EF will not handle the necessary deletes, but let you handle the rebinding/deletion of these child objects.
This issue arise because we try to delete the parent table still child table data is present. We solve the problem with help of cascade delete.
In model Create method in dbcontext class.
modelBuilder.Entity<Job>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Job)
.HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
modelBuilder.Entity<Sport>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Sport)
.HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);
After that,In our API Call
var JobList = Context.Job
.Include(x => x.JobSportsMappings) .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();
Cascade delete option delete the parent as well parent related child table with this simple code. Make it try in this simple way.
Remove Range which used for delete the list of records in the database Thanks
The other answers describing why the error occurs are correct, but it is actually possible to get EF to fully delete the child when .Remove is called on the parent object's collection of children, you don't need to go directly to the child entity's table in the DB context and delete it from that.
To get that to work you need to set up an Identifying Relationship, as described in this answer.

How do I delete multiple rows in Entity Framework (without foreach)

I want to delete several items from a table using Entity Framework. There is no foreign key / parent object, so I can't handle this with OnDeleteCascade.
Right now I'm doing this:
var widgets = context.Widgets
.Where(w => w.WidgetId == widgetId);
foreach (Widget widget in widgets)
{
context.Widgets.DeleteObject(widget);
}
context.SaveChanges();
It works, but the foreach bugs me. I'm using EF4, but I don't want to execute SQL. I just want to make sure I'm not missing anything -- this is as good as it gets, right? I can abstract the code with an extension method or helper, but somewhere we're still going to be doing a foreach, right?
EntityFramework 6 has made this a bit easier with .RemoveRange().
Example:
db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();
Warning! Do not use this on large datasets!
EF pulls all the data into memory, THEN deletes it. For smaller data sets this might not be an issue but generally avoid this style of delete unless you can guarantee you are only doing very small changes.
You could easily run your process out of memory while EF happily pulls in all the data you specified just to delete it.
using (var context = new DatabaseEntities())
{
context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}
Addition: To support list of ids you can write
var listOfIds = String.Join(',',customerIds.Select(id => $"'{id}'").ToList());
var sql= $#"DELETE [YOURTABLE] WHERE CustomerID in ({listOfIds})";
Note: if CustomerID Is a string, you should double-check for potential SQL injection risks, for integer CustomerID it’s safe
this is as good as it gets, right? I can abstract it with an extension
method or helper, but somewhere we're still going to be doing a
foreach, right?
Well, yes, except you can make it into a two-liner:
context.Widgets.Where(w => w.WidgetId == widgetId)
.ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
I know it's quite late but in case someone needs a simple solution, the cool thing is you can also add the where clause with it:
public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
string selectSql = db.Set<T>().Where(filter).ToString();
string fromWhere = selectSql.Substring(selectSql.IndexOf("FROM"));
string deleteSql = "DELETE [Extent1] " + fromWhere;
db.Database.ExecuteSqlCommand(deleteSql);
}
Note: just tested with MSSQL2008.
Update:
The solution above won't work when EF generates sql statement with parameters, so here's the update for EF5:
public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
var query = db.Set<T>().Where(filter);
string selectSql = query.ToString();
string deleteSql = "DELETE [Extent1] " + selectSql.Substring(selectSql.IndexOf("FROM"));
var internalQuery = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_internalQuery").Select(field => field.GetValue(query)).First();
var objectQuery = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_objectQuery").Select(field => field.GetValue(internalQuery)).First() as ObjectQuery;
var parameters = objectQuery.Parameters.Select(p => new SqlParameter(p.Name, p.Value)).ToArray();
db.Database.ExecuteSqlCommand(deleteSql, parameters);
}
It requires a little bit of reflection but works well.
If you don't want to execute SQL directly calling DeleteObject in a loop is the best you can do today.
However you can execute SQL and still make it completely general purpose via an extension method, using the approach I describe here.
Although that answer was for 3.5. For 4.0 I would probably use the new ExecuteStoreCommand API under the hood, instead of dropping down to the StoreConnection.
For anyone using EF5, following extension library can be used: https://github.com/loresoft/EntityFramework.Extended
context.Widgets.Delete(w => w.WidgetId == widgetId);
Entity Framework Core
3.1 3.0 2.2 2.1 2.0 1.1 1.0
using (YourContext context = new YourContext ())
{
var widgets = context.Widgets.Where(w => w.WidgetId == widgetId);
context.Widgets.RemoveRange(widgets);
context.SaveChanges();
}
Summary:
Removes the given collection of entities from the context underlying the set
with each entity being put into the Deleted state such that it will be deleted
from the database when SaveChanges is called.
Remarks:
Note that if System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled
is set to true (which is the default), then DetectChanges will be called once
before delete any entities and will not be called again. This means that in some
situations RemoveRange may perform significantly better than calling Remove multiple
times would do. Note that if any entity exists in the context in the Added state,
then this method will cause it to be detached from the context. This is because
an Added entity is assumed not to exist in the database such that trying to delete
it does not make sense.
Still seems crazy to have to pull anything back from the server just to delete it, but at least getting back just the IDs is a lot leaner than pulling down the full entities:
var ids = from w in context.Widgets where w.WidgetId == widgetId select w.Id;
context.Widgets.RemoveRange(from id in ids.AsEnumerable() select new Widget { Id = id });
Finally bulk delete has been introduced in Entity Framework Core 7 via the ExecuteDelete command:
context.Widgets
.Where(w => w.WidgetId == widgetId)
.ExecuteDelete();
Something to note here is that ExecuteDelete does not need a SaveChanges, as per its documentation:
This operation executes immediately against the database, rather than being deferred until DbContext.SaveChanges() is called. It also does not interact with the EF change tracker in any way: entity instances which happen to be tracked when this operation is invoked aren't taken into account, and aren't updated to reflect the changes.
I know that the question was asked for EF4, but if you upgrade this is a good alternative!
EF 6.1
public void DeleteWhere<TEntity>(Expression<Func<TEntity, bool>> predicate = null)
where TEntity : class
{
var dbSet = context.Set<TEntity>();
if (predicate != null)
dbSet.RemoveRange(dbSet.Where(predicate));
else
dbSet.RemoveRange(dbSet);
context.SaveChanges();
}
Usage:
// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");
Or:
// delete all from entity
DeleteWhere<MyEntity>();
For EF 4.1,
var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");
The quickest way to delete is using a stored procedure. I prefer stored procedures in a database project over dynamic SQL because renames will be handled correctly and have compiler errors. Dynamic SQL could refer to tables that have been deleted/renamed causing run time errors.
In this example, I have two tables List and ListItems. I need a fast way to delete all the ListItems of a given list.
CREATE TABLE [act].[Lists]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY,
[Name] NVARCHAR(50) NOT NULL
)
GO
CREATE UNIQUE INDEX [IU_Name] ON [act].[Lists] ([Name])
GO
CREATE TABLE [act].[ListItems]
(
[Id] INT NOT NULL IDENTITY,
[ListId] INT NOT NULL,
[Item] NVARCHAR(100) NOT NULL,
CONSTRAINT PK_ListItems_Id PRIMARY KEY NONCLUSTERED (Id),
CONSTRAINT [FK_ListItems_Lists] FOREIGN KEY ([ListId]) REFERENCES [act].[Lists]([Id]) ON DELETE CASCADE
)
go
CREATE UNIQUE CLUSTERED INDEX IX_ListItems_Item
ON [act].[ListItems] ([ListId], [Item]);
GO
CREATE PROCEDURE [act].[DeleteAllItemsInList]
#listId int
AS
DELETE FROM act.ListItems where ListId = #listId
RETURN 0
Now the interesting part of deleting the items and updating Entity framework using an extension.
public static class ListExtension
{
public static void DeleteAllListItems(this List list, ActDbContext db)
{
if (list.Id > 0)
{
var listIdParameter = new SqlParameter("ListId", list.Id);
db.Database.ExecuteSqlCommand("[act].[DeleteAllItemsInList] #ListId", listIdParameter);
}
foreach (var listItem in list.ListItems.ToList())
{
db.Entry(listItem).State = EntityState.Detached;
}
}
}
The main code can now use it is as
[TestMethod]
public void DeleteAllItemsInListAfterSavingToDatabase()
{
using (var db = new ActDbContext())
{
var listName = "TestList";
// Clean up
var listInDb = db.Lists.Where(r => r.Name == listName).FirstOrDefault();
if (listInDb != null)
{
db.Lists.Remove(listInDb);
db.SaveChanges();
}
// Test
var list = new List() { Name = listName };
list.ListItems.Add(new ListItem() { Item = "Item 1" });
list.ListItems.Add(new ListItem() { Item = "Item 2" });
db.Lists.Add(list);
db.SaveChanges();
listInDb = db.Lists.Find(list.Id);
Assert.AreEqual(2, list.ListItems.Count);
list.DeleteAllListItems(db);
db.SaveChanges();
listInDb = db.Lists.Find(list.Id);
Assert.AreEqual(0, list.ListItems.Count);
}
}
You can use extensions libraries for doing that like EntityFramework.Extended or Z.EntityFramework.Plus.EF6, there are available for EF 5, 6 or Core. These libraries have great performance when you have to delete or update and they use LINQ. Example for deleting (source plus):
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2))
.Delete();
or (source extended)
context.Users.Where(u => u.FirstName == "firstname")
.Delete();
These use native SQL statements, so performance is great.
This answers is for EF Core 7 (I am not aware if they merged EF Core with EF now or not, before they kept the two separately).
EF Core 7 now supports ExecuteUpdate and ExecuteDelete (Bulk updates):
// Delete all Tags (BE CAREFUL!)
await context.Tags.ExecuteDeleteAsync();
// Delete Tags with a condition
await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync();
The equivalent SQL queries are:
DELETE FROM [t]
FROM [Tags] AS [t]
DELETE FROM [t]
FROM [Tags] AS [t]
WHERE [t].[Text] LIKE N'%.NET%'
If you want to delete all rows of a table, you can execute sql command
using (var context = new DataDb())
{
context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}
TRUNCATE TABLE (Transact-SQL) Removes all rows from a table without logging the individual row deletions. TRUNCATE TABLE is similar to the DELETE statement with no WHERE clause; however, TRUNCATE TABLE is faster and uses fewer system and transaction log resources.
You can execute sql queries directly as follows :
private int DeleteData()
{
using (var ctx = new MyEntities(this.ConnectionString))
{
if (ctx != null)
{
//Delete command
return ctx.ExecuteStoreCommand("DELETE FROM ALARM WHERE AlarmID > 100");
}
}
return 0;
}
For select we may use
using (var context = new MyContext())
{
var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList();
}
UUHHIVS's is a very elegant and fast way for batch delete, but it must be used with care:
auto generation of transaction: its queries will be encompassed by a transaction
database context independence: its execution has nothing to do with context.SaveChanges()
These issues can be circumvented by taking control of the transaction. The following code illustrates how to batch delete and bulk insert in a transactional manner:
var repo = DataAccess.EntityRepository;
var existingData = repo.All.Where(x => x.ParentId == parentId);
TransactionScope scope = null;
try
{
// this starts the outer transaction
using (scope = new TransactionScope(TransactionScopeOption.Required))
{
// this starts and commits an inner transaction
existingData.Delete();
// var toInsert = ...
// this relies on EntityFramework.BulkInsert library
repo.BulkInsert(toInsert);
// any other context changes can be performed
// this starts and commit an inner transaction
DataAccess.SaveChanges();
// this commit the outer transaction
scope.Complete();
}
}
catch (Exception exc)
{
// this also rollbacks any pending transactions
scope?.Dispose();
}
In EF 7 you can use bulk delete
var ids = widgets.Select(x => x.Id).ToList();
await _mrVodDbContext.Widgets.Where(x => ids.Contains(x.Id)).ExecuteDeleteAsync();
EF core generate
DELETE FROM [i]
FROM [Widgets] AS [i]
WHERE [i].[Id] IN (4,3,2,1)
More about deleting or updating in release notes. https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executedelete-examples
You can also use the DeleteAllOnSubmit() method by passing it your results in a generic list rather than in var. This way your foreach reduces to one line of code:
List<Widgets> widgetList = context.Widgets
.Where(w => w.WidgetId == widgetId).ToList<Widgets>();
context.Widgets.DeleteAllOnSubmit(widgetList);
context.SubmitChanges();
It probably still uses a loop internally though.
Thanh's answer worked best for me. Deleted all my records in a single server trip. I struggled with actually calling the extension method, so thought I would share mine (EF 6):
I added the extension method to a helper class in my MVC project and changed the name to "RemoveWhere". I inject a dbContext into my controllers, but you could also do a using.
// make a list of items to delete or just use conditionals against fields
var idsToFilter = dbContext.Products
.Where(p => p.IsExpired)
.Select(p => p.ProductId)
.ToList();
// build the expression
Expression<Func<Product, bool>> deleteList =
(a) => idsToFilter.Contains(a.ProductId);
// Run the extension method (make sure you have `using namespace` at the top)
dbContext.RemoveWhere(deleteList);
This generated a single delete statement for the group.
I came up with a great library Zack.EFCore.Batch. It will convert your expression into simple DELETE FROM .... WHERE query. (Like some answers proposed) https://github.com/yangzhongke/Zack.EFCore.Batch
The usage example:
await ctx.DeleteRangeAsync<Book>(b => b.Price > n);
The Zack.EFCore.Batch library has lots of benefits over Z.EntityFramework.Extended https://entityframework-extensions.net/ which does not have true Async methods. (They are just wrappers around sync methods) You can get lots of unexpected issues by using this library in high load environment.
EF 6.=>
var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();
Best : in EF6 => .RemoveRange()
Example:
db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));
If you are using Generic Repository:
Inside Generic repository, following could be new method.
public void RemoveMultiple(Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _context.Set<T>().Where(predicate);
_context.Set<T>().RemoveRange(query.AsNoTracking());
}
Usage:
_unitOfWork.YOUR_ENTITY.RemoveMultiple(x => x.AccountId == accountId);
_unitOfWork.Complete();
context.Widgets.RemoveRange(context.Widgets.Where(w => w.WidgetId == widgetId).ToList());
db.SaveChanges();
See the answer 'favorite bit of code' that works
Here is how I used it:
// Delete all rows from the WebLog table via the EF database context object
// using a where clause that returns an IEnumerable typed list WebLog class
public IEnumerable<WebLog> DeleteAllWebLogEntries()
{
IEnumerable<WebLog> myEntities = context.WebLog.Where(e => e.WebLog_ID > 0);
context.WebLog.RemoveRange(myEntities);
context.SaveChanges();
return myEntities;
}
In EF 6.2 this works perfectly, sending the delete directly to the database without first loading the entities:
context.Widgets.Where(predicate).Delete();
With a fixed predicate it's quite straightforward:
context.Widgets.Where(w => w.WidgetId == widgetId).Delete();
And if you need a dynamic predicate have a look at LINQKit (Nuget package available), something like this works fine in my case:
Expression<Func<Widget, bool>> predicate = PredicateBuilder.New<Widget>(x => x.UserID == userID);
if (somePropertyValue != null)
{
predicate = predicate.And(w => w.SomeProperty == somePropertyValue);
}
context.Widgets.Where(predicate).Delete();