【问题标题】:Nullable Owned types in EF CoreEF Core 中的可空拥有类型
【发布时间】:2018-01-02 15:31:44
【问题描述】:

我想存储一个地址,但它必须是可选的。

我的映射如下所示:

map.OwnsOne(x => x.Address, cb => cb.OwnsOne(l => l.Location));

但是当我将我的 DbContext 与 Address as null 提交时,我得到了这个错误:

InvalidOperationException: 'Member' 的实体正在共享表 'Members' 与 'Member.Address#StreetAddress',但没有实体 具有相同键值 'Id:-2147480644' 的这种类型 标记为“已添加”。

然后我从构造函数中实例化了地址和位置,现在我可以保存实体了。但是当再次获取数据时,我也得到了一个实例化的地址,我真的想要一个空值。

难道不能创建可以为空的 Owned Types 吗?

【问题讨论】:

  • 您有什么理由使用流畅的 API 映射而不是属性映射?成员可以有一个地址,但每个地址都必须属于一个成员。确保您的 Address 类中有一个 MemberId(外键),并确保您的 Member 类中有一个 public virtual Address Address { get; set; } - 警告:这是未经测试的。
  • 简短的回答是否定的。拥有的类型与 EF6 复杂类型非常相似,是必需的,并且始终作为拥有者数据的一部分加载。
  • @BKSpureon:我喜欢保持我的域对象干净并且映射在它自己的文件中分离。我希望地址分布在实体上的列上,例如 Address_Streetname、Address_City 等,所以不需要成员的参考 ID?也许我想为订单实体添加地址字段。
  • @IvanStoev:我明白了,今天早上找到了一些文档。 docs.microsoft.com/en-us/dotnet/standard/…
  • 有什么建议可以让我拥有像地址这样使用表拆分并允许空值的值类型?

标签: c# entity-framework-core


【解决方案1】:

难道不能创建可以为空的 Owned Types 吗?

从 EF Core 3 开始,这现在是可能的 ?。

所有家属现在都是可选的。 (在预览版 4 中发货): Source


Sample Code:

static void Main(string[] args)
{
  using (var context = new OwnedEntityContext())
  {
    context.Add(new DetailedOrder
    {
      Status = OrderStatus.Pending,
      OrderDetails = new OrderDetails
      {
        ShippingAddress = new StreetAddress
        {
          City = "London",
          Street = "221 B Baker St"
        }
        //testing 3.0: "Yes, all dependents are now optional"
        //reference: https://github.com/aspnet/EntityFrameworkCore/issues/9005#issuecomment-477741082
        //NULL Owned Type Testing
        //BillingAddress = new StreetAddress
        //{
        //    City = "New York",
        //    Street = "11 Wall Street"
        //}
      }
    });
    context.SaveChanges();
  }
  //read test
  using (var context = new OwnedEntityContext())
  {
    #region DetailedOrderQuery
    var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
    Console.Write("NULL Owned Type Test, Is Billing Address NULL?");
    //PRINTS FALSE
    Console.WriteLine($"{order.OrderDetails.BillingAddress == null}");
    #endregion
  }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    #region OwnsOneNested
    modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od =>
    {
        od.OwnsOne(c => c.BillingAddress);
        od.OwnsOne(c => c.ShippingAddress);
    });
    #endregion

    #region OwnsOneTable
    modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od =>
    {
        od.OwnsOne(c => c.BillingAddress);
        od.OwnsOne(c => c.ShippingAddress);
        od.ToTable("OrderDetails");
        //Exception message:Microsoft.Data.SqlClient.SqlException:
        //'Cascading foreign key 'FK_OrderDetails_DetailedOrders_OrderId' cannot
        //be created where the referencing column 'OrderDetails.OrderId' is an identity column.
        //Could not create constraint or index. See previous errors.'
        //3.0 bug: https://github.com/aspnet/EntityFrameworkCore/issues/17448#issuecomment-525444101 
        //fixed in 3.1: https://github.com/aspnet/EntityFrameworkCore/pull/17458
        od.Property("OrderId")
            .ValueGeneratedNever();
    });
    #endregion
}

【讨论】:

【解决方案2】:

Owned Types 的限制之一是不支持可选(即可为空)。 我建议你关注this thread

在我的解决方案中,我使用空对象方法并使用 IsEmpty 方法来了解地址是否为空,而不是询问地址是否为空。我希望这种方法对您有所帮助。

public sealed class Address : ValueObject<Address>
{
    public string StreetAddress1 { get; private set; }
    public string StreetAddress2 { get; private set; }
    public string City { get; private set; }
    public string State { get; private set; }
    public string ZipCode { get; private set; }
    public string Country { get; private set; }

    private Address() { }
    public Address(string streetAddress1, string city, string state, string zipcode, string country)
    {
        StreetAddress1 = streetAddress1;
        City = city;
        State = state;
        ZipCode = zipcode;
        Country = country;
    }

    public Address(string streetAddress1, string streetAddress2, string city, string state, string zipcode, string country)
        : this(streetAddress1, city, state, zipcode, country)
    {
        StreetAddress2 = streetAddress2;
    }

    public static Address Empty()
    {
        return new Address("", "", "", "", "");
    }

    public bool IsEmpty()
    {
        if (string.IsNullOrEmpty(StreetAddress1)
         && string.IsNullOrEmpty(City)
         && string.IsNullOrEmpty(State)
         && string.IsNullOrEmpty(ZipCode)
         && string.IsNullOrEmpty(Country))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

}
public class Firm : AggregateRoot<Guid>
{
    public string Name { get; private set; }
    public Address Address { get; private set; }

    private Firm() { }
    public Firm(string name)
    {
        if (String.IsNullOrEmpty(name))
            throw new ArgumentException();

        Id = Guid.NewGuid();
        Name = name;
        Address = Address.Empty();
    }
}

【讨论】:

    【解决方案3】:

    实体框架document 声明

    对拥有的实体类型的引用导航不能为空,除非它们 被显式映射到与所有者不同的表中

    所以,事实上,您的问题有一个解决方案。您需要将您拥有的实体映射到单独的表中,而不是将其与所有者放在同一个表中。

    map.OwnsOne(x => x.Address, cb => cb.OwnsOne(l => l.Location, l=> l.ToTable("Locations")));
    

    通过将位置实体映射到名为 Locations 的单独表中,拥有的实体变为可为空。

    【讨论】:

      【解决方案4】:

      在这里查看:https://msdn.microsoft.com/en-us/magazine/mt846463.aspx 并向下滚动到“允许空值对象的临时解决方法”。

      可以在此处找到更短的示例:https://entityframeworkcore.com/knowledge-base/48063630/nullable-owned-types-in-ef-core

      如果没有这种解决方法,我会很高兴能够做到这一点,但我认为这还不可能。

      【讨论】:

        猜你喜欢
        • 2021-04-28
        • 1970-01-01
        • 1970-01-01
        • 2021-01-27
        • 1970-01-01
        • 2020-06-03
        • 1970-01-01
        • 2021-10-04
        • 2020-11-17
        相关资源
        最近更新 更多