使用C#10,.NET 6和EF Core 6更改嵌套值对象的值的问题

发布于 2025-01-28 00:15:35 字数 3492 浏览 2 评论 0原文

我的汇总是这样的:

public class Order : AggregateRoot
{
    private readonly List<OrderItem> _items = new();

    public DateTime Date { get; set; }
    public IReadOnlyCollection<OrderItem> Items => _items;

    public void SetItemDiscounts(int itemId, IEnumerable<Discount> discounts)
    {
        var orderItem = _items.Single(item => item.Id == itemId);
        orderItem.SetDiscounts(discounts);
    }
}

public class OrderItem : Entity
{
    private readonly List<Discount> _discounts = new();

    public int OrderId { get; private set; }
    public int Qty { get; private set; }
    public Money Price { get; private set; }
    public IReadOnlyCollection<Discount> Discounts => _discounts;

    public void SetDiscounts(IEnumerable<Discount> discounts)
    {
        _discounts.Clear();
        _discounts.AddRange(discounts);
    }
}

public class Discount : ValueObject<Discount>
{
    public int Plan { get; private set; }
    public Money Amount { get; private set; }
}

public class Money : ValueObject<Money>
{
    public int CurrencyId { get; private set; }
    public decimal Value { get; private set; }
}

EF核心的配置是:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>().HasKey(x => x.Id);
        modelBuilder.Entity<Order>().Property(t => t.Id)
            .HasColumnType(nameof(SqlDbType.BigInt)).IsRequired();
        modelBuilder.Entity<Order>().Property(t => t.Date)
            .HasColumnType(nameof(SqlDbType.DateTime2)).IsRequired();

        modelBuilder.Entity<Order>().HasMany<OrderItem>().WithOne()
            .HasForeignKey(x => x.OrderId);

        modelBuilder.Entity<OrderItem>().HasKey(x => x.Id);
        modelBuilder.Entity<OrderItem>().Property(t => t.Id)
            .HasColumnType(nameof(SqlDbType.Int)).IsRequired();
        modelBuilder.Entity<OrderItem>().Property(t => t.Qty)
            .HasColumnType(nameof(SqlDbType.Int)).IsRequired();

        modelBuilder.Entity<OrderItem>().OwnsOne(x => x.Price)
            .Property(x => x.Value).HasColumnType(nameof(SqlDbType.Decimal))
            .HasColumnName("Price").IsRequired();

        modelBuilder.Entity<OrderItem>().OwnsMany(
            orderItem => orderItem.Discounts,
            discountNavigationBuilder =>
            {
                discountNavigationBuilder.Property(p => p.Plan)
                    .HasColumnType(nameof(SqlDbType.Int)).IsRequired();
                discountNavigationBuilder.OwnsOne(
                    discount => discount.Amount,
                    amountNavigationBuilder =>
                    {
                        amountNavigationBuilder.Property(p => p.CurrencyId)
                            .HasColumnType(nameof(SqlDbType.Int)).HasColumnName("CurrencyId").IsRequired();
                        amountNavigationBuilder.Property(p => p.Value)
                            .HasColumnType(nameof(SqlDbType.Decimal)).HasColumnName("Amount").IsRequired();
                    });
            });
    }

当我创建带有项目和折扣的订单时没有问题,但是当我使用setDiscounts方法更新折扣时,我会遇到此错误:

system.invalidoperationException:属性:属性'Discount.amount#Money.discountid'是密钥的一部分,因此无法修改或标记为修改。要将现有实体的本金与识别外键更改,请首先删除因子并调用“ savechanges”,然后将依赖人与新的本金相关联。

这里的折扣是什么?我的配置或任何其他代码有什么问题吗?

My Aggregate is like this:

public class Order : AggregateRoot
{
    private readonly List<OrderItem> _items = new();

    public DateTime Date { get; set; }
    public IReadOnlyCollection<OrderItem> Items => _items;

    public void SetItemDiscounts(int itemId, IEnumerable<Discount> discounts)
    {
        var orderItem = _items.Single(item => item.Id == itemId);
        orderItem.SetDiscounts(discounts);
    }
}

