entity-framework - library - delete multiple rows using entity framework
¿Cómo elimino varias filas en Entity Framework(sin foreach)? (19)
Estoy eliminando varios elementos de una tabla usando Entity Framework. No hay una clave externa / objeto principal, así que no puedo manejar esto con OnDeleteCascade
Ahora mismo estoy haciendo esto:
var widgets = context.Widgets
.Where(w => w.WidgetId == widgetId);
foreach (Widget widget in widgets)
{
context.Widgets.DeleteObject(widget);
}
context.SaveChanges();
Funciona pero el foreach me molesta. Estoy usando EF4 pero no quiero ejecutar SQL. Solo quiero asegurarme de que no me esté perdiendo nada, esto es lo mejor que se puede hacer, ¿verdad? Puedo abstraerlo con un método de extensión o ayudante, pero en algún lugar todavía estaremos haciendo un foreach, ¿verdad?
Esto es tan bueno como se pone, ¿verdad? Puedo abstraerlo con un método de extensión o ayudante, pero en algún lugar todavía estaremos haciendo un foreach, ¿verdad?
Bueno, sí, excepto que puedes convertirlo en dos líneas:
context.Widgets.Where(w => w.WidgetId == widgetId)
.ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
Aún parece una locura tener que retirar algo del servidor solo para borrarlo, pero al menos recuperar solo las ID es mucho más simple que bajar todas las entidades:
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 });
EF 6. =>
var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();
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();
}
Uso:
// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");
Or:
// delete all from entity
DeleteWhere<MyEntity>();
EntityFramework 6 ha hecho esto un poco más fácil con .RemoveRange()
.
Ejemplo:
db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();
La forma más rápida de eliminar es mediante un procedimiento almacenado. Prefiero los procedimientos almacenados en un proyecto de base de datos en lugar de SQL dinámico porque los nombres se manejarán correctamente y tendrán errores de compilación. SQL dinámico podría referirse a tablas que han sido eliminadas / renombradas causando errores de tiempo de ejecución.
En este ejemplo, tengo dos tablas List y ListItems. Necesito una forma rápida de eliminar todos los ListItems de una lista dada.
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
Ahora, la parte interesante de eliminar los elementos y actualizar el marco de Entity utilizando una extensión.
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;
}
}
}
El código principal ahora puede usarlo es como
[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);
}
}
Mejor: in EF6 => .RemoveRange()
Ejemplo:
db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));
Para EF 4.1,
var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");
Para cualquier persona que use EF5, se puede usar la siguiente biblioteca de extensión: https://github.com/loresoft/EntityFramework.Extended
context.Widgets.Delete(w => w.WidgetId == widgetId);
Puede ejecutar consultas SQL directamente de la siguiente manera:
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;
}
Para seleccionar podemos usar
using (var context = new MyContext())
{
var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList();
}
Puede usar las bibliotecas de extensiones para hacer eso como EntityFramework.Extended o Z.EntityFramework.Plus.EF6, que están disponibles para EF 5, 6 o Core. Estas bibliotecas tienen un gran rendimiento cuando tienes que eliminar o actualizar y usan LINQ. Ejemplo para borrar ( fuente plus ):
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();
o ( https://github.com/loresoft/EntityFramework.Extended )
context.Users.Where(u => u.FirstName == "firstname") .Delete();
Estos utilizan instrucciones SQL nativas, por lo que el rendimiento es excelente.
Sé que es bastante tarde, pero en caso de que alguien necesite una solución simple, lo bueno es que también puede agregar la cláusula where:
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);
}
Nota: acabo de probar con MSSQL2008.
Actualización: la solución anterior no funcionará cuando EF genere una instrucción sql con parámetros , así que aquí está la actualización para 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);
}
Requiere un poco de reflexión pero funciona bien.
Si desea eliminar todas las filas de una tabla, puede ejecutar el comando sql
using (var context = new DataDb())
{
context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}
TRUNCATE TABLE (Transact-SQL) Elimina todas las filas de una tabla sin registrar las eliminaciones de filas individuales. TRUNCATE TABLE es similar a la sentencia DELETE sin una cláusula WHERE; sin embargo, TRUNCATE TABLE es más rápido y utiliza menos recursos del sistema y de registro de transacciones.
Si no desea ejecutar SQL directamente, llamar a DeleteObject en un bucle es lo mejor que puede hacer hoy.
Sin embargo, puede ejecutar SQL y seguir siendo completamente de propósito general a través de un método de extensión, utilizando el enfoque que describo here .
Aunque esa respuesta fue para 3.5. Para 4.0 probablemente usaría la nueva API ExecuteStoreCommand bajo el capó, en lugar de bajar a StoreConnection.
También puede usar el método DeleteAllOnSubmit () pasándole los resultados en una lista genérica en lugar de en var. De esta manera su foreach se reduce a una línea de código:
List<Widgets> widgetList = context.Widgets
.Where(w => w.WidgetId == widgetId).ToList<Widgets>();
context.Widgets.DeleteAllOnSubmit(widgetList);
context.SubmitChanges();
Probablemente todavía usa un bucle internamente.
Ver la respuesta ''bit de código favorito'' que funciona.
Aquí es cómo lo usé:
// 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;
}
UUHHIVS
''s es una forma muy elegante y rápida para eliminar por lotes, pero debe usarse con cuidado:
- Auto generación de transacción: sus consultas serán abarcadas por una transacción.
- Independencia del contexto de la base de datos: su ejecución no tiene nada que ver con el
context.SaveChanges()
Estos problemas pueden ser evitados tomando el control de la transacción. El siguiente código ilustra cómo eliminar por lotes y realizar inserciones masivas de manera transaccional:
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();
}
int id = 5;
db.tablename.RemoveRange(db.tablename.Where(c => c.firstid == id));
using (var context = new DatabaseEntities())
{
context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}