Entity Framework One-Many TPH Mapping - c#

I'm using a data structure similar to this where type of animal is determined from a discriminator column in the table:
public class Farm {
public int Id { get; set; }
public virtual ICollection<Pig> Pigs { get; set; }
public virtual ICollection<Cow> Cows { get; set; }
}
public class Animal {
public int Id { get; set; }
public int FarmId? { get; set; }
public virtual Farm Farm { get; set; }
public string Name { get; set; }
}
public class Pig : Animal {}
public class Cow : Animal {}
Mapping:
this.Map<Pig>(m => m.Requires("Type").HasValue((int) AnimalType.Pig));
this.Map<Cow>(m => m.Requires("Type").HasValue((int) AnimalType.Cow));
But I can't seem to map the relationship between the Pigs, Cows and Farm. I've tried this from FarmMap which gives a duplicate column mapping error:
this.HasMany(t => t.Pigs)
.WithOptional(t => t.Farm)
.Map(m => m.MapKey("FarmId"));
this.HasMany(t => t.Cows)
.WithOptional(t => t.Farm)
.Map(m => m.MapKey("FarmId"));
Mapping from each of the animals doesn't work either, it generates extra columns (eg. Farm_Id and Farm_Id1 - in addition to FarmId - one for each animal type).
this.HasOptional(t => t.Farm)
.WithMany(t => t.Pigs)
.HasForeignKey(d => d.FarmId)
Moving the navigation property from the Animal model to the inheriting models causes a single additional column to be generated - FarmId1 (so a little closer to what I want than the above 2!)
Is there any way to achieve this?

I'm no EF expert but from the Model-first approach I know that this would be mapped as a collection of Animal, you can then select Farm.Animals.OfType<Pig>()

Related

Entity Framework Core not supporting generic abstract entities with many to many relationships

I have faced a strange problem witch EF Core 1.1. I m trying to build application where some entities can be tagged, thus I've created an abstract generic class for the relation table list. The problem is that, it seems like EF do not support to have a generic abstract classes which FK (Id property works).
Here are models:
public abstract class TaggedEntityBase<T> : EntityBase
{
public ICollection<T> EntityTags { get; set; }
public List<Tag> Tags { get { return EntityTags?.Select(x => x.Tag).ToList(); } }
}
public class AddressTag
{
public long TagId { get; set; }
public Tag Tag { get; set; }
public long EntityId { get; set; }
public Address Entity { get; set; }
}
public class Address : TaggedEntityBase<AddressTag>
{
public string Street { get; set; }
public string City { get; set; }
}
public class Tag : EntityBase
{
public string Name { get; set; }
public virtual ICollection<AddressTag> AddressTags { get; set; }
}
The Model Builder mappings:
public DbSet<Address> Addresses { get; set; }
public DbSet<AddressTag> AddressTag { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AddressTag>()
.ToTable("AddressTag");
modelBuilder.Entity<AddressTag>()
.HasKey(t => new { t.EntityId, t.TagId });
modelBuilder.Entity<AddressTag>()
.HasOne(pt => pt.Entity)
.WithMany(p => p.EntityTags)
.HasForeignKey(p => p.EntityId);
modelBuilder.Entity<AddressTag>()
.HasOne(pt => pt.Tag)
.WithMany(p => p.AddressTags)
.HasForeignKey(p => p.TagId);
}
There is an error when EF try to fetch Tags
An unhandled exception of type 'System.Data.SqlClient.SqlException' occurred in Microsoft.EntityFrameworkCore.dll Additional information: Invalid column name 'AddressId'.
I dont even have that Id convention.
Note: when I place explicitly public ICollection<AddressTag> EntityTags { get; set; }inside Address POCO, then it works perfectly, including EntityTags.Tag too.
Thanks for any help :)
The issue has nothing to do with generic and/or abstract base entity classes.
First, to make your sample model compile, I've added the following classes
public abstract class EntityBase
{
public long Id { get; set; }
}
public abstract class EntityTagBase
{
public long TagId { get; set; }
public Tag Tag { get; set; }
}
modified the AddressTag class as follows:
public class AddressTag : EntityTagBase
{
public long EntityId { get; set; }
public Address Entity { get; set; }
}
and added where T : EntityTagBase constraint to TaggedEntityBase<T> class to allow Tag property accessor inside Select(x => x.Tag).
So far so good. The Tag related part of generated migration looks like this:
migrationBuilder.CreateTable(
name: "Tags",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
AddressId = table.Column<long>(nullable: true),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Tags", x => x.Id);
table.ForeignKey(
name: "FK_Tags_Addresses_AddressId",
column: x => x.AddressId,
principalTable: "Addresses",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
See the AddressId column and FK to Addresses table? Why is that? Because of your Tags property:
public List<Tag> Tags { get { return ...; } }
It's probably a current EF Core bug of mapping a read only collection property, but the net effect is that it considers one to many relationship between Address and Tag which of course is not your intention.
In general I would recommend keeping the entity model clean and not include such "helper" properties - both collection and reference type. They look like navigation properties, but they are not, and it's easy to use them by mistake inside a query, which will totally change the execution plan and lead to unexpected exceptions or wrong results (in case the underlying property is not loaded). Not speaking about the violation of a general rule to not create property returning List which is not a member of the class, but created in every property access call.
Shortly, simply remove that property and the problem will be gone. Or if you insist keeping it, then decorate it with NotMapped data annotation:
[NotMapped]
public List<Tag> Tags { get { return ...; } }

Directing context to use custom named join table for many to many entity relationship

I have 2 entities with a many to many relationship:
[Table("Student", Schema = "School")]
public class Student
{
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
I've specified in the fluent API how to construct the join table as such:
public StudentMap(string schema)
{
//Where schema = "School"
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", schema);
m.MapLeftKey("Student_ID");
m.MapRightKey("Teacher_ID");
});
}
However, when I go to access the Teachers navigation object on the Student, it defaults to the EF convention as opposed to what I've designated for the join table. How would I go about specifying to the SchoolContext that we should be looking at School.StudentsTeachers table instead of dbo.StudentTeachers ?
The problem isn't in the designation of the join table, or the many to many relationship being generated. Those worked fine. The problem arises when attempting to use the Entities, I need a way to specify the relationship should use the Join table I specified, as opposed to the EF naming convention. I was able to resolve a similar issue with EF using its conventions over my table names by using the Table Attribute as shown above. I'm now looking for an equivalent answer except with regards to the many to many join table that exists, but doesn't have an explicit model
I think this should work:
[Table("Student", Schema = "School")]
public class Student
{
[Key,Column("Student_ID")]
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
[Key,Column("Teacher_ID")]
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
public StudentMap()
{
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", "School");
m.MapLeftKey(p => p.ID);
m.MapRightKey(p => p.ID);
});
}

