c# - 无法使用实体框架删除行
问题描述
尝试使用 Entity Framework 删除行时遇到问题。它说我遇到了并发问题,但我不明白为什么会这样。
using (AppHost_DataHubEntities db = new AppHost_DataHubEntities())
{
DateTime tooOld = (DateTime.Now - HISTORY_TIME_SPAN);
var asd = db.FromDataHub.RemoveRange(db.FromDataHub.Where(x => x.DateTime < tooOld));
db.SaveChanges();
}
在这里,SaveChanges()
抛出以下错误:
存储更新、插入或删除语句影响了意外数量的行 (0)。自加载实体后,实体可能已被修改或删除。有关了解和处理选项的信息,请参阅http: //go.microsoft.com/fwlink/ ?LinkId=472540...
据我了解,这意味着我试图删除的行已被修改。但是,如果这是我机器上只有这个应用程序可以访问的开发数据库,这怎么可能呢?
我还查看了行的状态。并在通话后正确更改为“已删除” RemoveRange()
。
对此有什么想法吗?
如果需要,很乐意提供更多信息。
编辑:
DbContext 定义:
public partial class AppHost_DataHubEntities : DbContext
{
public AppHost_DataHubEntities()
: base("name=AppHost_DataHubEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<FromDataHub> FromDataHub { get; set; }
public virtual DbSet<Point> Point { get; set; }
public virtual DbSet<ToDataHub> ToDataHub { get; set; }
}
ERD 的屏幕截图:
重现项目:
- 根目录中的 SQL 设置脚本。
- 使用了 SQL Express。
- .NET 版本无关紧要(用 4.5、4.6 和 4.7 测试)
解决方案
有几件事要尝试,评论太多了:
尝试删除单个“旧”项:
using (AppHost_DataHubEntities db = new AppHost_DataHubEntities())
{
DateTime tooOld = (DateTime.Now - HISTORY_TIME_SPAN);
var item = db.FromDataHub.Where(x => x.DateTime < tooOld).First();
db.FromDataHub.Remove(item);
db.SaveChanges();
}
它会删除预期的行吗?它似乎改变了相关点中的任何内容吗?
接下来,尝试一个范围:
using (AppHost_DataHubEntities db = new AppHost_DataHubEntities())
{
DateTime tooOld = (DateTime.Now - HISTORY_TIME_SPAN);
var items = db.FromDataHub.Where(x => x.DateTime < tooOld).Take(3).ToList();
db.FromDataHub.RemoveRange(items);
db.SaveChanges();
}
同样检查任何错误或意外修改?这些“点”记录中的每一个都涉及到哪些“点”记录?同一个?不同的?
接下来,检查数据是否有可能删除所有From 记录,一个点的所有项目早于阈值,获取该点 ID:
using (AppHost_DataHubEntities db = new AppHost_DataHubEntities())
{
DateTime tooOld = (DateTime.Now - HISTORY_TIME_SPAN);
var items = db.FromDataHub.Where(x => x.DateTime < tooOld && x.PointId == pointId)..ToList();
db.FromDataHub.RemoveRange(items);
db.SaveChanges();
}
这行得通吗?
基本上,当遇到此类问题时,您需要计算出最小公分母,以查看是否存在映射问题或在所有情况下都会导致错误,或者在数据可能存在的情况下导致错误的特定数据组合确定删除。
如果您无法删除单行,则映射中的某些内容正在阻止它。如果您可以删除单行,但某个范围会导致问题,无论是跨单个点还是不同点,则需要进一步钻探以确定原因。如果这三个都有效,您可以尝试在呼叫中添加.ToList()
内部。DeleteRange
通常,我不会对 EF 进行批量删除以进行维护,而是设置一个计划的作业以在数小时后直接针对数据库运行。如果我确实想在 EF 中进行批量删除,我通常会将其分解为工作块。例如:
List<FromDataHub> itemsToClean = new List<FromDataHub>();
using(var context = new AppHost_DataHubEntities())
{
itemsToClean = context.FromDataHub
.Where(x => x.DateTime < tooOld)
.Select(x => new FromDataHub { PointId = x.PointId, DateTime = x.DateTime })
.ToList();
}
const int batchSize = 500;
int batchCount = 1;
var batch = itemsToClean.Take(batchSize);
while(batch.Any())
{
using(var context = new AppHost_DataHubEntities())
{
foreach(var item in batch)
{
context.FromDataHub.Attach(item);
context.Entity(item).EntityState = EntityState.Deleted;
}
context.SaveChanges();
}
batch = itemsToClean.Skip(++batchCount).Take(batchSize);
}
该代码基本上不在脑海中,而且已经很晚了,但应该给出这个想法。对于潜在的大型实体,我会考虑特定于此目的的有界上下文,其中为该上下文注册的已定义“FromDataHub”实体仅包含 ID 列。(在这种情况下,您可以将其简化为批量读取和删除。此示例所做的是执行查询以仅返回相关的 ID 列(假设为 PointId 和 DateTime)并从中填充分离的实体。然后分批500 它打开一个新的 DbContext,附加该实体,将其修改状态设置为已删除,然后保存批处理。这是为了防止在 DbContext 实例中跟踪大量实体,以防此代码需要删除大量行。再次,
更新:您将需要更新您的示例以提供完整且最小的可重现示例。我已经根据您提供的内容设置了一个架构,但无法使用 EF 重现该问题:
实体:
[Table("Points")]
public class Point
{
[Key]
public int PointId { get; set; }
public string PointName { get; set; }
public bool Collect { get; set; }
public virtual ICollection<FromDataHub> FromDataHubs { get; set; } = new List<FromDataHub>();
public virtual ICollection<ToDataHub> ToDataHubs { get; set; } = new List<ToDataHub>();
}
[Table("FromDataHub")]
public class FromDataHub
{
[Key, Column(Order = 0), ForeignKey("Point")]
public int PointId { get; set; }
[Key, Column(Order = 1)]
public DateTime DateTime { get; set; }
[Key, Column(Order = 2)]
public int Value { get; set; }
public virtual Point Point { get; set; }
}
[Table("ToDataHub")]
public class ToDataHub
{
[Key, Column(Order = 0), ForeignKey("Point")]
public int PointId { get; set; }
[Key, Column(Order = 1)]
public DateTime DateTime { get; set; }
public virtual Point Point { get; set; }
}
带映射:
modelBuilder.Entity<Point>()
.HasMany(x => x.FromDataHubs)
.WithRequired(x => x.Point);
modelBuilder.Entity<Point>()
.HasMany(x => x.ToDataHubs)
.WithRequired(x => x.Point);
...这是完全可选的,它在没有显式映射的情况下工作。
考试:
using (var context = new TestDbContext())
{
DateTime date = DateTime.Today.AddMonths(-6);
var itemsToDelete = context.FromDataHubs.Where(x => x.DateTime < date);
context.FromDataHubs.RemoveRange(itemsToDelete);
context.SaveChanges();
}
在用几个 Points 和少量 From 和 To DataHub 记录(范围小于和大于 6 个月)为表播种后,运行此代码成功删除了旧记录,没有任何异常。
如果您的类与上述有显着差异,请发布您当前使用的实体定义和任何映射配置。
更新#2:
好吧,哇,这是一个谜。我能够发现的是,由于某种原因,这个问题似乎是您的数据库所特有的。我修改了我的架构以匹配您的架构,并修改了您的数据库以消除可能的怪异。我还将您的测试数据复制到我的数据库中,以防它是与数据相关的问题。结果是我对您的数据库的测试继续失败并出现相同的异常,而您的应用程序针对我的数据库成功。
您的架构:
USE [StackExample]
GO
/****** Object: Table [dbo].[FromDataHub] Script Date: 25/11/2020 8:50:04 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[FromDataHub](
[PointId] [int] NOT NULL,
[DateTime] [datetime] NOT NULL,
[Value] [float] NOT NULL,
CONSTRAINT [PK_FromDataHub] PRIMARY KEY CLUSTERED
(
[PointId] ASC,
[DateTime] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[Point] Script Date: 25/11/2020 8:50:04 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Point](
[PointId] [int] IDENTITY(1,1) NOT NULL,
[PointName] [varchar](255) NOT NULL,
[Collect] [bit] NOT NULL,
CONSTRAINT [PK_Point] PRIMARY KEY CLUSTERED
(
[PointId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[ToDataHub] Script Date: 25/11/2020 8:50:04 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ToDataHub](
[PointId] [int] NOT NULL,
[Value] [float] NOT NULL,
CONSTRAINT [PK_ToDataHub] PRIMARY KEY CLUSTERED
(
[PointId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[FromDataHub] WITH CHECK ADD CONSTRAINT [FK_FromDataHub_Point] FOREIGN KEY([PointId])
REFERENCES [dbo].[Point] ([PointId])
GO
ALTER TABLE [dbo].[FromDataHub] CHECK CONSTRAINT [FK_FromDataHub_Point]
GO
ALTER TABLE [dbo].[ToDataHub] WITH CHECK ADD CONSTRAINT [FK_ToDataHub_Point] FOREIGN KEY([PointId])
REFERENCES [dbo].[Point] ([PointId])
GO
ALTER TABLE [dbo].[ToDataHub] CHECK CONSTRAINT [FK_ToDataHub_Point]
GO
我的架构
USE [Spikes]
GO
/****** Object: Table [dbo].[FromDataHub] Script Date: 25/11/2020 8:50:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[FromDataHub](
[PointId] [int] NOT NULL,
[DateTime] [datetime] NOT NULL,
[Value] [float] NOT NULL,
CONSTRAINT [PK_FromDataHub_1] PRIMARY KEY CLUSTERED
(
[PointId] ASC,
[DateTime] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[Point] Script Date: 25/11/2020 8:50:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Point](
[PointId] [int] NOT NULL,
[PointName] [nvarchar](20) NOT NULL,
[Collect] [bit] NOT NULL,
CONSTRAINT [PK_Points] PRIMARY KEY CLUSTERED
(
[PointId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[ToDataHub] Script Date: 25/11/2020 8:50:44 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ToDataHub](
[PointId] [int] NOT NULL,
[Value] [float] NOT NULL,
CONSTRAINT [PK_ToDataHub_1] PRIMARY KEY CLUSTERED
(
[PointId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Point] ADD CONSTRAINT [DF_Points_Collect] DEFAULT ((1)) FOR [Collect]
GO
ALTER TABLE [dbo].[FromDataHub] WITH CHECK ADD CONSTRAINT [FK_FromDataHub_Points] FOREIGN KEY([PointId])
REFERENCES [dbo].[Point] ([PointId])
GO
ALTER TABLE [dbo].[FromDataHub] CHECK CONSTRAINT [FK_FromDataHub_Points]
GO
ALTER TABLE [dbo].[ToDataHub] WITH CHECK ADD CONSTRAINT
[FK_ToDataHub_Point] FOREIGN KEY([PointId])
REFERENCES [dbo].[Point] ([PointId])
GO
ALTER TABLE [dbo].[ToDataHub] CHECK CONSTRAINT [FK_ToDataHub_Point]
GO
两者的数据是相同的。我没有更改的唯一架构差异是 PointName 的数据类型/大小。我对 Collect 值也有默认约束。在这两种情况下,原始模式的更改基本上是将点 ID PK 和 FK 从“ID”和“Point”更改为“PointId”。最初我的架构没有声明 FK。
我要注意的一件事是我无法让您的 EDMX 开箱即用。我注意到它声明的实体缺少实体的任何 Key 或 FK 声明。我不得不手动添加这些并禁用 CodeFirst 断言:
public AppHost_DataHubEntities : base("name=AppHost_DataHubEntities")
{
Database.SetInitializer<AppHost_DataHubEntities>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//throw new UnintentionalCodeFirstException();
modelBuilder.Entity<Point>()
.ToTable("Point")
.HasKey(x => x.PointId)
.HasMany(x => x.FromDataHub)
.WithRequired(x => x.Point)
.HasForeignKey(x => x.PointId);
//modelBuilder.Entity<Point>()
// .HasKey(x => x.ID)
// .HasOptional(x => x.ToDataHub)
// .WithRequired(x => x.Point1);
modelBuilder.Entity<FromDataHub>()
.ToTable("FromDataHub")
.HasKey(x => new { x.PointId, x.DateTime });
modelBuilder.Entity<ToDataHub>()
.ToTable("ToDataHub")
.HasKey(x => new { x.PointId });
}
测试代码:
using (var context = new SoDbContext())
{
DateTime tooOld = DateTime.Now - new TimeSpan(0, 15, 0);
var items = context.FromDataHubs.Where(x => x.DateTime < tooOld).ToList();
//db.FromDataHub.RemoveRange(items);
context.FromDataHubs.Remove(items.First());
context.SaveChanges();
}
我还将 DbContext 上的初始化程序设置为 null 并删除了迁移,但错误仍然存在于您的测试数据库中。因此,如果我将您的应用程序配置指向您的数据库,我会收到错误消息。如果我将它指向我的“Spikes”数据库,那么它可以毫无问题地运行。我之前的单元测试也是如此。
关于我唯一可以建议的是避免 EDMX & Code First 生成并手动创建所需的模式,将 EF 实体映射到它。生成的模式中似乎有一些微妙的东西,奇怪的是在为表或数据库级别生成的 SQL 中并不明显?
推荐阅读
- mysql - 连接到我的在线 mysql 数据库 (phpmyadmin)
- r - 使用 coeftest() 和异方差的 R 中面板数据的双聚类标准(时间和组)误差
- node.js - 如何在 Meteor 中使用带有 socket.io 的 ssh2 确保单个私有 ssh 连接
- postgresql - POSTGRESQL:如果具有相同的条件,则使用相同的数字枚举
- python - 如何在 Python-3.7 中修复 Tkinter?
- arrays - 为什么 - 会以正数返回正数?
- html - 我正在使用箭头键(向上,向下)导航 html,焦点将转到每个 div 元素。我想跳过html页面中一个div元素的焦点
- android - 从膨胀的 XML 将适配器设置为 ListView 以打印 PDF - 缺少项目
- react-native - 如何使用 wix react-native-navigation 获得 bottomTab 按下操作?
- python - 不允许空格和除下划线以外的特殊符号的正则表达式