Overriding EF CodeFirst Generated Database - entity-framework

I have a C# project that uses the EF CodeFirst approach. My problem is how EF is interpreting my classes and generating the database tables. EF is inferring too many things and my resulting db is not the way I would like. Specifically, it is generating additional id columns in one of my mapping classes.
Here are my POCO classes:
public partial class Attribute
{
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<EntityAttribute> EntityAttributes {get;set;}
}
public partial class Grant
{
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<EntityAttribute> EntityAttributes {get;set;}
}
public partial class Donor
{
public int Id {get;set;}
public string Name {get;set;}
public virtual ICollection<EntityAttribute> EntityAttributes {get;set;}
}
public enum EntityTypeEnum
{
Grant = 1,
Donor = 2
}
public partial class EntityAttribute
{
public int Id {get;set;}
public int EntityId {get;set;}
public int AttributeId {get;set;}
public int EntityTypeId {get;set;}
public EntityTypeEnum EntityType
{
get{return (EntityTypeEnum)this.EntityTypeId;}
set{this.EntityTypeId = (int)value;}
}
public virtual Grant Grant {get;set;}
public virtual Donor Donor {get;set;}
}
My mapping classes are typical but here is the EntityAttributeMap class:
public partial class EntityAttributeMap : EntityTypeConfiguration<EntityAttribute>
{
public EntityAttributeMap()
{
this.ToTable("EntityAttribute");
this.HasKey(ea => ea.Id);
this.Property(ea => ea.EntityTypeId).IsRequired();
this.Ignore(ea => ea.EntityType);
this.HasRequired(ea => ea.Grant)
.WithMany(g => g.EntityAttributes)
.HasForeignKey(ea => ea.EntityId);
this.HasRequired(ea => ea.Donor)
.WithMany(d => d.EntityAttributes)
.HasForeignKey(ea => ea.EntityId);
this.HasRequired(ea => ea.Attribute)
.WithMany(a => a.EntityAttributes)
.HasForeignKey(ea => ea.AttributeId)
}
}
All of my unit tests perform as expected. However, the table EntityAttribute gets rendered with DonorId and GrantId columns. I don't want this as I actually have dozens of other "EntityTypes" that will be used for this scenario. That is why I chose the EntityTypeEnum class.
What am I doing wrong? Or is there another way I should be mapping these so EF handles things the way I want. Thanks.

