Im working on EF Code first, I like abstraction! so want have ItemCat entity like this:
public abstract class EntityBase
{
public int Id { get; set; }
}
public abstract class TreeBase : EntityBase
{
public int? ParentId { get; set; }
public string Name { get; set; }
public virtual TreeBase Parent { get; set; }
public virtual ICollection<TreeBase> Children { get; set; }
}
public abstract class CatBase : TreeBase
{
public string ImageUrl { get; set; }
public string Description { get; set; }
public int OrderId { get; set; }
}
public class ItemCat : CatBase
{
public stringName { get; set; }
// other fields...
public virtual ICollection<Item> Items { get; set; }
}
My map startgey is table per type.TPT
All base classes for ItemCat are decoreated by abstract keyword. but in Migration i get TreeBases Table in Db, really why? i'm wonder because it's abstract. does my mapping need define any configuration explicitly? im using EF 6
Edit also EF in Migration create Discriminator column for TreeBase table and when i insert Record it has ItemCat value.
Edit
protected override void OnModelCreating(DbModelBuilder mb)
{
//Item
mb.Configurations.Add(new TreeBaseConfig());
mb.Configurations.Add(new CatConfig());
}
public class TreeBaseConfig:EntityTypeConfiguration<TreeBase>
{
public TreeBaseConfig()
{
HasMany(rs => rs.Children).WithOptional(rs => rs.Parent).HasForeignKey(rs => rs.ParentId).WillCascadeOnDelete(false);
}
}
public class CatConfig : EntityTypeConfiguration<CatBase>
{
public CatConfig()
{
//properties
Property(rs => rs.Name).IsUnicode();
Property(rs => rs.ImageUrl).IsUnicode();
Property(rs => rs.Description).IsUnicode();
}
}
Edit
I added ItemCatConfig Class:
public ItemCatConfig()
{
//map
Map(m => { m.ToTable("ItemCats"); m.MapInheritedProperties(); });
}
but get:
The type 'ItemCat' cannot be mapped as defined because it maps
inherited properties from types that use entity splitting or another
form of inheritance. Either choose a different inheritance mapping
strategy so as to not map inherited properties, or change all types in
the hierarchy to map inherited properties and to not use splitting.
You have used TPH (Table Per Hierarchy) when you don't do any ToTable() mapping, and TPC (Table Per Concrete Type) when doing both ToTable() and MapInheritedProperties(). If you want to use TPT (Table Per Type) do only the ToTable() mapping and leave the call to MapInheritedProperties() off, like so:
ToTable("ItemCats");
Related
I am having difficulties with Entity Framework. I am trying to set up datamodel with entity refering to its opposite via a connection table.
I have created an entity Style and connection table StyleXStyle.
[Table("Styles")]
public class Style : FullAuditedEntity
{
public virtual string Name { get; set; }
public virtual string ShortName { get; set; }
public List<StyleXStyle> Opposites { get; set; } = new List<StyleXStyle>();
}
[Table("StylesXStyles")]
public class StyleXStyle: FullAuditedEntity
{
public virtual int StyleId { get; set; }
public Style Style { get; set; }
public virtual int OppositeId { get; set; }
public Style Opposite { get; set; }
}
When I try to add database migration, I got this error:
Unable to determine the relationship represented by navigation property 'Style.Opposites' of type 'List<StyleXStyle>'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Since you need to self-refer to a collection of Style entities you should have 2 collection properties for this many-to-many mapping. One to navigate to your opposites and one to "alikes". For my examples I am including a second list property called Alikes.
With that you can include a custom mapping to tell EF how these are related:
Using attribute-based config:
public class Style : FullAuditedEntity
{
[InverseProperty("Style")]
public List<StyleXStyle> Alikes { get; set; } = new List<StyleXStyle>();
[InverseProperty("Opposite")]
public List<StyleXStyle> Opposites { get; set; } = new List<StyleXStyle>();
}
public class StyleXStyle : FullAuditedEntity
{
[ForeignKey("Opposite")]
public virtual int OppositeId { get; set; }
public Style Opposite { get; set; }
[ForeignKey("Style")]
public virtual int StyleId { get; set; }
public Style Style { get; set; }
}
Using Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configures one-to-many relationship
modelBuilder.Entity<StyleXStyle>()
.HasRequired<Style>(s => s.Style)
.WithMany(g => g.Alikes)
.HasForeignKey<int>(s => s.StyleId);
// configures one-to-many relationship
modelBuilder.Entity<StyleXStyle>()
.HasRequired<Style>(s => s.Opposite)
.WithMany(g => g.Opposites)
.HasForeignKey<int>(s => s.OppositeId);
}
The above performs the mapping on the joining entity/table so you can use that as your many-to-many relation. This is necessary if you need to track any information on the relation itself (for example, CreatedDate, CreatedBy, RelationDate, etc.), however, EF provides its own way to map this by convention IF you don't require additional information on the relation entity.
In the latter case, you can map your entities directly to each other and omit the relation table altogether. Here is a quick example:
[Table("Styles")]
public class Style : FullAuditedEntity
{
public virtual string Name { get; set; }
public virtual string ShortName { get; set; }
public ICollection<Style> Opposites { get; set; }
public ICollection<Style> Alikes { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Style>()
.HasMany<Style>(s => s.Alikes)
.WithMany(c => c.Opposites)
.Map(cs =>
{
cs.MapLeftKey("OppositeId");
cs.MapRightKey("StyleId");
cs.ToTable("StyleXStyle");
});
}
The code above was put together for your example, I did not fully test it but have implemented this the same way before.
HTH
I am trying to create a base class for all of my tables in order to make sure I have some data like CreateDate/Person, etc on all of my data.
So basically, I want my tables to inherit from a base class, but I want to have that data on each table separably (table per concrete type).
I've found some tutorials like these, but the problem with them is that I won't have any strongly named property for my tables.
weblogs.asp.net
As you see, in the link he created a dbset of the base class and there is no relation to the concrete class.
public class InheritanceMappingContext : DbContext
{
public DbSet<BillingDetail> BillingDetails { get; set; }
}
Here is my code:
public abstract class TrackableBase
{
public int Id { get; set; }
public virtual DateTime CreateDate { get; protected set; }
public TrackableBase()
{
CreateDate = DateTime.Now;
}
}
public class User : TrackableBase
{
public string Name { get; set; }
public string Email { get; set; }
}
You should add a property of type DbSet for each class and add the OnModelCreating for the mapping
public class InheritanceMappingContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Users");
});
}
}
Is there a better way to accomplish this end-goal of having easily-queryable (and Include-able) cross-sections of a related many-to-many entity stored in the same table?
I started off without implementing TPH in the join table, but that makes consuming one type or another in queries more involved, afaict.
// table Related: [Id]
public class Related
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
public List<RelatedOtherOne> RelatedOtherOnes { get; set; } = new List<RelatedOtherOne>();
public List<RelatedOtherTwo> RelatedOtherTwos { get; set; } = new List<RelatedOtherTwo>();
}
// table RelatedOther: [RelatedId, OtherId, Type]
public abstract class RelatedOther
{
public Guid RelatedId { get; set; }
public Guid OtherId { get; set; }
public Related Related { get; set; }
public Other Other { get; set; }
public abstract RelatedOtherType Type { get; }
}
public class RelatedOtherOne : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.One;
// should be unnecessary, 'Other' should be correct type
public OtherOne OtherOne { get; set; }
}
public class RelatedOtherTwo : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.Two;
// should be unnecessary, 'Other' should be correct type
public OtherTwo OtherTwo { get; set; }
}
public enum RelatedOtherType : int
{
One = 1,
Two = 2
}
// table Other: [Id, OneProp, TwoProp]
public abstract class Other
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
}
public class OtherOne : Other
{
public string OneProp { get; set; }
}
public class OtherTwo : Other
{
public string TwoProp { get; set; }
}
TPH is mapped like this
M2M is mapped like this + discriminator in HasKey()
This gets even more complicated (if not impossible?) when the 'Related' entity evolves into a TPH strategy like the 'Other'.
I have no easy solution but as I stumbled across the same problem I thought I'll share what I have so far.
I found out that I usually need to load all or many types of the relations to the classes of a TPH structure.
So I use the base many-to-many class to load the related objects. Thus this class cannot be abstract:
public class Event2Location
{
[Required]
public Event Event { get; set; }
public int EventId { get; set; }
[Required]
public Location Location { get; set; }
public int LocationId { get; set; }
public byte EntityType { get; set; }
}
The derived class only adds some properties for easier access:
public class Event2Country : Event2Location
{
[NotMapped]
public Country Country
{
get { return base.Location as Country; }
set { base.Location = value; }
}
[NotMapped]
public int CountryId
{
get { return base.LocationId; }
set { base.LocationId = value; }
}
}
In the Event class I have:
public virtual ICollection<Event2Location> Event2Locations { get; set; }
[NotMapped]
public virtual ICollection<Event2Country> Event2Countries => Event2Locations?.OfType<Event2Country>().ToList();
// I should probably add some caching here if accessed more often
[NotMapped]
public virtual ICollection<Event2City> Event2Cities => Event2Locations?.OfType<Event2City>().ToList();
So when I load the joined tables I can use
.Include(e => e.Event2Locations).ThenInclude(j => j.Location)
And I can access the relations of a specific type as needed with the NotMapped Collections.
I still use the derived Event2... classes to add a new relationship.
As you see I have added a column EntityType to the many-to-many class which I use as TPH discriminator. With this column I can also declare which types of Relations/entities I want to load if I do not want to load all.
modelBuilder.Entity<Event2Location>()
.HasDiscriminator<byte>("EntityType")
.HasValue<Event2Location>(0)
.HasValue<Event2Country>(1)
This is surely far from perfect but I finally gave up on optimizing that. First EFCore has to become more mature. Second I want to see how I actually use these structures.
PS: Actually my Location TPH structure has parent-child-relationships within it. Here I did not create a TPH structure for the relation class (as you said - not possible or at least not reasonable). I added ParentType and ChildType. Thus I can determine which relations I actually want to load. Then I fetch the related Locations of the types I need manually on the client side from the result.
I have model like:
public abstract class Entity
{
public int Id { get; set; }
}
public abstract class Tree : Entity
{
public Tree() { Childs = new List<Tree>(); }
public int? ParentId { get; set; }
public string Name { get; set; }
[ForeignKey("ParentId")]
public ICollection<Tree> Childs { get; set; }
}
public abstract class Cat : Tree
{
public string ImageUrl { get; set; }
public string Description { get; set; }
public int OrderId { get; set; }
}
public class ItemCat : Cat
{
...
public virtual ICollection<Item> Items { get; set; }
}
and config classes:
public class CatConfig : EntityTypeConfiguration<Cat>
{
public CatConfig()
{
//properties
Property(rs => rs.Name).IsUnicode();
Property(rs => rs.ImageUrl).IsUnicode();
Property(rs => rs.Description).IsUnicode();
}
}
public class ItemCatConfig :EntityTypeConfiguration<ItemCat>
{
public ItemCatConfig()
{
Map(m => { m.ToTable("ItemCats"); m.MapInheritedProperties(); });
}
}
and DbContext:
public class Db : IdentityDbContext<MehaUser>
{
public Db():base("Db")
{
}
public DbSet<ItemCat> ItemCats { get; set; }
}
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Configurations.Add(new ItemCatConfig());
base.OnModelCreating(mb);
}
but get:
System.NotSupportedException: The type 'ItemCat' cannot be mapped as defined because it maps inherited properties from types that use entity splitting or another form of inheritance. Either choose a different inheritance mapping strategy so as to not map inherited properties, or change all types in the hierarchy to map inherited properties and to not use splitting
Update: I also Read this
Find the answer. just remove Map in ItemCatConfig Class.
Map(m => { m.ToTable("ItemCats"); m.MapInheritedProperties(); });
In TPC abstract classes does not implement in db.
ItemCat inherit from abstract classes and it doesn't need to Map configuration explicitly.
I have two classes that each implement an interface. One of the classes contains an ICollection of the other's interfaces.
Now I want to map this to my database using EF but get an exception (below). Is this supposed to be possible somehow?
Entity definitions for my classes (products and categories):
public interface IProduct
{
string ProductId { get; set; }
string CategoryId { get; set; }
}
public interface ICategory
{
string CategoryId { get; set; }
ICollection<IProduct> Products { get; set; };
}
public class ProductImpl : IProduct
{
public string ProductId { get; set; }
public string CategoryId { get; set; }
}
public class CategoryImpl : ICategory
{
public string CategoryId { get; set; }
public ICollection<IProduct> Products { get; set; }
}
I want to map the relationship between CategoryImpl and ProductImpl so I'm using the following OnModelCreating method in my DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var a = modelBuilder.Entity<CategoryImpl>();
a.ToTable("Categories");
a.HasKey(k => k.CategoryId);
a.Property(p => p.CategoryId);
a.HasMany(p => p.Products).WithOptional().HasForeignKey(p => p.CategoryId);
var b = modelBuilder.Entity<ProductImpl>();
b.ToTable("Products");
b.HasKey(k => k.ProductId);
b.Property(p => p.ProductId);
}
The exception I get is below. Am I supposed to somehow specify that the concrete type to be used for IProduct is ProductImpl?
System.InvalidOperationException: The navigation property 'Products'
is not a declared property on type 'CategoryImpl'. Verify that it has
not been explicitly excluded from the model and that it is a valid navigation property.
It's not possible to do it with interfaces in EF. The type of a navigation property must be mapped for the property to be mapped. And for a type to be mapped it needs to be a concrete type among other things.
If you need to have different types of products and categories you could instead use a base class for them:
public class ProductBase
{
public string ProductId { get; set; }
public string CategoryId { get; set; }
}
public class CategoryBase
{
public string CategoryId { get; set; }
public virtual ICollection<ProductBase> Products { get; set; }
}
public class DerivedProduct : ProductBase
{
}
public class DerivedCategory : CategoryBase
{
}