EF Core中每个外键的顺序ID

时间:2018-11-09 10:27:56

标签: c# entity-framework-core

我正在使用Entity Framework Core设计数据库,该数据库应包含两种实体类型:

  1. 名为“ Channel”的实体,具有唯一的ChannelId
  2. 名为“ Message”的实体,带有外键ChannelIdMessageId

MessageId对于每个频道都必须是唯一的 ,并且应从1开始计数。

我第一次尝试实现此目的是对MessageChannelId的{​​{1}}实体使用复合键,但是不必保持这种方式。但是,我不知道如何使用EF Core自动生成MessageId

因此,我尝试获取当前频道的最后一个MessageId,并将其递增并尝试插入:

MessageId

此代码无效。发生异常后,EF Core不会插入ID递增的项目。除此之外,在并发插入的情况下似乎效率很低。

当我在message表中使用一个附加ID作为主键,也许还有一些附加表时,是否存在一种更优雅的解决方案来解决此问题?

1 个答案:

答案 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。