Querying against DbContext.Set(TypeVariable) in Entity Framework - entity-framework

I'm re-factoring an application to invert some dependencies. Part of the original code uses DbContext.Set<MyEntityType>() to obtain the set to query against.
With the inversion, the MyEntityType is no longer explicitly known to the code using DbContext.Set<MyEntityType>(), so now I'm using DbContext.Set(TypeVariable) instead.
The re-factoring works to the extent that the correct DbSet is being returned.
However, the type DbContext.Set(TypeVariable).Local is IList (contained type unknown) where-as the type of DbContext.Set<MyEntityType>().Local was ObservableCollection<MyEntityType>. This means that it's now impossible to do Linq against the DbSet.
The best workaround I've been able to achieve is to cast to an interface that MyEntityType and other entity types implement (some code omitted for clarity)
var set = Context.Set(targetType);
var entity = set.Local.OfType<IActionTarget>()
.FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);
var querables = set as IQueryable<IActionTarget>;
entity = querables.FirstOrDefault(e => e.Id == key.Id && e.EffectiveDate == key.EffectiveDate);
So, two questions:
Why doesn't DbContext.Set(TypeVariable) return a strongly typed set?
Is there a better way to do the dependency inversion?
Some further details as requested
It's all about dependencies. The Model contains POCO classes which are persisted via EF Code First in the typical way (but via a Repository). An ActionEvaluator takes some incoming data, and via Repository methods, determines what actions need to occur - hence the queries against the DbSets.
In the original code, there was only one type of incoming data (CSV of a particular format) and the ActionEvaluator had a tight dependency to this data and knew which POCO classes were applicable to which CSV records.
Now, we want to expand to use different CSV formats and web api messages. To do this, we need to invert the dependencies so that the DataSource tells the ActionEvaluator what POCO classes it's records apply to. This is done by way of a Type variable.
So, the ActionEvaluator can no longer use a generic type parameter, but it can pass the type variable to the Repository which uses it to find the correct DbSet.
The problem is the difference between the two ways of finding the DbSet - DbContext.Set<AnEntity>() and DbContext.Set(TypeVariable).
I guess I'm asking for an enhancement in EF to make these two functionally equivalent in their return values, but that may not be possible since the types of the second version are determined at runtime.
Here's the full method code as requested:
private IActionTarget DbContextGetEntity(Type targetType, IActionTarget key)
{
var set = Context.Set(targetType);
if (set == null)
{
throw new Exception("Unable to find DbSet for type '{0}'".F(targetType.Name));
}
// Look in the local cache first
var entity = set.Local.OfType<IActionTarget>()
.FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);
if (entity == null)
{
// If not found locally, hit the database
var querables = set as IQueryable<IActionTarget>;
if (querables != null)
{
entity = querables.FirstOrDefault(e => e.Id == key.Id && e.EffectiveDate == key.EffectiveDate);
}
}
return entity;
}
Ideally, I want to replace
var entity = set.Local.OfType<IActionTarget>()
.FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);
with
var entity = set.Local.OfType(targetType)
.FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);

I haven't compiled it so please excuse any formatting issues - can you use the dynamic type to achieve the same thing?
private IActionTarget DbContextGetEntity(Type targetType, IActionTarget key)
{
dynamic instance = Activator.CreateInstance(targetType);
return DbContextGetEntity(instance, key);
}
private IActionTarget DbContextGetEntity<T>(T instance, IActionTarget key)
where T : class, IActionTarget
{
var set = Context.Set<T>(targetType);
if (set == null)
{
throw new Exception();
}
// Look in the local cache first
var entity = set.Local
.FirstOrDefault(l => l.Id == key.Id && l.EffectiveDate == key.EffectiveDate);
if (entity == null)
{
// If not found locally, hit the database
entity = set
.FirstOrDefault(e => e.Id == key.Id && e.EffectiveDate == key.EffectiveDate);
}
return entity;
}

Related

How to count DbSet rows added to in a DbContext

Looked high and low but cannot find the syntax to count the number of rows in a [DbContext].[DbSet Entity] by the change status of the row. Specifically, I would like to know how many rows in one entity/table were added and are pending SaveChanges().
You can use one of the DbContext.ChangeTracker.Entries method overloads (similar to EF6):
var addedCount = db.ChangeTracker.Entries<YourEntity>()
.Count(e => e.State == EntityState.Added);
(First answer, so be gentle)
I had a similar problem. One approach is to override the context SaveChanges method as proposed here: https://www.exceptionnotfound.net/entity-change-tracking-using-dbcontext-in-entity-framework-6/
But in your situation a special method on your context class might be more appropriate:
public virtual Tuple<int, int> CountChanges()
{
var modified = ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).Count();
var added = ChangeTracker.Entries().Where(x => x.State == EntityState.Added).Count();
return new Tuple<int, int>(modified, added);
}

entity framework Remove vs EntityState.Deleted

What is the diff between these two statements?
Both should delete an entity.
_context.Entry(new Schoolyear { Id = schoolyearId }).State = EntityState.Deleted;
_context.Schoolyears.Remove(new Schoolyear { Id = schoolyearId });
and for those who does not know the EF Extensions:
_context.Schoolyears.Delete(s => s.Id == schoolyearId);
Thats even cooler :D
They are the same but both will fail. EF internally uses an ObjectManager to keep track of all elements used by EF. Entries to the ObjectManager are added by using retrieval functions of EF or by adding new entries to the EF with using _context.Schoolyears.Add(obj).
Referencing entries not stored in the object manager will usually create InvalidOperationException exceptions. The behavior of the following is similar:
Schoolyear year = context.Schoolyears.Single(x => x.Name == "2013");
_context.Schoolyears.Remove(year);
_context.SaveChanges();
or
Schoolyear year = context.Schoolyears.Single(x => x.Name == "2013");
_context.Entry(year).State = EntityState.Deleted;
_context.SaveChanges();
but EF does some more checks and status change activities in the first approach.
I would always prefer the first approach if possible.
On the other side of the game there's EntityFramework.Extended. This library allows mass updates/deletes on EF contexts.
This library does not use the ObjectManager, therefore you are allowed to use
_context.Schoolyears.Delete(s => s.Id == schoolyearId);
Hint: You can also use (preferred)
_context.Schoolyears.Where(s => s.Id == schoolyearId).Delete();
Warning: Please ensure that you do not manipulate objects at the same time in EF and EF.Extended. This Could cause unpredictable results or exceptions.

How do you get entity's schema name using Code First (Entity Framework) and DbContext?

Our database was designed in such a way where there are various schemas for production and various equivalent schemas for test. For example, many tables rest in MyProduction schema while the same tables live in MyTest schema.
What I want to do is determine which schema a table is using so I know which one to change it to. So, by default, everything will be under the production schemas. In the OnModelCreating event of the DbContext, if I need to point to test (determined by some true/false configuration), I need to determine the production schema being used, then point it to it's test equivalent.
I'm already aware of how to set the schema but can't find how to get it. Any ideas how I can determine the schema that a table is using?
Thank You.
Try below code after modifying according to you local settings:
var context = new YouDbContext("ConnectionName");
var adapter = (IObjectContextAdapter)context;
var objectContext = adapter.ObjectContext;
EntitySetBase schema = null;
if (objectContext.MetadataWorkspace != null)
{
schema = objectContext.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace).First()
.BaseEntitySets
.First(meta => meta.ElementType.Name == "ClassNameUnderYourDbContext");
}
//See the properties of schema in debug mode to understand details
Entity Framework schemas are System.ComponentModel.DataAnnotations.TableAttribute objects. Here are some methods you can use to get an entitity's schema name and table name. Cheers!
private string GetTableName(Type type)
{
var tableAttribute = type.GetCustomAttributes(false).OfType<System.ComponentModel.DataAnnotations.TableAttribute>().FirstOrDefault();
if (tableAttribute != null && !string.IsNullOrEmpty(tableAttribute.Name))
{
return tableAttribute.Name;
}
else
{
return string.Empty;
}
}
private string GetTableSchema(Type type)
{
var tableAttribute = type.GetCustomAttributes(false).OfType<System.ComponentModel.DataAnnotations.TableAttribute>().FirstOrDefault();
if (tableAttribute != null && !string.IsNullOrEmpty(tableAttribute.Schema))
{
return tableAttribute.Schema;
}
else
{
return string.Empty;
}
}
System.ComponentModel.DataAnnotations.Schema.TableAttribute

