To be clear, I am looking to map one entire object and all its properties to different copies of basically the same table. My searches show me how to split an object's properties across multiple tables, but that is not what I am trying to accomplish.
Here is my object model (stripped down):
class Customer
{
public Guid CustomerGuid { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
class Address
{
public Guid AddressGuid { get; set; }
public string Line1 { get; set; }
public string State { get; set; }
}
class Application
{
public Guid ApplicationGuid { get; set; }
public Address Address { get; set; }
public DateTime SubmittedDate { get; set; }
}
The problem is that I need the Address to act sort like a component, but be saved into two separate tables: CustomerAddress and ApplicationAddress, as such:
table Customer
(
CustomerGuid
Name
)
table Application
(
ApplicationGuid
SubmittedDate
)
table CustomerAddress
(
CustomerGuid
Line1
State
)
table ApplicationAddress
(
ApplicationGuid
Line1
State
)
I know I can accomplish one of the mappings using as one-to-one (HasOne) for say Customer to CustomerAddress, but then how can I do the same thing with Application to ApplicationAddress?
for customer and analog for Application
class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id, "CustomerGuid").GeneratedBy.GuidComb();
Map(x => x.Name);
Join("CustomerAddress", join =>
{
join.KeyColumn("CustomerGuid");
join.Component(x => x.Address, c =>
{
c.Map(x => x.Line1);
c.Map(x => x.State);
});
});
}
}
Related
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");
});
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);
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 :)
I've read a lot about Fluent NHibernate's ReferencesAny but I haven't seen a complete example. I think I understand most of it, but there is one part I don't get. In the class mapping ReferencesAny(x => x.MemberName) is used to define the relationship to the one or more referenced classes. What is MemberName? How is it defined and how is it used to create the data in the database.
I have three tables, the records in one table can reference records in one of the other two tables. The first two are auto mapped, so the Id field is not specifically defined.
public class Household
{
public virtual string Name { get; set; }
public virtual IList<AddressXref> AddressXrefs { get; set; }
}
public class Client
{
public virtual string Name { get; set; }
public virtual IList<AddressXref> AddressXrefs { get; set; }
}
I'm not sure if the AddressXref table can be auto mapped. If so I need to find out how to do that too. For now I'll do it the conventional way with Fluent.
public class AddressXref
{
public virtual int id { get; set; }
public virtual string TableName { get; set; }
public virtual Int32 Table_id { get; set; }
public virtual string Street { get; set; }
public virtual string City { get; set; }
}
class AddressXrefMap : ClassMap<AddressXref>
{
public AddressXrefMap()
{
Table("AddressXref");
Id(x => x.id);
Map(x => x.TableName);
Map(x => x.Table_id);
Map(x => x.Street);
Map(x => x.City);
ReferencesAny(x => x.TableRef)
.AddMetaValue<Household>(typeof(Household).Name)
.AddMetaValue<Client>(typeof(Client).Name)
.EntityTypeColumn("TableName")
.EntityIdentifierColumn("Table_id")
.IdentityType<int>();
}
}
The part I need help with is how is the TableRef, referred to in ReferencesAny(), member of AddressXref defined in the class?
Also, how it is used in the code when creating data records? I image it will be similar to this:
Household Household = new Household();
Household.Name = "Household #1";
AddressXref AddrXref = new AddressXref();
AddrXref.Street1 = "123 Popular Street";
AddrXref.City = "MyTown";
AddrXref.TableRef = Household;
Session.SaveOrUpdate(AddrXref);
I love using Fluent with NHibernate, but I'm still amazed at the learning curve. :)
Thanks,
Russ
since both Household and Client don't share a base class other than object you have to declare it as this:
public class AddressXref
{
public virtual int Id { get; set; }
public virtual object TableRef { get; set; }
public virtual string Street { get; set; }
public virtual string City { get; set; }
}
and test it like this
if (addrXref.TableRef is HouseHold)
// it's a household
as the title says, I would like to create a many-to-one relationship using Fluent NHibernate. There are GroupEntries, which belong to a Group. The Group itself can have another Group as its parent.
These are my entities:
public class GroupEnty : IGroupEnty
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IGroup Group { get; set; }
}
public class Group : IGroup
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IGroup Parent { get; set; }
}
And these are the mapping files:
public class GroupEntryMap : ClassMap<GroupEntry>
{
public GroupEntryMap()
{
Table(TableNames.GroupEntry);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Group);
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table(TableNames.Group);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Parent);
}
}
With this configuration, Fluent NHibernate creates these tables:
GroupEntry
bigint Id string Name ... bigint Group_id
Group
bigint Id string Name ... bigint Parent_id bigint GroupEntry_id
I don't know why it creates the column "GroupEntry_id" in the "Group" table. I am only mapping the other side of the relation. Is there an error in my configuration or is this a bug?
The fact that "GroupEntry_id" is created with a "not null" constraint gives me a lot of trouble, otherwise I would probably not care.
I'd really appreciate any help on this, it has been bugging me for a while and I cannot find any posts with a similar problem.
Edit: I do NOT want to create a bidirectional association!
If you want a many-to-one where a Group has many Group Entries I would expect your models to look something like this:
public class GroupEntry : IGroupEntry
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IGroup Group { get; set; }
}
public class Group : IGroup
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
...
public virtual IList<GroupEntry> GroupEntries { get; set; }
public virtual IGroup Parent { get; set; }
}
Notice that the Group has a list of its GroupEntry objects. You said:
I don't know why it creates the column "GroupEntry_id" in the "Group" table. I am only mapping the other side of the relation.
You need to map both sides of the relationship, the many side and the one side. Your mappings should look something like:
public GroupEntryMap()
{
Table(TableNames.GroupEntry);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Group); //A GroupEntry belongs to one Group
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table(TableNames.Group);
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Name).Not.Nullable();
...
References<Group>(x => x.Parent);
//A Group has-many GroupEntry objects
HasMany<GroupEntry>(x => x.GroupEntries);
}
}
Check out the fluent wiki for more examples.
The solution was that I accidentally assigned the same table name for two different entities... Shame on me :(
Thanks a lot for the input though!