DataAnnotations vs .Map Functions Database Model Mapping?

Which one is better and why?
[Table("Bar")]
public class Bar {
[Key]
public Int32 BarId { get; set; }
public String BarName { get; set; }
public IEnumerable<Foo> Foos { get; set; }
}
or
public class BarMap : EntityTypeConfiguration<Bar> {
public BarMap() {
this.HasMany(t => t.Foos)
.WithMany(t => t.Bars)
.Map(m => {
m.ToTable("FooBarRelationships");
m.MapLeftKey("TheBarId");
m.MapRightKey("TheFooId");
});
}
}
I know the first one is called DataAnnotations but don't know how can we call the second type.
The second is called Fluent API (fluent configuration).
From my point of view, I would prefer the second option which can separate lots of Attributes out of models. It would make the models cleaner.

How to map a related table with no primary key with fluent-NHibernate

Looks a common situation to me: I have two tables:
documents:
dID (pk, int), dName(varchar)
and document_options:
dID (int), oType(int), oValue(varchar)
I would like to have a class Document with a property Options (a List of DocumentOption class)
Since document_options has no PK I cannot use HasMany, and rows from this table don't seem like 'real' entities anyway...
I see a way to generate an auto-number key for document options and map with HasMany, or maybe create a composite ID, but I'd like to know if there is a better option that I don't know about.
In this case, DocumentOptions is a value object, since it has no identity of its own and has no meaning outside of the document it belongs to. So, you would use Component to map the collection properties to the value object.
public class Document : Entity // don't worry about Entity; it's a base type I created that contains the Id property
{
public virtual string Name { get; set; }
public virtual IList<DocumentOptions> Options { get; protected set; }
public Document()
{
Options = new List<DocumentOptions>();
}
}
public class DocumentOptions
{
public virtual int Type { get; set; }
public virtual string Value { get; set; }
}
And the mapping:
public DocumentMap()
{
Table("documents");
Id(c => c.Id)
.Column("dId")
.GeneratedBy.HiLo("10");
Map(c => c.Name)
.Column("dName");
HasMany(c => c.Options)
.Component(c =>
{
c.Map(c2 => c2.Value).Column("oValue");
c.Map(c2 => c2.Type).Column("oType");
})
.Table("document_options")
.KeyColumn("dId")
.Cascade.AllDeleteOrphan();
}
If I understand correctly I had to map options as a list of components:
HasMany(x => x.DocumentOptions)
.Table("document_options")
.KeyColumn("dID")
.Component(c => {
c.Map(x => x.Option, "oID");
c.Map(x => x.Value, "oValue");
})
.Fetch.Subselect(); //This type of join isn't strictly needed, is used for SQL optimization
classes FYI:
public class Options {
public virtual int Option { get; set; }
public virtual int Value { get; set; }
}
public class Document {
public virtual int ID { get; set; }
public virtual String Name { get; set; }
public virtual IList<DocumentOption> DocumentOptions { get; set; }
}

