LINQ:如果ID在我之前查询过的列表中,我该如何在表中查找记录



假设我有一个表

ID1 文本
1 aaa
2 bbb

给定一个a的列表,前提是我需要从我想要删除的a中手动删除所有B和C,而无需级联删除功能,理想情况下无需加载所有数据。。我希望确保我的A、B和C实体设置有导航属性,因此A将具有ICollection<B>,B将具有ICollection<C>。如果当前的DbContext和实体定义不满足此要求,则可以使用新的基本实体定义为此操作创建新的有界DbContext。由于这只用于发出Delete操作,您只需要声明实体及其PK列和虚拟集合。如果您已经用导航属性设置了完整的实体,那么它可以与完整的实体一起工作,在每个实体中加载/存储所有列而不仅仅是ID只是一个开销问题。

例如:

public class A
{
[Key]
public int Id { get; set; }
public virtual ICollection<B> Bs { get; set; } = new List<B>();
}
public class B
{
[Key]
public int Id { get; set; }
public virtual ICollection<C> Cs { get; set; } = new List<C>();
}
public class C
{
[Key]
public int Id { get; set; }
}
// EF6 example
public class DeleteBoundedDbContext : DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<C> Cs { get; set; }
public DeleteBoundedDbContext()
: base ("AppDbContext")  // Use the name of your main application DbContext to use the same connection string...
{ }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>()
.HasMany(a => a.Bs)
.WithRequired()
.Map(a => a.MapKey("AId")); // FK on B pointing to it's A
modelBuilder.Entity<B>()
.HasMany(b => b.Cs)
.WithRequired()
.Map(b => b.MapKey("BId")); // FK on C pointing to it's B
}
}

然后获取我们的数据:

