EF Core自动增量数字的最佳实践

3
我正在寻找使用 EF Core 增加数据库中一些数字的最佳实践。
我有一个名为 PARENT 的数据库模型。在我的应用程序中,我可以有几个 PARENTS。每个 PARENT 都有几个 CHILDS。
PARENT1: CHILD1 - CHILD2
PARENT2: CHILD1 - CHILD2 - CHILD3
我希望在每个 PARENT 中,每个 CHILD 都通过一个名为 int Number 的列按递增顺序编号。
这意味着:
PARENT1.CHILD1 = 001;
PARENT1.CHILD2 = 002;
PARENT2.CHILD1 = 001;
PARENT2.CHILD2 = 002;
所以,它必须是唯一的,并且按照父级递增。您能否给我一些有效实现此目标的最佳实践建议?
public class Bar {
    public Guid BarId {get;set;}
    public ICollection<Client> Clients {get;set;}
}

public class Client {
    public Guid ClientId {get;set;}
    public Guid BarId {get;set;}
    public int ClientNumber {get;set;}
}

public class Context : DBContext {
    entity.HasKey(e => new { e.BarId, e.ClientId }).HasName("PK_CLIENT");
                entity.Property(e => e.ClientId).ValueGeneratedWhenDefault();
    entity.Property(e => e.ClientNumber).ValueGeneratedOnAdd();
    entity.HasIndex(e => new {e.BarId, e.ClientNumber}).IsUnique()
        .HasName("IX_CLIENT_NUMBER");
}

通过调用 ValueGeneratedOnAdd() 方法使数字列递增,但我希望它能够针对每个 Bar 递增,这样就可以允许重复的客户编号在不同的 Bars 之间出现,但不能在同一个 Bar 中出现,并且按照 Bar 而不仅仅是按照客户实体递增。

1
在调用Update/SaveChanges之前,我只会在子实例中设置数字。我的意思是,这种方法与EF Core无关。但我想你一定有寻求另一种方法的充分理由。可以让我看看你的代码吗? - heringer
我刚刚添加了更新内容,这样更清楚吗?@heringer - Esteban Chornet
1
你为什么想要这样做?每个记录都应该有一个唯一的标识符。我会通过这些唯一标识符来链接我的记录。因此,子记录将具有一个名为SrcParentId的字段,其中包含父记录的唯一记录ID。如果需要更多数字,则添加文本字段,您可以在其中插入所需内容。出于可读性和维护原因,我会使用INT而不是GUID作为我的标识符类型。使用GUID编写支持语句是非常糟糕的。 - user743414
在我看来,最佳实践是使用SQL表键SQL外键。请查看这个关于通过Entity Framework Core管理一对多关系的优秀教程 - StepUp
3个回答

5

一种方法是按照以下步骤操作:

1)使用整数作为键,并将ClientNumber属性设置为可空。

    public class Bar
    {
        public int BarId { get; set; }
        public ICollection<Client> Clients { get; set; }
    }

    public class Client
    {
        public int ClientId { get; set; }
        public Bar Bar { get; set; }
        public int BarId { get; set; }
        public int? ClientNumber { get; set; }
    }

2)将ClientId设置为身份(自动递增),将ClientNumber设置为数据库生成的值(否则EF Core在插入后不会重新从数据库读取该值)

entity.Property(e => e.ClientId).ValueGeneratedOnAdd();
entity.Property(e => e.ClientNumber).ValueGeneratedOnAddOrUpdate();

3) 在插入后添加触发器以修改ClientNumber。

    create trigger ClientNumber on Clients after insert 
    as
    begin
        set nocount on;
        update c set ClientNumber = n.RowNum from Clients c
        inner join (
            select ClientId, ROW_NUMBER() OVER(PARTITION BY BarId ORDER BY ClientId ASC) as RowNum
            from Clients
        ) n on n.ClientId = c.ClientId
    where c.ClientId in (select ClientId from inserted)
    end

现在一切都自动处理插入操作:
            var bar1 = new Bar() { Clients = new List<Client>() };
            var bar2 = new Bar() { Clients = new List<Client>() };

            bar1.Clients.Add(new Client());
            bar1.Clients.Add(new Client());

            bar2.Clients.Add(new Client());
            bar2.Clients.Add(new Client());

            context.Bars.AddRange(new[] { bar1, bar2 });
            await context.SaveChangesAsync();

            bar1.Clients.Add(new Client());
            bar2.Clients.Add(new Client());
            bar1.Clients.Add(new Client());
            await context.SaveChangesAsync();

将呈现

ClientId    BarId   ClientNumber
1   1   1
2   1   2
6   1   3
7   1   4
3   2   1
4   2   2
5   2   3

缺点是在触发器执行之前,您无法使用唯一索引IX_CLIENT_NUMBER,因为ClientNumber将为NULL。


0

我使用AutoMapper IValueResolver解决了这个问题...

public class PublishedEntityVersionResolver : IValueResolver<CreatePublishedEntityCommand, PublishedEntity, int>
{
    private readonly IApplicationDbContext _dbContext;

    public PublishedEntityVersionResolver(IApplicationDbContext dbContext)
    {
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
    }

    public int Resolve(
        CreatePublishedEntityCommand source, PublishedEntity destination, int destinationMember, ResolutionContext context)
    {
        // If this EntityId & TenantId has been previously published, get it's version and increment otherwise start afresh...
        var previousVersion = _dbContext.PublishedEntities
            .SingleOrDefault(entity => entity.EntityId == source.EntityId && entity.TenantId == source.TenantId);

        return previousVersion?.Version + 1 ?? 1;
    }
}

 public void Mapping(Profile profile)
        => profile.CreateMap<CreatePublishedEntityCommand, PublishedEntity>()
            .ForMember(
                entity => entity.Version,
                options => options.MapFrom<PublishedEntityVersionResolver>());

0

也许这不是您想要的,但既然我正在编写一些代码,它就不适合作为注释。

要在子组合中设置ClientNumber,我会写如下代码:

Bar bar = new Bar();
int seq = 1;
bar.Clients.Add(new Client() { ClientNumber = seq++; });
bar.Clients.Add(new Client() { ClientNumber = seq++; });
dbContext.Update(bar);
dbContext.SaveChanges();

当您创建新的Bar时,它可以正常工作。如果可以编辑并添加或删除栏中的客户端 - 就像CRUD一样 - 那么您必须更改某些内容。但是它展示了我在评论中所说的内容(设置ClientNumber)。

另一种方法是拦截SaveChanges,但在这种情况下,我会避免使用,因为对于未经建议的开发人员来说有点不清楚:

public class DbContextWithBlackMage : DbContext
{
    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        OnBeforeSaving();
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

    private void OnBeforeSaving()
    {
        var entries = ChangeTracker.Entries();
        foreach (var entry in entries)
        {
            //TODO black mage to fill the ClientNumber very secretly and obscurely
            //TODO Matheus will be angry when he finds out muahahahahaha!
        }
    }
}

无论如何...这可能不是你想要的,但也许它会激发你对如何解决它(或如何不解决)的想法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接