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

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.

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)

Invalid column name 'Column' in EF Core Fluent API

I am trying to create a one-to-one relationship in EF Core using Fluent API but when I try to save the model it throws invalid column name exception.
public partial class Child
{
public Guid? ParentId {get;set;} // this is the FK
public virtual Parent {get;set;}
}
public partial class Parent
{
public Guid Id {get;set;}
public virtual Child {get;set;}
}
entity.HasOne(d => d.Child)
.WithOne(p => p.Parent)
.HasForeignKey<Child>(d => d.ParentId)
.HasConstraintName("FK_Parent_Child");
So when I try to save a Parent model to database I keep getting that ParentId is an invalid column name( it does actually exist in the database, and I also have the logger to debug SQL queries and it does point to the right table )

Using Fluent API entity Framework Core 2 configure many-to-many

I have the following class (reduced for bevity);
public class Profile : AuditableEntity
{
...
public int? ApprovedById { get; set; }
public ApplicationUser ApprovedBy { get; set; }
}
Where ApplicationUser has no reference to the Profile class.
I have tried the following in my configuration;
entity.HasOne(x => x.ApprovedBy)
.WithMany()
.HasForeignKey(x => x.ApprovedById)
.OnDelete(DeleteBehavior.SetNull);
As the profile will be approved by a single ApplicationUser but a designated ApplicationUser will be able to approve multiple profile. (I have no need to trace this from the ApplicationUser perspective, hence why no reference in the ApplicationUser class).
However, when I try to add my migration I get the following error;
Unhandled Exception: System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'Profile.ApprovedBy' of type 'ApplicationUser'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Can anyone explain why this is happening and how I would resolve it please?

Reusing a column for a required property with Entity Framework 6.0, Fluent API, and DataAnnotations

I have a base class
public class BaseClass
{
public int Id {get; set;}
}
and two derived classes
public class Foobar: BaseClass
{
[Required]
public int Whatever {get; set;}
}
public class Snafu: BaseClass
{
[Required]
public int Whatever {get; set;}
}
I'm using Table Per Hierarchy inheritance and trying to cut down on my duplicate columns, so with Fluent API I've mapped them like so:
modelBuilder.Entity<Foobar>().Property(fb => fb.Whatever).HasColumnName("Whatever");
modelBuilder.Entity<Snafu>().Property(sf => sf.Whatever).HasColumnName("Whatever");
However, this results in
(137,10) : error 3023: Problem in mapping fragments starting at line 137:Column BaseClass.Whatever in table BaseClass must be mapped: It has no default value and is not nullable.
In EF6 this type of mapping seems to work fine if I take off the [Required] attribute from both subclasses. Adding a [DefaultValue(0)] attribute to both derived classes does not fix the problem.
Any idea how to get these properties to share a column in the database while maintaining their required attribute?
This is actually a bug in EF6. In EF5 the scenario used not to work at all (we would throw an exception in the lines of "column names need to be unique"). While in EF6 we did some work to enable it, but apparently we missed the fact that the shared column has to be nullable in the database even if the property is required in the derived types. The latter is because unless the base class is abstract, you need to be able to store an instance of the base type and for any instance of the base type the column should be null.
I have filed the issue in our bug database:
https://entityframework.codeplex.com/workitem/1924
Feel free to vote for it.
As for a workaround, if having an intermediary type is not an option, you can mark the column as nullable explicitly appending a call to .IsOptional() on the entity configurations. This won't give you exactly what you want because for the purpose of EF data validation this call to IsOptional() on the fluent API will override the [Required] data annotation. However, other flavors of data validation, such as MVC's validation will still honor the attribute.
There are other possible workarounds that I haven't tried, maybe if it is acceptable to use TPT and have both derived types have Whatever live in a different table this would work. I believe any approach that relies on setting a default value won't help because the bug is not only about the table schema not being able to hold an instance of the base class, it is also about the EF mapping generated by Code First not being valid.
UPDATE: This will be fixed in Entity Framework version 6.1.0 which is currently available in beta.
Introducing another type, which contains the required property shared by the other two accomplishes what you're looking for. The entities then look this:
public class BaseClass
{
public int Id { get; set; }
}
public abstract class BaseIntermediaryClass : BaseClass
{
[Required]
public int Whatever { get; set; }
}
public class Foobar : BaseIntermediaryClass
{
}
public class Snafu : BaseIntermediaryClass
{
}
And the mappings like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseIntermediaryClass>().Property(fb => fb.Whatever).HasColumnName("Whatever");
base.OnModelCreating(modelBuilder);
}
Full code of working example can be found here: https://gist.github.com/trayburn/7923392

Overriding EF CodeFirst Generated Database

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.