EF Code First 4.1 - How to Set One-to-Many Relationships by Default

I have a Customer object that references a collection of Addresses. The complication is that I want to define a specific address as the default address.

If possible, I would like to keep the default FK addresses in the Customer table. It seems more elegant than a column in the address table to determine the default value.

I'm having difficulty using a free API in terms of defining this relationship. When I run the following code, I get an exception that says: "An error occurred while saving objects that do not disclose foreign key properties for their relations. The EntityEntries property will return null because one object cannot be identified as the source of the exception. Handling exceptions when retention can be simplified by publishing external key properties in entity types. For more information, see InnerException. " "It is not possible to determine the valid order for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store values."

I created a console application to show the exact problem. In this test application, I have a Customer object, address and flient api configuration.

Any help would be greatly appreciated:

using System; using System.Collections.Generic; using System.Data.Entity.ModelConfiguration; using System.ComponentModel.DataAnnotations; using System.Data.Entity; namespace OneToManyWithDefault { public class Customer { private ICollection<Address> m_Addresses; public Customer() { Addresses = new List<Address>(); } public int Id { get; set; } public string CompanyName { get; set; } public virtual ICollection<Address> Addresses { get { if (m_Addresses == null) { m_Addresses = new List<Address>(); } return m_Addresses; } set { m_Addresses = value; } } public Address DefaultAddress { get; set; } public int DefaultAddressId { get; set; } } public class Address { public int Id { get; set; } public string Town { get; set; } public Customer Customer { get; set; } } public class MyContext : DbContext { public DbSet<Customer> Customers { get; set; } public MyContext(string connectionString) : base(connectionString) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new CustomerConfiguration()); modelBuilder.Configurations.Add(new AddressConfiguration()); base.OnModelCreating(modelBuilder); } } public class CustomerConfiguration : EntityTypeConfiguration<Customer> { public CustomerConfiguration() : base() { HasKey(p => p.Id); Property(p => p.Id) .HasColumnName("Id") .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) .IsRequired(); Property(p => p.CompanyName) .HasColumnName("Name") .IsRequired(); // Configure the mapping for the Default Address (this is likely to be wrong!): HasRequired(p => p.DefaultAddress).WithMany() .Map(x => x.MapKey("DefaultAddressId")) .WillCascadeOnDelete(false); HasRequired(p => p.DefaultAddress) .WithMany() .HasForeignKey(x => x.DefaultAddressId); ToTable("Customers"); } } public class AddressConfiguration : EntityTypeConfiguration<Address> { public AddressConfiguration() : base() { HasKey(p => p.Id); Property(p => p.Id) .HasColumnName("Id") .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) .IsRequired(); Property(p => p.Town) .HasColumnName("Town") .IsRequired(); HasRequired(p => p.Customer) .WithMany(c => c.Addresses) .Map(x => x.MapKey("CustomerId")); ToTable("Addresses"); } } class Program { private const string ConnectionString = @"Server=.\sql2005;Database=OneToManyWithDefault;integrated security=SSPI;"; static void Main(string[] args) { Customer headOffice = new Customer(); headOffice.CompanyName = "C1"; Address address = new Address(); address.Town = "Colchester"; headOffice.Addresses.Add(address); address = new Address(); address.Town = "Norwich"; headOffice.Addresses.Add(address); headOffice.DefaultAddress = address; MyContext context = new MyContext(ConnectionString); context.Customers.Add(headOffice); context.SaveChanges(); Console.WriteLine("Done."); Console.ReadLine(); } } } 

Many thanks,

Paul.

+6
source share
1 answer

I do not understand what EF says about "non-public foreign keys" in the exception. I would consider internal exception as an important part:

It is not possible to determine the actual ordering for dependent operations. dependencies may exist due to foreign key constraints, model requirements, or store values.

I think the problem in your model is that you have a mutual relationship between Customer and Address : the client needs the address (you marked it as mandatory in your cartographic code) and, on the other hand, the client needs the address (the default is ) due to a non-elementary foreign key and because of your mapping code). So, EF does not know which object to save first in your code example - default address or client? Both objects need the primary key of the other, which must be stored with valid FK restrictions.

The simplest solution that I see is to make an optional default address in your model and then save twice (I omit the mappings that work by convention anyway):

 public class Customer { private ICollection<Address> m_Addresses; public Customer() { Addresses = new List<Address>(); } public int Id { get; set; } public string CompanyName { get; set; } public virtual ICollection<Address> Addresses { get { ... } set { ... } } public Address DefaultAddress { get; set; } public int? DefaultAddressId { get; set; } // FK for optional relationship } public class Address { public int Id { get; set; } public string Town { get; set; } public Customer Customer { get; set; } } // ... public class CustomerConfiguration : EntityTypeConfiguration<Customer> { public CustomerConfiguration() : base() { Property(p => p.CompanyName) .HasColumnName("Name") .IsRequired(); HasMany(c => c.Addresses) .WithRequired(a => a.Customer) .Map(x => x.MapKey("CustomerId")); } } public class AddressConfiguration : EntityTypeConfiguration<Address> { public AddressConfiguration() : base() { Property(p => p.Town) .HasColumnName("Town") .IsRequired(); } } 

And then your program will look like this:

 static void Main(string[] args) { Customer headOffice = new Customer(); headOffice.CompanyName = "C1"; Address address = new Address(); address.Town = "Colchester"; headOffice.Addresses.Add(address); address = new Address(); address.Town = "Norwich"; headOffice.Addresses.Add(address); //headOffice.DefaultAddress = address; //We don't set the default address here as SaveChanges would throw an //exception. But because it is optional now we are allowed to leave it null. MyContext context = new MyContext(ConnectionString); context.Customers.Add(headOffice); context.SaveChanges(); headOffice.DefaultAddress = address; // headoffice and address have now PKs context.SaveChanges(); // Updates headoffice in the DB with default address } 

This double SaveChanges is ugly, but I see no other way.

+7
source

Source: https://habr.com/ru/post/887616/


All Articles