var dataToDelete = context.As
.Where(/* insert criteria */)
.Select(a => new 
{
A = a,
Bs = a.Bs,
Cs = a.Bs.SelectMany(b => b.Cs).ToList()
).ToList();
foreach(var a in dataToDelete)
{
context.Cs.RemoveRange(a.Cs);
context.Bs.RemoveRange(a.Bs);
context.As.Remove(a.A);
}
context.SaveChanges();

如果实体的数量预计可能很大(超过1000个),那么您应该考虑使用Take分阶段处理并一次提交几个a。

使用一个单独的DbContext,为相关项目提供最少的实体定义,这意味着您可以快速加载这些实体引用,并且使用最少的内存。我们选择";A";s、 连同它们的每一组";B";s、 然后;C";s与所有这些B相关联。从那里,通过为每个定义DbSet,我们可以使用RemoveRange来告诉EF删除它们。所有的C后面跟着B,后面跟着每个相关的A.

如果使用单独的DbContext方法并且仍在从主DbContext查询数据,则需要删除并重新创建主应用程序DbContext。理想情况下,这将是一个针对给定请求的操作。

与SQL不同,在实体框架中,您只能移除以前提取的对象。在SQL中,您可以说:;请删除所有纽约客户";。在实体框架中,您首先必须获取所有纽约客户,然后才能删除他们。

直接回答您的问题

不是最佳!见下文

所以你有三张桌子,它们之间有一对多的关系。您的表有点抽象,所以为了更容易理解,我将其更改为表Customers,其中每个Customer有零个或多个Orders,每个Order有零个以上OrderLines。每个OrderLine都是Order中的OrderLine,使用外键,每个Order都是Customer的Order,也使用外键。

显然,您想要删除一个客户(表a中的一项)。您声明,在删除此客户之前,您必须删除他的所有订单和订单行。

稍后我将向您展示,如果您使用级联,则这是不必要的。但首先我会给你看你问题的答案。

在移除项目之前,您必须将其完全取出。因此,要删除一个客户,您必须从他的订单中获取该客户、他的所有订单和所有订单行。

在您的案例中:表A中的项目,表B中引用该项目的所有项目,以及表C中引用表B中项目的所有项(幸运的是,客户订单数据库更容易描述)。

int customerId = 54;
using (var dbContext = new OrderDbContext())
{
var customerToRemove = dbContext.Customers.Find(customerId);
// or use a Where(...).FirstOrDefault();
// fetch all Orders that have a foreign key referring to the Customer:
var ordersToRemove = dbContext.Orders
.Where(order => order.CustomerId == customerId)
.ToList();
// fetch all OrderLines (table C) that refer to one of the Ids of the Orders that
// must be removed
// Orders are already local, so this is not a database action:
var orderIdsToRemove = ordersToRemove.Select(order => order.Id).ToList();
var orderLinesToRemove = dbContext.OrderLines
.Where(orderLine => orderIdsToRemove.Contains(orderLine.Id))
.ToList();

大写:

  • 获取主键等于customerId的Customer
  • 获取外键等于customerId的所有订单
  • 优化:从所有提取的订单中提取所有主键
  • 获取在提取的主键中具有外键的所有OrderLines

现在删除所有项目:

dbContext.OrderLines.RemoveRange(orderLinesToRemove);
dbContext.Orders.RemoveRange(ordersToRemove);
dbContext.Customer.Remove(customerToRemove);

SaveChanges和ChangeTracker

注意:到目前为止,数据库尚未更改。DbContext具有ChangeTracker。每当您从数据库中提取一个完整的项目时(=使用"查找",或在不使用"选择"的情况下提取),该项目都会被放入ChangeTracker中,同时也是一个副本。您可以在ChangeTracker中获得对原始项目的引用。因此,每当您更改某个提取项的属性值时,它都会在ChangeTracker中更改。

已添加的项目仅添加到ChangeTracker,状态为:已添加。已删除的项目已在ChangeTracker中。他们得到一个状态:删除。

因此,所有属性更改、所有添加/删除都是在没有数据库的情况下完成的。它们都是在ChangeTracker中完成的。

保存所有更改通过:完成

dbContext.SaveChanges();

这将检查ChangeTracker以查看添加/删除了哪些项目。在所有其他项目中,它将通过比较原始和副本来检查哪些属性已更改。所有这些更改都被转换为一个大型SQL命令,并执行该命令。

所以现在你知道如何按照你建议的方式删除这些项目了。

还有改进的空间

事务:因为所有更改都在SaveChanges中执行,所以在获取项时不需要定义事务。我敢肯定,因为更改是在一个DbCommand中执行的,所以您根本不需要它,除非您要执行几个SaveChanges。

附带说明:如果您不打算更改提取的数据,请始终使用Select,并且只选择您实际计划使用的属性。通过使用Select,数据不会被放入ChangeTracker中,也不会被复制。保存更改时,需要比较的项目较少。

如果你从一个客户那里获取100个订单,每个订单都有几个OrderLines,这将是一个巨大的优化:所有100个订单和所有100个OrderLines都必须放在ChangeTracker中以及一个副本中。此外:客户[42]的每个订单都有一个值为42的外键。从数据库中提取这个值100次是浪费。

优化:使用虚拟ICollection一次获取所有数据

如果您遵循了实体框架约定,您将拥有类似于以下的类:

class Customer
{
public int Id {get; set;}
public string Name {get; set;}
...
// Every Customer has zero or more Orders (one-to-many)
public virtual ICollection<Order> Orders {get; set;}
}
class Order
{
public int Id {get; set;}
public int OrderNumber {get; set;}
public DateTime OrderDate {get; set;}
...
// Every Order is the Order of exactly one Customer, using foreign key:
public int CustomerId {get; set;}
public virtual Customer Customer {get; set;}
// Every Customer has zero or more Orders (one-to-many)
public virtual ICollection<Order> Orders {get; set;}
}

订单行:

class OrderLine
{
public int Id {get; set;}
...
// Every OrderLine is an OrderLine in exactly one Order, using foreign key:
public int OrderId {get; set;}
public virtual Order Order {get; set;}
}

在实体框架中,表中的列由非虚拟属性表示。虚拟属性表示表之间的关系(一对多、多对多)

现在要一次性获取所有内容,请使用Include

int customerId = 42;
var customerToRemove = dbContext.Customers
.Where(customer => customer.Id == customerId)
.Include(customer => customer.Order.OrderLines)
.ToList();

现在,如果您愿意,您可以删除所有订单行,然后删除所有订单,最后删除客户

最佳优化:使用CascadeOnDelete

大多数数据库都有可能自动删除依赖项:如果删除Customer,它的所有Orders都会被删除,而且因为OrderLines依赖于Orders,所以它们的所有OrderLines也会被删除。代码将类似于:

int customerId = 54;
using (var dbContext = new OrderDbContext())
{
Customer customerToRemove = dbContext.Customers.Find(customerId);
dbContext.Customers.Remove(customerToRemove);
dbContext.SaveChanges();
}

因为使用了CascadeOnDelete,所以您不必将所有订单和订单行提取到本地流程中。当您删除客户时,数据库将自动删除它们。

如果您遵循了实体框架约定,那么这种行为将是标准的。你也可以在DbContext.OnModelCreating 中使用流利的API强制它

// Every Customer has zero or more Orders (one-to-many)
modelBuilder.Entity<Customer>()
.HasMany(customer => customer.Orders)
.WithRequired(order => order.Customer)
.HasForeignKey(order => order.CustomerId)
.WillCascadeOnDelete(true);

换句话说:您可以在属性Orders中找到客户的订单。每个订单都是一个客户的订单。此订单的客户可以在属性Customer中找到。在数据库中,这是使用外键CustomerId完成的。最后:如果您删除该客户,则该客户的所有订单也必须删除。

我的建议是:每当你有一对多关系时,打开删除级联。这会让删除变得更容易!

最新更新