我正在使用Entity Framework Core设计数据库,该数据库应包含两种实体类型:
ChannelId
ChannelId
和MessageId
MessageId
对于每个频道都必须是唯一的 ,并且应从1开始计数。
我第一次尝试实现此目的是对Message
和ChannelId
的{{1}}实体使用复合键,但是不必保持这种方式。但是,我不知道如何使用EF Core自动生成MessageId
。
因此,我尝试获取当前频道的最后一个MessageId
,并将其递增并尝试插入:
MessageId
此代码无效。发生异常后,EF Core不会插入ID递增的项目。除此之外,在并发插入的情况下似乎效率很低。
当我在message表中使用一个附加ID作为主键,也许还有一些附加表时,是否存在一种更优雅的解决方案来解决此问题?
答案 0 :(得分:1)
经过长期研究,我找到了解决该问题的方法:
我在MessageIdCounter
表中添加了Channels
行。
与经典代码不同,SQL允许原子条件写。这可以用于乐观并发处理。首先,我们读取计数器值并将其递增。然后,我们尝试应用更改:
UPDATE Channels SET MessageIdCounter = $incrementedValue
WHERE ChannelId = $channelId AND MessageIdCounter = $originalValue;
数据库服务器将返回更改数量。如果未进行任何更改,则MessageIdCounter
同时必须已更改。然后,我们必须再次运行该操作。
实体:
public class Channel
{
public long ChannelId { get; set; }
public long MessageIdCounter { get; set; }
public IEnumerable<Message> Messages { get; set; }
}
public class Message
{
public long MessageId { get; set; }
public byte[] Content { get; set; }
public long ChannelId { get; set; }
public Channel Channel { get; set; }
}
数据库上下文:
public class DatabaseContext : DbContext
{
public DbSet<Channel> Channels { get; set; }
public DbSet<Message> Messages { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var channel = builder.Entity<Channel>();
channel.HasKey(c => c.ChannelId);
channel.Property(c => c.MessageIdCounter).IsConcurrencyToken();
var message = builder.Entity<Message>();
message.HasKey(m => new { m.ChannelId, m.MessageId });
message.HasOne(m => m.Channel).WithMany(c => c.Messages).HasForeignKey(m => m.ChannelId);
}
}
实用方法:
/// <summary>
/// Call this method to retrieve a MessageId for inserting a Message.
/// </summary>
public long GetNextMessageId(long channelId)
{
using (DatabaseContext ctx = new DatabaseContext())
{
bool saved = false;
Channel channel = ctx.Channels.Single(c => c.ChannelId == channelId);
long messageId = ++channel.MessageIdCounter;
do
{
try
{
ctx.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
const string name = nameof(Channel.MessageIdCounter);
proposedValues[name] = messageId = (long)databaseValues[name] + 1;
entry.OriginalValues.SetValues(databaseValues);
}
} while (!saved);
return messageId;
}
}
为了成功使用EF Core的并发令牌,我必须将MySQL的事务隔离至少设置为
READ COMMITTED
。
可以使用EF Core为每个外键实现增量ID。
此解决方案不是完美的,因为它一次插入需要两个事务,因此比自动增量行要慢。此外,当应用程序在插入消息时崩溃时,可能会跳过MessageId
s。