The EF doesn't support enums at all, as of V4 CTP 5. They might be included in the next release.
Having said that, the schema looks (to me; it's not clear from your post, and your intentions may be different) too close to an EAV for my comfort. For the usual reasons (Google it) I dislike these, and wouldn't want that sort of model even with enum support in the EF.
Why not map to another entity type instead of an enum?
If you ask a question in the form of "Here are my business needs; what is the best schema for this?" you may get a better answer.

Related

EF Core 3.1 - build tree structure from different entities

Im developing a warehouse system, in which I have Pack and Load entities.
The real life structure looks something like that:
Warehouse:
Pack1
Load1
Pack2
Pack3
Load2
Pack4
Pack5
Load3
Pack6
Load4
etc.
As you can see, the Packs can be inside other Packs, and those can be inside yet other Packs, and at the end of the chains, there are Loads.
Pack and Load entities are quite different, the only thing that they have in common is that they are a part of the mentioned tree structure.
I was trying to enable that by creating a base abstract class (StorageEntity), from which Packs
and Loads can inherit.
The StorageEntity class would have fields that allow using the objects as tree nodes:
public abstract class StorageEntity
{
public int ContainerId { get; set; }
public virtual StorageEntity Container { get; set; }
public virtual ICollection<StorageEntity> Content { get; set; }
}
My problems:
I don't have an idea how to make this work with the Entity Framework. I was trying to use fluid API to configure the mechanism like so:
modelBuilder.Entity<StorageEntity>()
.HasMany(p => p.Content)
.WithOne(s => s.Container);
but when I try to add the migration I get an error:
The entity type 'StorageEntity' requires a primary key to be defined. If you intended to use a keyless entity type call 'HasNoKey()'.
If I add HasNoKey() to the configuration I get a null reference exception instead.
Is that approach even the proper one? What are alternatives?
This is very common Node structure, as seens with Folders & Files.
Usually you'd declare one root folder (top level node) with registered subfolders and files as collections (subnodes) on the aggregate entity or letting the root folder be the aggregate.
public abstract class BaseEntity
{
public long Id {get; set;}
}
public class Pack : BaseEntity
{
public long StorageEntityId {get; set;}
public virtual StorageEntity ParentStorageEntity {get; set;}
public virtual Pack ParentPack {get; set;}
public virtual ICollection<Pack> SubPacks {get; set;}
public virtual ICollection<Load> Loads {get; set;}
}
public class Load : BaseEntity
{
// Load entity's props here
}
public class StorageEntity : BaseEntity
{
public virtual Pack RootPack {get; set;}
}
all your Database entities should have a primary key.
and this should and would scaffold your migration correctly without configuration. Though for easier understanding on what is actually configured on the database I can only recommend using explicit fluent configuration for EF Core.
modelBuilder.Entity<StorageEntity>()
.HasOne(t => t.RootPack)
.WithOne(t => t.ParentStorageEntity)
.HasForeignKey<Pack>(t => t.ParentStorageEntityId);
modelBuilder.Entity<Pack>()
.HasMany(t => t.SubPacks)
.WithOne(t => t.ParentPack);
modelBuilder.Entity<Pack>
.HasMany(t => t.Loads)
.WithOne();
modelBuilder.Entity<Pack>
querying it can be a bit tricky,
but you could do something like
var flattenedPacks = await _context.Packs.Where(t => t.ParentStorageEntityId == storageId).ToListAsync();
var flattenedLoads = await _context.Loads.Where(t => t.ParentStorageEntityId == storageId).ToListAsync();
// assemble them in a map method or use changetracking through EF core
var rootPack = flattenedPacks.FirstOrDefault(t => t.ParentPack == null);
root packs structure should now because of EF Core's changetracking have the correct structure. :o)

Entity framework - virtual table for multiple entities (code first)

We are building a Context with multiple entities. All entities build on a abstract class with ID (guid).
We want all entities to have relation to a common Log table, however can't get EFCF to understand the relationship due to the naming.
public class Log {
public BaseEntity Entity {get;set;}
public Guid EntityID {get;set;}
}
public class Example : BaseEntity {
public virtual ICollection<Log> Logs {get;set;}
}
Can anyone help on a pattern which support? We've tried abstracting, setting up the OnModelCreating etc. but keep getting fuzzy errors due to the none-supported naming. If we add these;
[ForeignKey("EntityID")]
public Example Example {get;set;}
[ForeignKey("EntityID")]
public Example5 Example5 {get;set;}
[ForeignKey("EntityID")]
public Example2 Example2 {get;set;}
set of properties on the Log class, everything works fine. Problem occurs when adding a new Log entry.
I would a possible and working solution, abstracting the common fields into a base class.
Using the MapInheritedProperties() in the model building (and followed)
modelBuilder.Entity<Models.Example>().Map(x => x.MapInheritedProperties());
modelBuilder.Entity<Models.Log>()
.HasRequired(x => x.BaseEntity)
.WithMany(x => x.Logs)
.Map(x =>
{
x.ToTable("Example");
x.MapKey(new[] { "ID" });
})
.WillCascadeOnDelete(false);
I can now assign the "virtual" table to any entity, as long as they derive from the base class.

In Entity Framework, is it possible to auto-map a column to an entity property? ID => [tablename]ID

I want to use .Id in my entity classes for the unique id, but our dba wants [tablename]Id in the database tables. Is there a way that Entity Framework can make this mapping automatically without having to create a new map file for every entity?
As long as I understand you correctly, you have something like:
public class Foo
{
public Int32 ID { get; set; }
// ...
}
public class Bar
{
public Int32 ID { get; set; }
// ...
}
And, without too much effort (or creating multiple entityTypeConfiguration<T> models) you'd like something along the lines of the following outcome:
Current Mapping Desired Mapping
[Foo] [Foo]
ID FooID
... ...
[Bar] [Bar]
ID BarID
... ...
For this, a few methods exist (and depend on which version of EF you're using). With that said, some approachable tactics:
ColumnAttribute
You can visit each entity model and decorate the ID property with the ColumnAttribute. This tells EF that, despite what we named the column, we want something else to be the name within the database. e.g.
public class Foo
{
[Column("FooID")]
public Int32 ID { get; set; }
// ...
}
public class Foo
{
[Column("BarID")]
public Int32 ID { get; set; }
// ...
}
The only problem here is that you're now going to every model and adding the attribute.
OnModelCreating & Fluent Mapping
Another method is to do the mapping but keep it all in one place. The OnModelCreating event is great for this kind of thing.
public class MyDbContext : DbContext
{
public Dbset<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnmodelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>()
.Property(x => x.ID).HasColumnName("FooID");
modelBuilder.Entity<Bar>()
.Property(x => x.ID).HasColumnName("BarID");
}
}
Again, the problem here is that you're creating a configuration for each entity.
Custom Conventions
As of EF6, you can use Custom Conventions which make things easier (Including developing your own convention that would make ID=TableNameID). Unfortunately I don't have the time to write an example, but the docs are pretty enlightening.
According to MSDN , both way should work.
Primary key detection is case insensitive. Recognized naming patterns
are, in order of precedence: 'Id' [type name]Id

Multiple level of inheritance in EF Code First Configuration

I have an abstract base class for a few entities I'm defining. One of those derived entities is actually a non-abstract base class to another entity.
Following this code:
public abstract class BaseReportEntry {
public int ReportEntryId { get; set;}
public int ReportBundleId { get; set; } //FK
public virtual ReportBundle ReportBunde { get; set; }
}
//A few different simple pocos like this one
public PerformanceReportEntry : BaseReportEntry {
public int PerformanceAbsolute { get; set; }
public double PerformanceRelative { get; set; }
}
//And one with a second level of inheritance
public ByPeriodPerformanceReportEntry : PerformanceReportEntry {
public string Period { get; set; }
}
I'm using a base EntityTypeConfiguration:
public class BaseReportEntryMap<TReportEntry> : EntityTypeConfiguration<TReportEntry>
where TReportEntry : BaseReportEntry
{
public BaseReportEntryMap()
{
this.HasKey(e => e.ReportEntryId);
this.HasRequired(e => e.ReportsBundle)
.WithMany()
.HasForeignKey(e => e.ReportsBundleId);
}
}
Presumably this works fine for the one-level of inheritance but throw the following error for that one case where it has a second level:
The foreign key component 'ReportsBundleId' is not a declared property on type 'ByPeriodPerformanceReportEntry'
public class ByPeriodPerformanceReportEntryMap : BaseReportEntryMap<ByPeriodPerformanceReportEntry>
{
public ByPeriodPerformanceReportEntryMap ()
: base()
{
this.Property(e => e.Period).IsRequired();
this.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("ByPeriodPerformanceReportEntries");
});
}
}
Here's ReportBundle class if needed
public class ReportsBundle
{
public int ReportsBundleId { get; set; }
public virtual ICollection<PerformanceReportEntry> PerformanceReportEntries{ get; set; }
public virtual ICollection<ByPeriodPerformanceReportEntry> ByPeriodPerformanceReportEntries{ get; set; }
}
The problem is not so much the second level of inheritance but that PerformanceReportEntry (the base of ByPeriodPerformanceReportEntry) is an entity while BaseReportEntry (the base of PerformanceReportEntry) is not.
Your mapping would work if PerformanceReportEntry would not be an entity - i.e. its mapping is not added to the model builder configuration and you have no DbSet for this type and it would not occur in a navigation collection in ReportsBundle.
Deriving the configuration from BaseReportEntryMap<ByPeriodPerformanceReportEntry> is not possible in this case - and it is not necessary because the mapping for the base properties already happened by the BaseReportEntryMap<PerformanceReportEntry>. Therefore you can use
public class ByPeriodPerformanceReportEntryMap
: EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
But I have doubt that the resulting model is as you would expect it. I don't know what the PerformanceReportEntries and ByPeriodPerformanceReportEntries collections in ReportsBundle are supposed to express. Do you expect that ByPeriodPerformanceReportEntries is a collection filtered by the subtype? Do you expect that PerformanceReportEntries contains only the ReportsEntries that are PerformanceReportEntrys but not ByPeriodPerformanceReportEntrys? Do you expect that PerformanceReportEntries contains all entries including the ByPeriodPerformanceReportEntries?
Anyway, BaseReportEntry.ReportBundle is a navigation property mapped in PerformanceReportEntry (not in ByPeriodPerformanceReportEntry). That means that the inverse navigation property in class ReportsBundle must refer to PerformanceReportEntry which is the PerformanceReportEntries navigation collection. ByPeriodPerformanceReportEntries will introduce a second one-to-many relationship between ReportsBundle and ByPeriodPerformanceReportEntry (without a navigation property in ByPeriodPerformanceReportEntry). The inverse navigation property of ByPeriodPerformanceReportEntries will NOT be BaseReportEntry.ReportBundle.
My feeling is that you should not have the ReportsBundle.ByPeriodPerformanceReportEntries collection, but I am not sure what you want to achieve exactly.
Edit
Refering to your comment that you only have these two Report types your mapping is way too complicated in my opinion. I would do the following:
Remove the BaseReportEntry class and move its properties into PerformanceReportEntry. It makes no sense to have a base class that only one single other class derives from.
Remove the ByPeriodPerformanceReportEntries from ReportsBundle, so that ReportsBundle will be:
public class ReportsBundle
{
public int ReportsBundleId { get; set; }
public virtual ICollection<PerformanceReportEntry>
PerformanceReportEntries { get; set; }
}
Remove the BaseReportEntryMap and move the mapping into PerformanceReportEntryMap. Derive this map from EntityTypeConfiguration<PerformanceReportEntry>.
Correct the mapping. Currently it is wrong because you don't specify the inverse navigation property in WithMany. PerformanceReportEntryMap should look like this:
public class PerformanceReportEntryMap
: EntityTypeConfiguration<PerformanceReportEntry>
{
public PerformanceReportEntryMap()
{
this.HasKey(e => e.ReportEntryId);
this.HasRequired(e => e.ReportsBundle)
.WithMany(b => b.PerformanceReportEntries)
.HasForeignKey(e => e.ReportsBundleId);
}
}
Derive ByPeriodPerformanceReportEntryMap from EntityTypeConfiguration<ByPeriodPerformanceReportEntry> and specify only mappings for properties that are declared in ByPeriodPerformanceReportEntry, not again for the base properties. That already happened in PerformanceReportEntryMap. You don't need and can't specify it again because it will cause exactly the exception you had.
Use Table-Per-Hierarchy (TPH) inheritance instead of Table-Per-Concrete-Type (TPC), especially if you only have a few properties declared in ByPeriodPerformanceReportEntry. TPC is more difficult to use because it has problems with database-generated identities and with polymorphic associations (which you have in your relationship between PerformanceReportEntry and ReportsBundle). The problems are explained in more details here. TPH instead offers the best performance. ByPeriodPerformanceReportEntryMap would then look like this:
public class ByPeriodPerformanceReportEntryMap
: EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
{
public ByPeriodPerformanceReportEntryMap()
{
this.Property(e => e.Period).IsRequired();
}
}
No explicit configuration for TPH is necessary because it is the default inheritance mapping.

entityframework Inherited classes in single table can not use timestamp?

I want to create a TimeStamp field in Inherited class like this:
[Table("TABLE_A")]
public class A
{
public int ID {get;set;}
public string Name {get;set;}
}
[Table("TABLE_B")]
public class B : A
{
public string Address {get;set;}
[TimeStamp]
public byte[] RowVersion {get;set;}
}
but failed, how can I do here ?
You will see error
Type 'B' defines new concurrency requirements that are not allowed for
subtypes of base EntitySet types.
That means exactly what error says. Entity Framework do not support concurrency checks in derived types. You will see same error if you'll add simple concurrency check instead of timestamp:
[Table("TABLE_B")]
public class B : A
{
[ConcurrencyCheck]
public string Address { get; set; }
}
If you will move concurrency checking to base class, then it will work, but only on base type. If you need checking to be performed on derived type, I think you should use Stored Procedure for updating entity.