public class OrderItem : Entity
{
    private readonly List<Discount> _discounts = new();

    public int OrderId { get; private set; }
    public int Qty { get; private set; }
    public Money Price { get; private set; }
    public IReadOnlyCollection<Discount> Discounts => _discounts;

    public void SetDiscounts(IEnumerable<Discount> discounts)
    {
        _discounts.Clear();
        _discounts.AddRange(discounts);
    }
}

public class Discount : ValueObject<Discount>
{
    public int Plan { get; private set; }
    public Money Amount { get; private set; }
}

public class Money : ValueObject<Money>
{
    public int CurrencyId { get; private set; }
    public decimal Value { get; private set; }
}

And the configuration for EF Core is:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>().HasKey(x => x.Id);
        modelBuilder.Entity<Order>().Property(t => t.Id)
            .HasColumnType(nameof(SqlDbType.BigInt)).IsRequired();
        modelBuilder.Entity<Order>().Property(t => t.Date)
            .HasColumnType(nameof(SqlDbType.DateTime2)).IsRequired();

        modelBuilder.Entity<Order>().HasMany<OrderItem>().WithOne()
            .HasForeignKey(x => x.OrderId);

        modelBuilder.Entity<OrderItem>().HasKey(x => x.Id);
        modelBuilder.Entity<OrderItem>().Property(t => t.Id)
            .HasColumnType(nameof(SqlDbType.Int)).IsRequired();
        modelBuilder.Entity<OrderItem>().Property(t => t.Qty)
            .HasColumnType(nameof(SqlDbType.Int)).IsRequired();

        modelBuilder.Entity<OrderItem>().OwnsOne(x => x.Price)
            .Property(x => x.Value).HasColumnType(nameof(SqlDbType.Decimal))
            .HasColumnName("Price").IsRequired();

        modelBuilder.Entity<OrderItem>().OwnsMany(
            orderItem => orderItem.Discounts,
            discountNavigationBuilder =>
            {
                discountNavigationBuilder.Property(p => p.Plan)
                    .HasColumnType(nameof(SqlDbType.Int)).IsRequired();
                discountNavigationBuilder.OwnsOne(
                    discount => discount.Amount,
                    amountNavigationBuilder =>
                    {
                        amountNavigationBuilder.Property(p => p.CurrencyId)
                            .HasColumnType(nameof(SqlDbType.Int)).HasColumnName("CurrencyId").IsRequired();
                        amountNavigationBuilder.Property(p => p.Value)
                            .HasColumnType(nameof(SqlDbType.Decimal)).HasColumnName("Amount").IsRequired();
                    });
            });
    }

There is no problem when I create an order with Items and discounts, but while I update discounts using SetDiscounts method I get this error :

System.InvalidOperationException: The property 'Discount.Amount#Money.DiscountId' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.

What is DiscountId here? Is there any problem with my configuration or any other code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

几味少女 2025-02-04 00:15:36

在EF核心中进行了一些搜索和研究之后,我发现有一个价值对象的功能。我更改了代码,它的工作原理很好:

public class Money : ValueObject<Money>
{
    public enum CurrencyType
    {
        USD = 1,
        IRI = 2
    }

    private Money(decimal value) => Value = value;

    public Money(decimal value, CurrencyType currencyType)
    {
        Value = Math.Round(value, currencyType == CurrencyType.USD ? 2 : 0);
    }

    public decimal Value { get; }

    public static Money Zero => new(0);
    
    public static implicit operator decimal(Money money) => money.Value;
    
    public static Money operator +(decimal a, Money b) => new (a + b.Value);

    public static Money operator +(Money a, decimal b) => new (a.Value + b);
}

我写了一个类的前值转换:

public class MoneyValueConverter : ValueConverter<Money, decimal>
{
    public MoneyValueConverter() : base(
            money => money.Value,
            value => Money.Zero + value
        )
    {
    }
}

