簡體   English   中英

EF Core 中每個外鍵的順序 ID

[英]Sequential ID per foreign key in EF Core

我正在設計一個帶有 Entity Framework Core 的數據庫,它應該包含兩種實體類型:

  1. 具有唯一ChannelId名為“Channel”的實體
  2. 帶有外鍵ChannelIdMessageId名為“Message”的實體

每個頻道MessageId必須是唯一,並且應該從 1 開始計數。

我第一次嘗試實現這一點是對帶有ChannelIdMessageIdMessage實體使用復合鍵,但它不必保持這種方式。 但是我不知道如何使用 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM