我的一个查询遇到了一个奇怪的问题。当我第一次执行它时它可以工作,但所有后续调用都失败了
无法在表格'消息'。
中插入标识列的显式值
当我重新启动网络服务时,它也会再次运行。
我的表的架构是:
internal class Message
{
public long Id { get; set; }
public int TimeRangeId { get; set; }
public byte[] Body { get; set; }
public string BodyHash { get; set; }
public DateTime? DeletedOn { get; set; }
#region Navigation properties
public TimeRange TimeRange { get; set; }
#endregion
}
internal class Queue
{
public int Id { get; set; }
public string Name { get; set; }
#region Navigation properties
public TimeRange TimeRange { get; set; }
#endregion
}
internal class TimeRange
{
public int Id { get; set; }
public int QueueId { get; set; }
public DateTime StartsOn { get; set; }
public DateTime EndsOn { get; set; }
public DateTime CreatedOn { get; set; }
#region Navigation properties
public Queue Queue { get; set; }
public List<Message> Messages { get; set; }
#endregion
}
其中上下文设置如下:
internal class MessageQueueContext : DbContext
{
private readonly string _connectionString;
private readonly string _schema;
public MessageQueueContext(string connectionString, string schema)
{
_connectionString = connectionString;
_schema = schema;
}
public DbSet<TimeRange> TimeRanges { get; set; }
public DbSet<Message> Messages { get; set; }
public DbSet<Queue> Queues { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(_connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Message>(e =>
{
e.ToTable(nameof(Message), _schema).HasKey(t => t.Id);
e.Property(p => p.Id).UseSqlServerIdentityColumn();
e.Property(p => p.TimeRangeId).IsRequired();
e.Property(p => p.Body).IsRequired();
e.Property(p => p.BodyHash).IsRequired();
});
modelBuilder.Entity<TimeRange>(e =>
{
e.ToTable(nameof(TimeRange), _schema).HasKey(t => t.Id);
e.Property(p => p.Id).UseSqlServerIdentityColumn();
e.Property(p => p.StartsOn).IsRequired();
e.Property(p => p.EndsOn).IsRequired();
e.Property(p => p.CreatedOn).ValueGeneratedOnAdd();
});
modelBuilder.Entity<Queue>(e =>
{
e.ToTable(nameof(Queue), _schema).HasKey(t => t.Id);
e.Property(p => p.Id).UseSqlServerIdentityColumn();
e.Property(p => p.Name).IsRequired();
});
}
}
失败的查询就是这个:
public async Task<int> EnqueueAsync(DateTime timeRangeStartsOn, DateTime timeRangeEndsOn, IEnumerable<byte[]> bodies, CancellationToken cancellationToken)
{
const int nothingEnqueued = 0;
using (var context = new MessageQueueContext(_connectionString, _schema))
{
context.Queues.Add(_queue.Value);
context.Entry(_queue.Value).State = EntityState.Unchanged;
var messages =
(from body in bodies
let bodyHash = _computeBodyHash(body).ToHexString()
where true // CanEnqueueDuplicates || !context.Messages.AsNoTracking().Any(m => m.BodyHash == bodyHash && m.DeletedOn == null)
select new Message
{
Body = body,
BodyHash = bodyHash
}).ToList();
if (!CanEnqueueEmptyTimeRange && !messages.Any())
{
return nothingEnqueued;
}
var timeRange = new Entities.TimeRange
{
Queue = _queue.Value,
StartsOn = timeRangeStartsOn,
EndsOn = timeRangeEndsOn,
Messages = messages
};
await context.TimeRanges.AddAsync(timeRange, cancellationToken);
return await context.SaveChangesAsync(cancellationToken);
}
}
它正确生成Message.Id
,并且它也在 Identity 中正确设置在数据库中。
我使用 SQL Server Profiler 调试它,发现第二个请求与第一个请求不同:
这是第一个电话产生的内容:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [smq].[TimeRange] ([EndsOn], [QueueId], [StartsOn])
VALUES (@p0, @p1, @p2);
SELECT [Id], [CreatedOn]
FROM [smq].[TimeRange]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 datetime2(7),@p1 int,@p2 datetime2(7)',@p0='2018-05-02 00:00:00',@p1=1,@p2='2018-05-01 00:00:00'
exec sp_executesql N'SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] bigint, [_Position] [int]);
MERGE [smq].[Message] USING (
VALUES (@p3, @p4, @p5, @p6, 0),
(@p7, @p8, @p9, @p10, 1)) AS i ([Body], [BodyHash], [DeletedOn], [TimeRangeId], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Body], [BodyHash], [DeletedOn], [TimeRangeId])
VALUES (i.[Body], i.[BodyHash], i.[DeletedOn], i.[TimeRangeId])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;
SELECT [t].[Id] FROM [smq].[Message] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];
',N'@p3 varbinary(8000),@p4 varbinary(8000),@p5 datetime2(7),@p6 int,@p7 varbinary(8000),@p8 varbinary(8000),@p9 datetime2(7),@p10 int',@p3=0x7,@p4=0x7,@p5=NULL,@p6=21,@p7=0x7,@p8=0x1,@p9=NULL,@p10=21
这就是EF在所有后续调用中生成的内容:
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [smq].[Message] ([Id], [Body], [BodyHash], [DeletedOn], [TimeRangeId])
VALUES (@p0, @p1, @p2, @p3, @p4),
(@p5, @p6, @p7, @p8, @p9);
INSERT INTO [smq].[TimeRange] ([EndsOn], [QueueId], [StartsOn])
VALUES (@p10, @p11, @p12);
SELECT [Id], [CreatedOn]
FROM [smq].[TimeRange]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
',N'@p0 bigint,@p1 varbinary(8000),@p2 varbinary(8000),@p3 datetime2(7),@p4 int,@p5 bigint,@p6 varbinary(8000),@p7 varbinary(8000),@p8 datetime2(7),@p9 int,@p10 datetime2(7),@p11 int,@p12 datetime2(7)',@p0=21,@p1=0x7,@p2=0x7,@p3=NULL,@p4=21,@p5=22,@p6=0x7,@p7=0x1,@p8=NULL,@p9=21,@p10='2018-05-02 00:00:00',@p11=1,@p12='2018-05-01 00:00:00'
我错过了明显的吗?呼叫不同看起来并不正常。他们不应该,我是对的吗?
如果您需要任何其他信息,请通知我,我会修改此问题。
答案 0 :(得分:1)
我发现了这个错误。
private readonly Lazy<Entities.Queue> _queue;
public async Task<int> EnqueueAsync(DateTime timeRangeStartsOn, DateTime timeRangeEndsOn, IEnumerable<byte[]> bodies, CancellationToken cancellationToken)
{
const int nothingEnqueued = 0;
using (var context = new MessageQueueContext(_connectionString, _schema))
{
context.Queues.Add(_queue.Value);
context.Entry(_queue.Value).State = EntityState.Unchanged;
最后两行我将有关当前队列的信息添加到上下文中。由于这是一个字段,它似乎在上下文之间存活,并以某种方式使其受损。
我将此更改为
context.Queues.Attach(new Entities.Queue
{
Id = _queue.Value.Id,
Name = _queue.Value.Name
});
现在它没有问题。简单地附加不会修复错误(它比添加和更改状态更容易)。
真正的修复是创建实体的副本,因为db-context不喜欢它的某些内容。我想人们需要知道EF的内部才能理解它......