EF Core 2.2, owned entities generated as another table when multiple in hierarchy - asp.net-core

I have a model with a class Address marked [Owned] and a hierarchy of people (person, customer or employee, then even more subtypes etc). There are addresses at different stages of this hierarchy and all of it ends up in one table as EF Core is limited to table per hierarchy. I expected all the attributes from address to appear multiple times in that person table (once per mention in any of the subtypes) however it doesn't appear at all! Instead i see FK for each of them and a separate Address table.
Does EF Core not support multiple owned members of the same type? If not is there anything i should do? I don't have any fluent API / specific configuration that could interfere with the defaults (new empty console project, only config line is .UseSQLServer(connectionstring)
Sample code bellow :
public class SampleContext : DbContext
{
public virtual DbSet<Address> Addresses { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Employee> Employees { get; set; }
public virtual DbSet<Person> Persons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("my connection string here");
}
base.OnConfiguring(optionsBuilder);
}
}
[Owned]
public class Address
{
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string AddressLine3 { get; set; }
public string City { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class Employee : Person
{
public Address Address { get; set; }
}
public class Customer : Person
{
public Address DeliveryAddress { get; set; }
public Address InvoicingAddress { get; set; }
}
Expected Person table :
DeliveryAddressAddressLine1
DeliveryAddressAddressLine2
DeliveryAddressAddressLine3
DeliveryAddressAddressCity
InvoicingAddressAddressLine1
InvoicingAddressAddressLine2
InvoicingAddressAddressLine3
InvoicingAddressAddressCity
EmployeeAddressAddressLine1
EmployeeAddressAddressLine2
EmployeeAddressAddressLine3
EmployeeAddressAddressCity
Generated Person table (+ an unexpected Address table):
EmployeeAddressAddressId
DeliveryAddressAddressId
InvoicingAddressAddressId
Edit : updated the question, added the context definition and noticed i had Addresses as a DbSet so i assume this may be the cause, removing it gives me the following error :
Cannot use table 'Person' for entity type 'Customer.DeliveryAddress#Address' since it is being used for entity type 'Employee.Address#Address' and there is no relationship between their primary keys.`

According to EF Core Owned Entity Types documentation:
Inheritance hierarchies that include owned entity types are not supported
You can overcome this problem by moving public Address Address { get; set; }, public Address DeliveryAddress { get; set; } and public Address InvoicingAddress { get; set; } navigation properties from Employee and Customer to the base class Person as follows:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Address Address { get; set; }
public Address DeliveryAddress { get; set; }
public Address InvoicingAddress { get; set; }
}
Then configure with fluent API to override the Navigation_OwnedEntityProperty rule for owned entity column name as follows:
modelBuilder.Entity<Person>().OwnsOne(p => p.Address,
a =>
{
a.Property(p => p.AddressLine1).HasColumnName("EmployeeAddressLine1");
a.Property(p => p.AddressLine2).HasColumnName("EmployeeAddressLine2");
a.Property(p => p.AddressLine2).HasColumnName("EmployeeAddressLine3");
a.Property(p => p.City).HasColumnName("EmployeeAddressCity");
}).OwnsOne(p => p.DeliveryAddress,
a =>
{
a.Property(p => p.AddressLine1).HasColumnName("DeliveryAddressLine1");
a.Property(p => p.AddressLine2).HasColumnName("DeliveryAddressLine2");
a.Property(p => p.AddressLine2).HasColumnName("DeliveryAddressLine3");
a.Property(p => p.City).HasColumnName("DeliveryAddressCity");
}).OwnsOne(p => p.InvoicingAddress,
a =>
{
a.Property(p => p.AddressLine1).HasColumnName("InvoicingAddressLine1");
a.Property(p => p.AddressLine2).HasColumnName("InvoicingAddressLine2");
a.Property(p => p.AddressLine2).HasColumnName("InvoicingAddressLine3");
a.Property(p => p.City).HasColumnName("InvoicingAddressCity");
});
Now you if you don't want to move public Address Address { get; set; }, public Address DeliveryAddress { get; set; } and public Address InvoicingAddress { get; set; } navigation properties from Employee and Customer to the base class Person then you have to create separate tables from each address types as follows:
modelBuilder.Entity<Employee>().OwnsOne(p => p.Address,
a =>
{
a.ToTable("EmployeeAddresses");
});
modelBuilder.Entity<Customer>().OwnsOne(p => p.DeliveryAddress,
a =>
{
a.ToTable("DeliveryAddresses");
}).OwnsOne(p => p.InvoicingAddress,
a =>
{
a.ToTable("InvoicingAddresses");
});

Related

EF Core and Automapper. Mapping relationship one-to-many

I'm currently using AutoMapper on an Project running code-first Entity Framework.
Here just simple entities and DTO's:
// DTO Profile
public class CreateOrEditProfileDto
{
public string Code { get; set; }
public string Name { get; set; }
public List<RouteDto> Routes { get; set; }
}
// entity Profile
public class Profile
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
}
// DTO Route
public class RouteDto
{
public string DriverName { get; set; }
public string DriverSurname { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public int ProfileId { get; set; }
}
//entity Route
public class Route
{
public virtual string DriverName { get; set; }
public virtual string DriverSurname { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual int ProfileId { get; set; }
[ForeignKey("ProfileId")]
public Profile ProfileFk { get; set; }
}
//Mapper
configuration.CreateMap<RouteDto, Route>().ReverseMap();
// configuration.CreateMap<CreateOrEditProfileDto, Profile>().ReverseMap();
// this type of configuration give the error written below
configuration.CreateMap<CreateOrEditProfileDto, Profile>()
.ForMember(dest => dest, opt =>
opt.MapFrom(src => src.Routes.Select(x =>
new Route()
{
ProfileId = x.ProfileId,
DriverName = x.DriverName,
DriverSurname = x.DriverSurname,
Phone = x.Phone,
Email = x.Email,
}
)
)
);
I'm a little bit confusing, I'm trying to map one-to-many relationship between Profile and Route, Route has a foreign key to Profile. A single profile could have more routes. So, I want to create a profile and attach the list of routes, but when I compile the solution, I get this error:
AutoMapper.AutoMapperConfigurationException: 'Custom configuration for members is only supported for top-level individual members on a type.'
Does anyone know the best way to solve this mapping?
Regards
Because List<RouteDto> is mapped to Profile, the type does not match. You need to add a property in Profile.
public class Profile
{
public virtual string Code { get; set; }
public virtual string Name { get; set; }
public List<Route> Routes { get; set; }
}
The mapping attribute dest.Routes need to be specified. Then, the Routes will be automatically mapped.
CreateMap<CreateOrEditProfileDto, EntityProfile>()
.ForMember(dest => dest.Routes, opt =>
opt.MapFrom(src => src.Routes.Select(x=>
new Route()
{
ProfileId = x.ProfileId,
DriverName = x.DriverName,
DriverSurname = x.DriverSurname,
Phone = x.Phone,
Email = x.Email,
}
))
);

EF Two-to-Many relationship

I have a Section object that is in relationship with exactly two Node objects. Basically, the Section object is a route with a starting point and an ending point. I can't find a lot about Multiple-to-Many relationships and I don't even know if this is a thing.
public class Section
{
public Guid Id { get; set; }
public int Length { get; set; }
public Node StartNode { get; set; }
public Guid StartNodeId { get; set; }
public Node EndNode { get; set; }
public Guid EndNodeId { get; set; }
}
public class Node
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Section> Sections { get; set; }
}
This should not work :
modelBuilder.Entity<Section>()
.HasOne(se => se.StartNode)
.WithMany(sn => en.Sections)
.HasForeignKey(se => se.StartNodeId);
modelBuilder.Entity<Section>()
.HasOne(se => se.EndNode)
.WithMany(en => en.Sections)
.HasForeignKey(se => se.EndNodeId);
Should I map this as a Many-to-Many relationship ? I would like to have access to the Sections from a Node, and the two points from my Section.
What you actually wanting is multiple one-to-many with the same entity.
So your Node class should be as follows:
public class Node
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Section> StarNodeSections { get; set; }
public ICollection<Section> EndNodeSections { get; set; }
}
Then in the Fluent API configuration as follows:
modelBuilder.Entity<Section>()
.HasOne(se => se.StartNode)
.WithMany(sn => sn.StarNodeSections) // <-- Here it is
.HasForeignKey(se => se.StartNodeId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Section>()
.HasOne(se => se.EndNode)
.WithMany(en => en.EndNodeSections) // <-- Here it is
.HasForeignKey(se => se.EndNodeId)
.OnDelete(DeleteBehavior.Restrict);

EF Core Many to many with same class

I have City class representing City with its postal code. I have also a Route class representing bus route between two cities. So, i want in my Route class to have cityFrom and cityTwo properties, both with type City - many to many relationship with same class involved.
How can I achieve this with EF Core code first?
Thx!
UPDATE:
My models looks something like this:
public class Route
{
public int Id { get; set; }
public City CityFrom { get; set; }
public City CityTo { get; set; }
}
public class City
{
public int Id { get; set; }
public int PostCode { get; set; }
public string Name { get; set; }
}
Would this solve my problem:
modelBuilder.Entity<Route>()
.HasOne(f => f.CityFrom)
.WithMany()
.HasForeignKey(f => f.CityFromId);
modelBuilder.Entity<Route>()
.HasOne(f => f.CityTo)
.WithMany()
.HasForeignKey(f => f.CityToId);
And also to add int properties CityFromId and CityToId in Route model?
You can achieve many-to-many relationships in ASP.NET Core using Data Annotations
In your requirements, we can introduce a new class named CityRoute that represent many-to-many between the City and the Route
public class Route
{
public int RouteId { get; set; } // change to make it more specific id
public string CityFrom { get; set; }
public string CityTo { get; set; }
public ICollection<CityRoute> CityRoutes { get; set; }
}
public class City
{
public int CityId { get; set; } // change to make it more specific id
public int PostCode { get; set; }
public string Name { get; set; }
public ICollection<CityRoute> CityRoutes { get; set; }
}
// new class
public class CityRoute
{
public City City { get; set; }
public int CityId { get; set; }
public Route Route { get; set; }
public int RouteId { get; set; }
}
Your DataAnnotations can be like this
modelBuilder.Entity<CityRoute>()
.HasKey(cr => new {cr.CityId, cr.RouteId});
I hope this helps you
I wouldn't design this is a many-to-many relation.
Every Route has exactly one FromCity, defined by foreignkey FromCityId.
Every Route has exactly one ToCity, defined by foreignkey ToCityId.
public class Route
{
public int Id { get; set; }
// Every Route has one starting City defined by foreign key:
public int FromCityId { get; set; }
public virtual City FromCity { get; set; }
// Every Route has one termination city, defined by foreign key
public virtual int ToCityId { get; set; }
public virtual City ToCity { get; set; }
...
}
And the DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Proper name for table of Cities
ModelBuilder.Entity<City>().ToTable("Cities");
// Every Route has one required starting point defined by foreign key
// Several routes may use the same City Id
modelBuilder.Entity<Route>()
.HasRequired(route => route.FromCity)
.WithMany()
.HasForeignKey(route => route.FromCityId)
.WillCascadeOnDelete(false);
// Every Route has one required termination point defined by foreign key
// Several routes may use the same City Id
modelBuilder.Entity<Route>()
.HasRequired(route => route.ToCity)
.WithMany()
.HasForeignKey(route => route.ToCityId)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
If you delete a Route, then you don't want their cities to be deleted, as they might be used by other Routes: hence we don't want cascading on delete CascadeOnDelete(false)
EFCore 3.
Needs its own many to many class.
My problem was: Person to Person relations (n->m)
My solution.
Create a relationship class that implements the many to many.
I chose for the records in the class to have their own ID.
So the relationship class has 1 PK and 2 FKs (both to the Person class).
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Relationship> MyRelationships { get; set; }
public List<Relationship> TheirRelationships { get; set; }
}
//MyRelationships for people I added as a relation.
//TheirRelationships for people that added me as a relation.
public class Relationship {
public int RelationshipID { get; set; }
public DateTime Since { get; set; }
//ref to person myrelationships
public int MyID { get; set; }
public Person Me { get; set; }
//ref to person theirrelationships
public int TheirID { get; set; }
public Person They { get; set; }
}
Using add-migration and update-database I learned that in this particular case EFCore cannot resolve which FK to use for which relation.
I solved that using the fluent API.
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Write Fluent API configurations here
modelBuilder.Entity<Person>()
.HasMany<Relationship>(mr => mr.MyRelationships)
.WithOne(p => p.Me)
.HasForeignKey(m => m.MyID)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Person>()
.HasMany<Relationship>(tr => tr.TheirRelationships)
.WithOne(p => p.They)
.HasForeignKey(t => t.TheirID)
.OnDelete(DeleteBehavior.NoAction);
}
public DbSet<Person> People { get; set; }
public DbSet<Relationship> Relationships { get; set; }
}
Now add-migration xxx will work:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "People",
columns: table => new
{
ID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
FirstName = table.Column<string>(nullable: true),
MiddleName = table.Column<string>(nullable: true),
LastName = table.Column<string>(nullable: true),
Email = table.Column<string>(nullable: true),
UserID = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_People", x => x.ID);
table.ForeignKey(
name: "FK_People_AspNetUsers_UserID",
column: x => x.UserID,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_People_UserID",
table: "People",
column: "UserID");
migrationBuilder.CreateTable(
name: "Relationships",
columns: table => new
{
RelationshipID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Since = table.Column<DateTime>(nullable: false),
Kind = table.Column<int>(nullable: false),
MyID = table.Column<int>(nullable: false),
TheirID = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Relationships", x => x.RelationshipID);
table.ForeignKey(
name: "FK_Relationships_People_MyID",
column: x => x.MyID,
principalTable: "People",
principalColumn: "ID");
table.ForeignKey(
name: "FK_Relationships_People_TheirID",
column: x => x.TheirID,
principalTable: "People",
principalColumn: "ID");
});
migrationBuilder.CreateIndex(
name: "IX_Relationships_MyID",
table: "Relationships",
column: "MyID");
migrationBuilder.CreateIndex(
name: "IX_Relationships_TheirID",
table: "Relationships",
column: "TheirID");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Relationships");
migrationBuilder.DropTable(
name: "People");
}
}

Self Referencing Many-to-Many relations

I have an Ticket entity:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTickets { get; set; }
}
I want to setup many-to-many self-relations in Entity Framework Core, so i made two one-to-many relations:
public class Relation
{
[Required, ForeignKey("TicketFrom")]
public int FromId { get; set; }
[Required, ForeignKey("TicketTo")]
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
I've tried to create the relationship using fluent API:
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.FromId);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
But in result i have an error:
Cannot create a relationship between 'Ticket.RelatedTickets' and
'Relation.TicketTo', because there already is a relationship between
'Ticket.RelatedTickets' and 'Relation.TicketForm'. Navigation
properties can only participate in a single relationship.
The possible solution is to add Parent relation directly to TicketEntity:
public class Ticket
{
public int Id { get; set; }
[Required, ForeignKey("ParentRelation")]
public Nullable<int> ParentRelationId { get; set; }
public virtual Ticket ParentRelation {get;set;}
public virtual ICollection<Ticket> RelatedTickets { get; set; }
...
}
With fluent api like this:
modelBuilder.Entity<Ticket> =>
{
entity
.HasMany(e => e.RelatedTickets)
.WithOne(e => e.ParentRelation)
.HasForeignKey(e => e.ParentRelationId );
});
But it looks 'dirty' to store parent relation like this.
What is the right approach?
It's not possible to have just one collection with relations. You need two - one with relations the ticket equals TicketFrom and second with relations the ticket equals TicketTo.
Something like this:
Model:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTo { get; set; }
public virtual ICollection<Relation> RelatedFrom { get; set; }
}
public class Relation
{
public int FromId { get; set; }
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
Configuration:
modelBuilder.Entity<Relation>()
.HasKey(e => new { e.FromId, e.ToId });
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketFrom)
.WithMany(e => e.RelatedTo)
.HasForeignKey(e => e.FromId);
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketTo)
.WithMany(e => e.RelatedFrom)
.HasForeignKey(e => e.ToId);
Note that a solution using Parent is not equivalent, because it would create one-to-many association, while if I understand correctly you are seeking for many-to-many.
Here is very good explanation how to make many-to-many relationship in EF Core
Many-to-many self referencing relationship
Every collection or reference navigation property can only be a part of a single relationship. While many to many relationship with explicit join entity is implemented with two one to many relationships. The join entity contains two reference navigation properties, but the main entity has only single collection navigation property, which has to be associated with one of them, but not with both.
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany() // <-- one of this must be empty
.HasForeignKey(pc => pc.FromId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
Just make sure that WithMany exactly matches the presence/absence of the corresponding navigation property.
Note that you have to turn the delete cascade off.
#IvanStoev is correct. This is an example of a more general self referencing many to many relationship with many parents and many children.
public class Ticket
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public List<TicketTicket> TicketChildren { get; set; }
public List<TicketTicket> TicketParents { get; set; }
}
public class TicketTicket
{
public int TicketChildId { get; set; }
public Ticket TicketChild { get; set; }
public int TicketParentId { get; set; }
public Ticket TicketParent { get; set; }
}
modelBuilder.Entity<TicketTicket>()
.HasKey(tt => new {tt.TicketChildId, tt.TicketParentId});
modelBuilder.Entity<Ticket>()
.HasMany(t => t.TicketChildren)
.WithOne(tt => tt.ProductParent)
.HasForeignKey(f => tt.ProductParentId);
modelBuilder.Entity<Ticket>()
.HasMany(t => t.TicketParents)
.WithOne(tt => tt.TicketChild)
.HasForeignKey(tt => tt.TicketChildId);

How to use composite Ids in one-to-many mappings in fluent nhibernate?

I got a scenario where a composite Id uniquely identifies an entity. I defined the MSSQL to have a multiple primary key on those fields. In addition I would like an auto-incremented id to be used for referencing a one-to-many relationship. Here's the schema:
public class Character
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Region Region { get; set; }
public virtual string Realm { get; set; }
public virtual IList<CharProgression> Progression { get; set; }
}
public class CharProgression
{
public virtual int Id { get; set; }
public virtual Character Character { get; set; }
public virtual Stage Stage { get; set; }
public virtual int ProgressionPoints { get; set; }
public virtual int NumOfSaves { get; set; }
}
public class Stage
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
The mappings look like this:
class CharacterMap : ClassMap<Character>
{
public CharacterMap()
{
Table("characters");
Id(ch => ch.Id, "id").GeneratedBy.Identity().Not.Nullable();
CompositeId().KeyProperty(ch => ch.Region, "region")
.KeyProperty(ch => ch.Realm, "realm")
.KeyProperty(ch => ch.Name, "name");
HasMany<CharProgression>(ch => ch.Progression).Inverse().Cascade.All();
}
}
class CharProgressionMap : ClassMap<CharProgression>
{
public CharProgressionMap()
{
Table("char_progression");
CompositeId().KeyReference(cprog => cprog.Character, "char_id",
.KeyReference(cprog => cprog.Stage, "stage_id");
Id(cprog => cprog.Id, "id").GeneratedBy.Identity().Not.Nullable();
Map(cprog => cprog.ProgressionPoints, "progression_points");
Map(cprog => cprog.NumOfSaves, "num_of_saves");
}
}
public class StageMap : ClassMap<Stage>
{
public StageMap()
{
Table("stages");
Id(st => st.Id, "id").GeneratedBy.Identity().Not.Nullable();
Map(st => st.Name, "name");
Map(st => st.Description, "description");
}
}
Now, the thing is that I would like to use SaveOrUpdate() on a character and use the composite id for the update, since the character uniqueness is defined by those 3 fields - region, realm, name.
However, when I am referencing the Character from CharProgression, I don't want to use the composite Id as I don't want the char_progression table to hold 3 fields for identifying a character, a simple Id is enough... which is why I also defined an IDENTITY id on the Character entity.
Is what i'm trying possible? or is there another way to achieve this?
Thanks :)