Why are my Fluent NHibernate SubClass Mappings generating redundant columns?

I have the following entities
public abstract class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Product Product { get; set; }
public virtual Sprint Sprint { get; set; }
}
public class Story:Card
{
public virtual double Points { get; set; }
public virtual int Priority { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Story> Stories { get; private set; }
public Product()
{
Stories = new List<Story>();
}
}
And the following mappings
public class CardMap:ClassMap<Card>
{
public CardMap()
{
Id(c => c.Id)
.Index("Card_Id");
Map(c => c.Name)
.Length(50)
.Not.Nullable();
Map(c => c.Description)
.Length(1024)
.Not.Nullable();
References(c=>c.Product)
.Not.Nullable();
References(c=>c.Sprint)
.Nullable();
}
}
public class StoryMap : SubclassMap<Story>
{
public StoryMap()
{
Map(s => s.Points);
Map(s => s.Priority);
}
}
public class ProductMap:ClassMap<Product>
{
public ProductMap()
{
Id(p => p.Id)
.Index("Product_Id");
Map(p => p.Name)
.Length(50)
.Not.Nullable();
HasMany(p => p.Stories)
.Inverse();
}
When I generate my Schema, the tables are created as follows
Card
---------
Id
Name
Description
Product_id
Sprint_id
Story
------------
Card_id
Points
Priority
Product_id
Sprint_id
What I would have expected would have been to see the columns Product_id and Sprint_id ONLY in the Card table, not the Story table.
What am I doing wrong or misunderstanding?
NB: Tested on the NH2 project only
Well, you are probably going to want to chew on a door once you read this, but the TLDR reason is because the Product_id and Spring_id columns in your Story table are not redundant - they exist for the HasMany(x => x.Stories) relations in your SpringMap and ProductMap. They just happen to be share the same naming convention as the CardMap References(x => x.Product and References(x => x.Sprint).
Validate this for yourself by commenting out ProductMap.cs:24-25 and SprintMap.cs:22 and rebuilding.
If the above does not make sense, let me know and I will try to explain in further detail.
So, it should work fine as is. If you want to clarify the columns, you could explicitly define the column names like so:
ProductMap.cs
HasMany(p => p.Stories)
.KeyColumn("ProductOwner_id")
.Inverse();
SprintMap.cs
HasMany(s => s.Stories)
.KeyColumn("SprintOwner_id")
;
CardMap.cs
References(c=>c.Product)
.Column("Product_id")
.Not.Nullable();
References(c=>c.Sprint)
.Column("Sprint_id")
.Nullable();
Here I am guessing that the 1:N relationships between a Story and a Product/Sprint are an "owner". You would want to rename it to whatever is appropriate semantically.
One other thing. I would have thought the last changes (the changes to CardMap.cs) would be unnecessary - but they seem to be for some reason, or the Sprint_id column becomes SprintOwner_id. I have no idea why this would happen - I would speculate that this is some sort of bidirectional relationship inferencing on fluent/nhibernates part gone awry, but I'd put very little money on that.
I see that the Story entity inherits from the Card entity you created, but you don't know why you have Product_Id and Sprint_Id properties in the Story table Schema, since they're virtual properties in the Card class.
I'm guessing that this happens because in NHibernate, all properties need to be virtual but only at first. They don't really stay virtual. The NHibernate framework overrides them, and probably because of this, this is happening to you.

Categories

Resources