Owned type mapping EF Core fails when saving

Viewed 5697

I want to make TableSplitting using Owned Types. I have the following model:

public class Account 
{
  public GUID Id { get; set; }
  public string Email { get; set; }
  public StreetAddress Address { get; set; }
}

public class StreetAddress
{
  public string Name { get; set; }
  public string Address { get; set; }
  public string Zipcode { get; set; }
  public string City { get; set; }
  public string  Country { get; set; }
  public Location Location { get; set; }
}

public class Location
{
  public double Lat { get; set; }
  public double Lng { get; set; }
}

And I defined my mapping of the Account like this:

public override void Map(EntityTypeBuilder<Account> map)
{
    // Keys
    map.HasKey(x => x.Id);

    // Indexs
    map.HasIndex(x => x.Email).IsUnique();

    // Property mappings.
    map.Property(x => x.Email).HasMaxLength(255).IsRequired();

    // Owned types.
    map.OwnsOne(x => x.Address, cb => cb.OwnsOne(a => a.Location));
}

When I run the migration things are working and the columns are created in the database. But when I try to insert and save an address like so:

var account1 = new Account("e@mail.com", "First", "Last")
    {
      Address = new StreetAddress()
                  {
                        Address1 = "Street 1",
                        City = "City",
                        Zipcode = "2000",
                        Country = "Denmark",
                        Location = new Location()
                        {
                            Lat = 0.0,
                            Lng = 5.5
                        }

                    }
                };
this.Context.Accounts.Add(account1);

I get this error

Message "The entity of 'Account' is sharing the table 'Accounts' with 'Account.Address#StreetAddress', but there is no entity of this type with the same key value 'Id:b7662057-44c2-4f3f-2cf0-08d504db1849' that has been marked as 'Added'."

5 Answers

Consider using virtual properties. This can also be used for lazy loading in your application

 public class Account 
 {
  public GUID Id { get; set; }
  public string Email { get; set; }
  public virtual StreetAddress Address { get; set; }
 }

public class StreetAddress
{
  public string Name { get; set; }
  public string Address { get; set; }
  public string Zipcode { get; set; }
  public string City { get; set; }
  public string  Country { get; set; }
  public virtual Location Location { get; set; }
}

public class Location
{
  public double Lat { get; set; }
  public double Lng { get; set; }
}

You must add constructors and initialize owned entities.

public class Account 
{
  public Account (){
    Address = new StreetAddress();
  }

  public GUID Id { get; set; }
  public string Email { get; set; }
  public StreetAddress Address { get; set; }
}

public class StreetAddress
{
  public StreetAddress(){
    Location = new Location();
  }

  public string Name { get; set; }
  public string Address { get; set; }
  public string Zipcode { get; set; }
  public string City { get; set; }
  public string  Country { get; set; }
  public Location Location { get; set; }
}

public class Location
{
  public double Lat { get; set; }
  public double Lng { get; set; }
}

In other words there can't be optional owned entities.

Note: if you have a non-empty constructor you also must add empty constructor due to ef core limitations.

public class Account {
  public GUID Id { get; set; }
  public string Email { get; set; }
  public StreetAddress Address { get; set; }
}

public class StreetAddress {
  public string Name { get; set; }
  public string Address { get; set; }
  public string Zipcode { get; set; }
  public string City { get; set; }
  public string  Country { get; set; }
  public Location[] Location { get; set; }


public class Location {
  public double Lat { get; set; }
  public double Lng { get; set; }
}

try this because once you call your object in the shape of all is in relation with Account class but the reality is you don't define that relation which I made

For an Account to be added in the database. It's references should be saved first in the table. For adding data in database it should be in the following order: Location > StreetAddress > Account

I am sure if you try adding every object to the context then build the parent it would work meaning add the location first and then create address and reference the location object u made add the address to the context afterwards create and account and reference the added address in it!

Or you could add ctors to ur models that init new instances of each navigation property

one last thing, this really shouldn't be an issue as of EF Core 5 since it has tons of new bells and whistles when it comes to navigation properties

public override void Map(EntityTypeBuilder<Account> map)
{
    // Keys
    map.HasKey(x => x.Id);

    // Indexs
    map.HasIndex(x => x.Email).IsUnique();

    // Property mappings.
    map.Property(x => x.Email).HasMaxLength(255).IsRequired();

    // Owned types.
    map.OwnsOne(x => x.Address, cb => cb.OwnsOne(a => a.Location));
}

if you're using it, you really dont need the following part just add your DbSet<T>s appropriately and thats it

Related