Reputation: 5588
I am trying to create a model that has a Customer
entity with two references to Address
entities: BillingAddress
and ShippingAddress
.
Customer
public class Customer
{
public Guid CustomerId { get;set;}
public Guid? BillingAddressId { get; set; }
public Address BillingAddress { get; set; }
public Guid? ShippingAddressId { get; set; }
public Address ShippingAddress { get; set; }
}
Address
public class Address
{
public Guid AddressId { get; set; }
public Customer Customer { get; set; }
public Guid CustomerId { get; set; }
}
OnModelCreating
modelBuilder.Entity<Address>(eb =>
{
eb.HasOne(e => e.Customer).WithOne(o => o.BillingAddress).OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Address>(eb =>
{
eb.HasOne(e => e.Customer).WithOne(o => o.ShippingAddress).OnDelete(DeleteBehavior.Cascade);
});
I get the following error when trying to create the migration:
Cannot create a relationship between 'Customer.ShippingAddress' and 'Address.Customer', because there already is a relationship between 'Customer.BillingAddress' and 'Address.Customer'. Navigation properties can only participate in a single relationship.
I'm trying to configure the model so that when the Customer is deleted the referenced Addresses are deleted as well. I would like to be able to do this without loading the Addresses into the Context and relying on the database to cascade.
Upvotes: 6
Views: 4928
Reputation: 1
I have two points.
Please refer value object
Upvotes: 0
Reputation: 488
Taken straight from here, section [InverseProperty]: https://learn.microsoft.com/en-us/ef/core/modeling/relationships
You can use the Data Annotations to configure how navigation properties on the dependent and principal entities pair up. This is typically done when there is more than one pair of navigation properties between two entity types.
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int AuthorUserId { get; set; }
public User Author { get; set; }
public int ContributorUserId { get; set; }
public User Contributor { get; set; }
}
public class User
{
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[InverseProperty("Author")]
public List<Post> AuthoredPosts { get; set; }
[InverseProperty("Contributor")]
public List<Post> ContributedToPosts { get; set; }
}
Upvotes: 1
Reputation: 187
The relationship between an address and a customer is a many-to-one (determined by the foreign key CustomerId on Address). This means that you will need to specify a collection of Addresses on your customer entity.
public class Customer
{
public Guid CustomerId { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
So we need some other way to determine which address is for billing and which is for shipping:
public class Address
{
public Guid AddressId { get; set; }
public bool IsBillingAddress { get; set; }
public bool IsShippingAddress { get; set; }
public Guid CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
Then, OnModelCreating
you can do this to reference your addresses from the customers and add the cascaded delete behaviour:
modelBuilder.Entity<Customer>()
.HasMany(c => c.Addresses)
.WithOne(a => a.Customer)
.OnDelete(DeleteBehavior.Cascade);
This then leaves us with one small additional problem - a customer can currently have multiple billing or multiple shipping addresses, so we need to add some form of constraint:
modelBuilder.Entity<Address>()
.HasIndex(a => new { a.CustomerId, a.IsBillingAddress })
.HasName("UQ_CustomerBillingAddress")
.IsUnique(true)
.HasFilter("IsBillingAddress = 1");
modelBuilder
.Entity<Address>()
.HasIndex(a => new { a.CustomerId, a.IsShippingAddress })
.HasName("UQ_CustomerShippingAddress")
.IsUnique(true)
.HasFilter("IsShippingAddress = 1");
This will allow a customer to have as many addresses as you like, but there will be at most one billing address and one shipping address.
Upvotes: 2