我将EF配置更改为:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>().HasKey(x => x.Id);
    modelBuilder.Entity<Order>().Property(t => t.Id)
        .HasColumnType(nameof(SqlDbType.BigInt)).IsRequired();
    modelBuilder.Entity<Order>().Property(t => t.Date)
        .HasColumnType(nameof(SqlDbType.DateTime2)).IsRequired();

    modelBuilder.Entity<Order>().HasMany<OrderItem>().WithOne()
        .HasForeignKey(x => x.OrderId);

    modelBuilder.Entity<OrderItem>().HasKey(x => x.Id);
    modelBuilder.Entity<OrderItem>().Property(t => t.Id)
        .HasColumnType(nameof(SqlDbType.Int)).IsRequired();
    modelBuilder.Entity<OrderItem>().Property(t => t.Qty)
        .HasColumnType(nameof(SqlDbType.Int)).IsRequired();

    modelBuilder.Entity<OrderItem>()
        .Property(t => t.Price)
        .HasConversion<MoneyValueConverter>()
        .HasColumnName(nameof(OrderItem.Price))
        .HasColumnType(nameof(SqlDbType.Decimal))
        .IsRequired();

    modelBuilder.Entity<OrderItem>().OwnsMany(
        orderItem => orderItem.Discounts,
        discountNavigationBuilder =>
        {
            discountNavigationBuilder
                .Property(p => p.Plan)
                .HasColumnType(nameof(SqlDbType.Int))
                .IsRequired();
            discountNavigationBuilder
                .Property(t => t.Amount)
                .HasConversion<MoneyValueConverter>()
                .HasColumnName(nameof(Discount.Amount))
                .HasColumnType(nameof(SqlDbType.Decimal))
                .IsRequired();
        });
}

After some search and study in EF-Core I found that there is a feature for ValueObjects. I changed the code and it works really fine:

public class Money : ValueObject<Money>
{
    public enum CurrencyType
    {
        USD = 1,
        IRI = 2
    }

    private Money(decimal value) => Value = value;

    public Money(decimal value, CurrencyType currencyType)
    {
        Value = Math.Round(value, currencyType == CurrencyType.USD ? 2 : 0);
    }

    public decimal Value { get; }

    public static Money Zero => new(0);
    
    public static implicit operator decimal(Money money) => money.Value;
    
    public static Money operator +(decimal a, Money b) => new (a + b.Value);

    public static Money operator +(Money a, decimal b) => new (a.Value + b);
}

I wrote a class fore value conversion:

public class MoneyValueConverter : ValueConverter<Money, decimal>
{
    public MoneyValueConverter() : base(
            money => money.Value,
            value => Money.Zero + value
        )
    {
    }
}

And I changed Ef configuration to :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>().HasKey(x => x.Id);
    modelBuilder.Entity<Order>().Property(t => t.Id)
        .HasColumnType(nameof(SqlDbType.BigInt)).IsRequired();
    modelBuilder.Entity<Order>().Property(t => t.Date)
        .HasColumnType(nameof(SqlDbType.DateTime2)).IsRequired();

    modelBuilder.Entity<Order>().HasMany<OrderItem>().WithOne()
        .HasForeignKey(x => x.OrderId);

    modelBuilder.Entity<OrderItem>().HasKey(x => x.Id);
    modelBuilder.Entity<OrderItem>().Property(t => t.Id)
        .HasColumnType(nameof(SqlDbType.Int)).IsRequired();
    modelBuilder.Entity<OrderItem>().Property(t => t.Qty)
        .HasColumnType(nameof(SqlDbType.Int)).IsRequired();

    modelBuilder.Entity<OrderItem>()
        .Property(t => t.Price)
        .HasConversion<MoneyValueConverter>()
        .HasColumnName(nameof(OrderItem.Price))
        .HasColumnType(nameof(SqlDbType.Decimal))
        .IsRequired();

    modelBuilder.Entity<OrderItem>().OwnsMany(
        orderItem => orderItem.Discounts,
        discountNavigationBuilder =>
        {
            discountNavigationBuilder
                .Property(p => p.Plan)
                .HasColumnType(nameof(SqlDbType.Int))
                .IsRequired();
            discountNavigationBuilder
                .Property(t => t.Amount)
                .HasConversion<MoneyValueConverter>()
                .HasColumnName(nameof(Discount.Amount))
                .HasColumnType(nameof(SqlDbType.Decimal))
                .IsRequired();
        });
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文