Problem using Include() when serializing Entity Framework 4 POCO classes with WCF

I have a WCF service with an Entity Framework 4 model, using POCO classes that are serialized and sent over to client applications. I have LazyLoadingEnabled and ProxyCreationEnabled set to false, and I'm using Linq to Entites to query an Entity, and return it via List<> to the client. Everything goes perfect when I don't use Include():
public List<TBLTable1> GetTBLTable1(string pCode)
{
using (PcFactoryEntities oPcFactoryDB = new PcFactoryEntities())
{
oPcFactoryDB.ContextOptions.ProxyCreationEnabled = false;
oPcFactoryDB.ContextOptions.LazyLoadingEnabled = false;
var oRS = oPcFactoryDB.TBLTable1
.Where(c => c.Code == pCode).ToList();
XmlObjectSerializer serializer = new DataContractSerializer(typeof(TBLTable1));
serializer.WriteObject(new XmlTextWriter(Console.Out) { Formatting = Formatting.Indented }, oRS[0]);
return oRS;
}
}
After the Linq query, I use the serializer to simulate the serialization process that happens when the POCO class is sent to the client, and I works great. However, when I add an Include() to load one of the navigation list for the class, it starts serializing all of Table2's navigation's list as if LazyLoadingEnabled was set to true, and it goes on forever serializing probably the whole database!
public List<TBLTable1> GetTBLTable1(string pCode)
{
using (PcFactoryEntities oPcFactoryDB = new PcFactoryEntities())
{
oPcFactoryDB.ContextOptions.ProxyCreationEnabled = false;
oPcFactoryDB.ContextOptions.LazyLoadingEnabled = false;
var oRS = oPcFactoryDB.TBLTable1
.Include("TBLTable2")
.Where(c => c.Code == pCode).ToList();
XmlObjectSerializer serializer = new DataContractSerializer(typeof(TBLTable1));
serializer.WriteObject(new XmlTextWriter(Console.Out) { Formatting = Formatting.Indented }, oRS[0]);
return oRS;
}
}
Why is this happening? Shouldn't the LazyLoadingEnabled set to false apply to the class included manually and return all of it's navigation lists to null as it happens with all of the other navigation lists for Table1? Is there a way to fix this so I can return with Table1 some navigations lists filled in with their navigation lists set to null?
Tks
Instead of trying to directly serialize the entity, try projecting to a DTO and serializing that. I agree what your seeing is bizarre behaviour - but it could be that the EF internal graph is taking over when your serializing the entities, but if you serialize a DTO, EF should not intervene.
E.g:
var dto = oPcFactoryDB.TBLTable1
.Where(x => x.Code == pCode)
.Select(x => new SpecialisedDTO
{
PropertyOne = x,
PropertyTwo = x.TBLTable2
}).ToList();
And then serialize that.
Since your projecting, you don't need to eager load - EF will grab what it needs to based on the query you have provided.
It's usually good practice in N-Tier situations to transmit DTO's over the wire, rather than the pure POCO entities.
Do you have a Navigation Property on TBLtable1 to TBLtable2? The .Include() is used to include entities that are linked va FK relationships and the .Include() is passed the Name of the Navigation Property.
So if you have a Person Entity with a NavigationProperty to an Addresses Entity called PersonAddresses you would then execute the following in order to get the Person and their addresses.
var p = dbContext.Person
.Where(x => x.Id == id)
.Include("PersonAddresses")
.SelectFirstOrDefault;

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();