[英]Sequential ID per foreign key in EF Core
我正在設計一個帶有 Entity Framework Core 的數據庫,它應該包含兩種實體類型:
ChannelId
名為“Channel”的實體ChannelId
和MessageId
名為“Message”的實體每個頻道的MessageId
必須是唯一的,並且應該從 1 開始計數。
我第一次嘗試實現這一點是對帶有ChannelId
和MessageId
的Message
實體使用復合鍵,但它不必保持這種方式。 但是我不知道如何使用 EF Core 自動生成MessageId
。
所以我試圖獲取當前頻道的最后一個MessageId
增加它並嘗試插入:
public class DatabaseContext : DbContext
{
public void AddMessage(Message message)
{
long id = Messages
.Where(m => m.ChannelId == message.ChannelId)
.Select(m => m.MessageId)
.OrderByDescending(i => i)
.FirstOrDefault()
+ 1;
while (true)
{
try
{
message.MessageId = id;
Messages.Add(insert);
SaveChanges();
return;
}
catch
{
id++;
}
}
}
}
此代碼不起作用。 發生異常后,EF Core 不會插入具有遞增 ID 的項目。 除此之外,在並發插入的情況下似乎效率很低。
當我在消息表中使用附加 ID 作為主鍵或者一些附加表時,是否有更優雅的解決方案來解決這個問題?
經過長時間的研究,我找到了解決問題的方法:
我在我的Channels
表中添加了一個MessageIdCounter
行。
與經典代碼不同,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
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.