Entity Framework Table Per Class Inheritance - c#

I am trying to implement a history table for an entity in EF6, code first.
I figured there would be a way to do this with inheritance. The history table, which is a derived type of the actual table entity, just containing straight copies of all the properties. Along with an edit to the key.
My code first table entity config for Booking.
public class BookingEntityConfiguration
: EntityTypeConfiguration<Booking>
{
public BookingEntityConfiguration()
{
Property(b => b.BookingId).HasColumnOrder(0);
HasKey(b => new { b.BookingId });
HasOptional(b => b.BookingType)
.WithMany()
.HasForeignKey(c => c.BookingTypeId);
}
}
My code first table entity config for BookingHistory.
public class BookingHistoryTypeEntityConfiguration
: EntityTypeConfiguration<BookingHistory>
{
public BookingHistoryTypeEntityConfiguration()
{
Property(b => b.BookingId).HasColumnOrder(0);
Property(b => b.BookingVersion).HasColumnOrder(0);
HasKey(b => new { b.BookingId, b.BookingVersion });
}
}
Where
public class BookingHistory : Booking { }
My BookingHistory table never gets generated in the contexts associated database, which includes these references to the table entities:
public DbSet<Booking> Bookings { get; set; }
public DbSet<BookingHistory> BookingHistories { get; set; }
Is there any simple way to achieve what I want? Which is the derived entity (history table) generates a table that contains the same column fields as the base class entity, but with a change of key.
I appreciate my code above is pretty naive, but I can't seem to find a blog post of similar to help.

The best way is to have a base type from which both the entity and its history entity inherit:
public class BookingsContext : DbContext
{
public DbSet<Booking> Bookings { get; set; }
public DbSet<BookingHistory> BookingHistories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BookingBase>()
.HasKey(p => p.BookingId)
.Property(p => p.BookingId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Booking>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Booking");
});
modelBuilder.Entity<BookingHistory>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("BookingHistory");
});
}
}
By ToTable you specify that both entities should be mapped to different tables. On top of that, MapInheritedProperties tells EF to mapp all properties from the base type to this table as well. the result is two completely independent tables that can be addressed by two separate DbSet properties.

Related

Xamarin Forms, EF Core 5 and SQLite: Join table of Many-To-Many is not being cleared when deleting

The join table of a many-to-many relationship in my Xamarin.Forms application seems to not be cleared correctly when deleting one of the two entities.
I have these classes:
public class Input
{
// One-to-many
public ObservableCollection<InputResult> InputResults { get; set; }
//...
// Here are many more entities which shouldn't be relevant for this example
//...
}
public class InputResult
{
// One-to-many
public string ParentInputId { get; set;}
// Many-to-many
public ObservableCollection<MyDropdown> MyDropdowns { get; set; }
}
public class MyDropdown
{
// Many-to-many
public ObservableCollection<InputResult> InputResults { get; set; }
}
I configured the relationships in my DbContext class like this:
modelBuilder.Entity<Input>()
.HasMany(b => b.InputResults)
.WithOne()
.HasForeignKey(b => b.ParentInputId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<InputResult>()
.HasMany(b => b.MyDropdowns)
.WithMany(b => b.InputResults);
Let's say I have previously loaded an Input with its InputResults. Then I delete the InputResults in a helper class like that:
Context.RemoveRange(Input.InputResults);
The InputResults get deleted correctly. When I look into the SQLite database directly I still see all the entries in the join table of InputResults and MyDropdown. Why are there still entries? Yesterday one of our users got a unique constraint error after deleting some data and trying to insert the same data again.
I appreciate any help.
Edit:
To expand my comment on CSharp's answer:
I can't use OnDelete(DeleteBehavior.Cascade) when configuring the DbContext. It seems as EF Core did this correctly by itself though. The part of the join table in the DatabaseContextModelSnapshot.cs looks like this:
modelBuilder.Entity("InputResultMyDropdown", b =>
{
b.HasOne("Inputs.MyDropdown", null)
.WithMany()
.HasForeignKey("MyDropdownId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Inputs.InputResult", null)
.WithMany()
.HasForeignKey("InputResultId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
There should be a cascade delete behavior in DbContext:
modelBuilder.Entity<InputResult>()
.HasMany(b => b.MyDropdowns)
.WithMany(b => b.InputResults)
.OnDelete(DeleteBehavior.Cascade);

How to define navigation for in-class encapsulated property?

I inherited a shared project, where models are defined. For easier XML serialization they are in the form:
public class Blog
{
public int Id { get; set; }
public Posts Posts { get; set; }
}
public class Posts
{
public List<Post> PostsCollection { get; set; }
}
public class Post
{
public int BlogId { get; set; }
public int PostId { get; set; }
public string Content { get; set; }
}
How do I specify EF DbContext in OnModelCreating method to use Posts.PostsCollection as navigation property? Let's assume, I am not allowed to change anything in Post and Blog classes. I just need to programmatically specify relations for EF. Is it possible? I have read about defining relationships on MS site and also other topics about defining model on this site and various others, but couldn't find anything for my scenario.
It's possible, but the intermediate class must be mapped as fake entity, serving as principal of the one-to-many relationship and being dependent of one-to-one relationship with the actual principal.
Owned entity type looks a good candidate, but due to EF Core limitation of not allowing owned entity type to be a principal, it has to be configured as regular "entity" sharing the same table with the "owner" (the so called table splitting) and shadow "PK" / "FK" property implementing the so called shared primary key association.
Since the intermediate "entity" and "relationship" with owner are handled with shadow properties, none of the involved model classes needs modification.
Following is the fluent configuration for the sample model
modelBuilder.Entity<Posts>(entity =>
{
// Table splitting
entity.ToTable("Blogs");
// Shadow PK
entity.Property<int>(nameof(Blog.Id));
entity.HasKey(nameof(Blog.Id));
// Ownership
entity.HasOne<Blog>()
.WithOne(related => related.Posts)
.HasForeignKey<Posts>(nameof(Blog.Id));
// Relationship
entity
.HasMany(posts => posts.PostsCollection)
.WithOne()
.HasForeignKey(related => related.BlogId);
});
The name of the shadow PK/FK property could be anything, but you need to know the owner table name/schema and PK property name and type. All that information is available from EF Core model metadata, so the safer and reusable configuration can be extracted to a custom extension method like this (EF Core 3.0+, could be adjusted for 2.x)
namespace Microsoft.EntityFrameworkCore
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Metadata.Builders;
public static class CustomEntityTypeBuilderExtensions
{
public static CollectionNavigationBuilder<TContainer, TRelated> HasMany<TEntity, TContainer, TRelated>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, TContainer>> containerProperty,
Expression<Func<TContainer, IEnumerable<TRelated>>> collectionProperty)
where TEntity : class where TContainer : class where TRelated : class
{
var entityType = entityTypeBuilder.Metadata;
var containerType = entityType.Model.FindEntityType(typeof(TContainer));
// Table splitting
containerType.SetTableName(entityType.GetTableName());
containerType.SetSchema(entityType.GetSchema());
// Shadow PK
var key = containerType.FindPrimaryKey() ?? containerType.SetPrimaryKey(entityType
.FindPrimaryKey().Properties
.Select(p => containerType.FindProperty(p.Name) ?? containerType.AddProperty(p.Name, p.ClrType))
.ToArray());
// Ownership
entityTypeBuilder
.HasOne(containerProperty)
.WithOne()
.HasForeignKey<TContainer>(key.Properties.Select(p => p.Name).ToArray());
// Relationship
return new ModelBuilder(entityType.Model)
.Entity<TContainer>()
.HasMany(collectionProperty);
}
}
}
Using the above custom method, the configuration of the sample model will be
modelBuilder.Entity<Blog>()
.HasMany(entity => entity.Posts, container => container.PostsCollection)
.WithOne()
.HasForeignKey(related => related.BlogId);
which is pretty much the same (just one additional lambda parameter) as the standard configuration if collection navigation property was directly on Blog
modelBuilder.Entity<Blog>()
.HasMany(entity => entity.PostsCollection)
.WithOne()
.HasForeignKey(related => related.BlogId);
It's not clear from the question, but I assume you only have the Blog and Post table in your database, and the Posts table does not exists and only has a class in the code.
You could have the Blog and Posts entities mapped to the same table as a splitted table and define the navigation property for that. For this you need to add one property to the Posts class (the Id as in the Blog) but you said you are only not allowed to change the Blog and Post classes, and if you need it to XML serialization, you can just mark this property with the [XmlIgnoreAttribute] attribute.
public class Posts
{
[XmlIgnoreAttribute]
public int Id { get; set; }
public List<Post> PostsCollection { get; set; }
}
Then in your OnModelCreating method:
modelBuilder.Entity<Blog>(entity => {
entity.ToTable("Blog");
entity.HasOne(b => b.Posts).WithOne().HasForeignKey<Blog>(b => b.Id);
});
modelBuilder.Entity<Posts>(entity => {
entity.ToTable("Blog");
entity.HasOne<Blog>().WithOne(b => b.Posts).HasForeignKey<Posts>(p => p.Id);
entity.HasMany(p => p.Post).WithOne().HasForeignKey(p => p.BlogId).HasPrincipalKey(p => p.Id);
});
modelBuilder.Entity<Post>(entity => {
entity.ToTable("Post");
entity.HasOne<Posts>().WithMany().HasForeignKey(p => p.BlogId).HasPrincipalKey(p => p.Id);
});

How to add the same column to all entities in EF Core?

Imagine that I want to add an IsDeleted colum or some auditing columns to all of my entities. I could create a base class from which all of my entities will inherit and this will solve my problem, however I cannot specify the order in which the column will be created so I will end up with all the auditing fields before the fields of my entity, which I do not want. I want them to be at the end of the table.
In the standard version of entity framework we can do this by using annotations that specify the order of the columns. However, such a thing does not exist for EF core at the moment.
I could do it with the fluent api on the OnModelCreating() method, the problem is that I only know how to do it individually for each of my entities, which means I would have to write the same code for every entity I have.
Is there any way I can do it generically for all of my entities? Some sort of for loop that iterates through all the entities registered in the DbSets on my dbcontext?
Your question title is about adding the same properties to multiple entities. However, you actually know how to achieve this (use a base type) and your actual question is how to ensure that these properties come last in the generated tables' columns.
Although column order shouldn't really matter nowadays, I'll show an alternative that you may like better than a base type and also positions the common properties at the end of the table. It makes use of shadow properties:
Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model.
Most of the times, auditing properties don't need much visibility in the application, so I think shadow properties is exactly what you need. Here's an example:
I have two classes:
public class Planet
{
public Planet()
{
Moons = new HashSet<Moon>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Moon> Moons { get; set; }
}
public class Moon
{
public int ID { get; set; }
public int PlanetID { get; set; }
public string Name { get; set; }
public Planet Planet { get; set; }
}
As you see: they don't have auditing properties, they're nicely mean and lean POCOs. (By the way, for convenience I lump IsDeleted together with "audit properties", although it isn't one and it may require another approach).
And maybe that's the main message here: the class model isn't bothered with auditing concerns (single responsibility), it's all EF's business.
The audit properties are added as shadow properties. Since we want to do that for each entity we define a base IEntityTypeConfiguration:
public abstract class BaseEntityTypeConfiguration<T> : IEntityTypeConfiguration<T>
where T : class
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property<bool>("IsDeleted")
.IsRequired()
.HasDefaultValue(false);
builder.Property<DateTime>("InsertDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
builder.Property<DateTime>("UpdateDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
}
}
The concrete configurations are derived from this base class:
public class PlanetConfig : BaseEntityTypeConfiguration<Planet>
{
public override void Configure(EntityTypeBuilder<Planet> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
// Follows the default convention but added to make a difference :)
builder.HasMany(p => p.Moons)
.WithOne(m => m.Planet)
.IsRequired()
.HasForeignKey(m => m.PlanetID);
base.Configure(builder);
}
}
public class MoonConfig : BaseEntityTypeConfiguration<Moon>
{
public override void Configure(EntityTypeBuilder<Moon> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
base.Configure(builder);
}
}
These should be added to the context's model in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PlanetConfig());
modelBuilder.ApplyConfiguration(new MoonConfig());
}
This will generate database tables having columns InsertDateTime, IsDeleted and UpdateDateTime at the end (independent of when base.Configure(builder) is called, BTW), albeit in that order (alphabetical). I guess that's close enough.
To make the picture complete, here's how to set the values fully automatically in a SaveChanges override:
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries()
.Where(e => e.Properties.Any(p => p.Metadata.Name == "UpdateDateTime")
&& e.State != Microsoft.EntityFrameworkCore.EntityState.Added))
{
entry.Property("UpdateDateTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
}
Small detail: I make sure that when an entity is inserted the database defaults set both fields (see above: ValueGeneratedOnAdd(), and hence the exclusion of added entities) so there won't be confusing differences caused by client clocks being slightly off. I assume that updating will always be well later.
And to set IsDeleted you could add this method to the context:
public void MarkForDelete<T>(T entity)
where T : class
{
var entry = this.Entry(entity);
// TODO: check entry.State
if(entry.Properties.Any(p => p.Metadata.Name == "IsDeleted"))
{
entry.Property("IsDeleted").CurrentValue = true;
}
else
{
entry.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
}
}
...or turn to one of the proposed mechanisms out there to convert EntityState.Deleted to IsDeleted = true.
You can always generate an initial migration for the model and manually rearrange the column order in the Migration.
Here is the open issue tracking support for explicit column ordering in EF Core: https://github.com/aspnet/EntityFrameworkCore/issues/10059
Also see this question and answer on using Shadow Properties and Query Filters for soft deletes. EF Core: Soft delete with shadow properties and query filters

EF 6 Configured for one-to-one relationship sometimes violates the constraint

I'm using Fluent API in EF 6.1.3 to define a one-to-one relationship between the entity User and the entity EcommerceCart. Everything seems to work fine 99.9% of the time, but every once in a while our exception logger tells us that while trying to access User.Cart the following exception is thrown:
A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object.
We checked the database, and it seems that Entity Framework managed to create 2 Carts for the same User, despite the one-to-one relationship of the entity types.
And what strikes me the most is that the exception never occurs when the entities are created, but when the code tries to access User.Cart, finding more than one result in the database.
Any idea of how this could happen?
PS: I'm using Lazy Loading, although I don't think this should make a difference.
These are my entities:
public class User
{
public Guid Id { get; set; }
public virtual EcommerceCart Cart{ get; set; }
}
public class EcommerceCart
{
public Guid Id { get; set; }
public virtual User User { get; set; }
}
These are my configuration files:
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
HasOptional(x => x.Cart).WithOptionalPrincipal(y => y.User).Map(x => x.MapKey("User_Id"));
}
}
public class EcommerceCartConfiguration : EntityTypeConfiguration<EcommerceCart>
{
public EcommerceCartConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
}
}
And this is how the EcommerceCarts table looks like:
Following adam0101 advice, I created the following migration, which forces
the foreign key User_Id to be unique:
public partial class EcommerceCart_User_Index : DbMigration
{
public override void Up()
{
DropIndex("dbo.EcommerceCarts", new[] { "User_Id" });
Sql(
#"CREATE UNIQUE NONCLUSTERED INDEX IX_User_Id
ON dbo.EcommerceCarts(User_Id)
WHERE User_Id IS NOT NULL");
}
}
Unfortunately, I couldn't find any other solution using Fluent API, because it seems like there is no way to create a unique index on Navigation properties without using SQL.
If anyone knows a better solution, please let me know.

Base Types with EntityFramework CodeOnly

I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework. I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework.
When I try to run this code using a DbContext with a DbSet<Template>, I get the following error.
A The navigation property 'Flags' is mapped to two different join tables 'page.flags' and 'template.flags'. Only one mapping of the navigation property may exist
What this says to me is that I cannot map inherited properties. This is quite breaking to a lot of object oriented code design. Is there a known remedy? I realize I can make Layout non-abstract, and have a backing for it, but it's very obvious this is not the intention of the domain model. The abstract class is a foundational base, not the stored model.
I would like to add, if I put the IList<Flag> in the Template class, this code runs. The Id field still works, even through inheritance. I do not understand why this is happening. Can someone enlighten me?
public abstract class Layout
{
public virtual int Id
{
get;
set;
}
public virtual IList<Flag> Flags
{
get;
set;
}
}
public class Template : Layout
{
public virtual string Name
{
get;
set;
}
}
public class Page: Layout
{
}
public class LayoutConfiguration : EntityConfiguration<Layout>
{
public LayoutConfiguration()
{
HasKey(u => u.Id);
Property(u => u.Id).IsIdentity();
MapHierarchy().Case<Page>(c => new
{
c.Id
}).ToTable("Pages");
MapHierarchy().Case<Template>(c => new
{
c.Id,
c.Name
}).ToTable("Templates");
}
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
Property(o => o.Name).HasMaxLength(64).IsUnicode();
HasMany(u => u.Flags).WithOptional()
.Map("template.flags",
(template, flag) => new {
Template = template.Id,
Flag = flag.Id
});
MapSingleType(c => new {
c.Id,
c.Name
}).ToTable("templates");
}
}
public class PageConfiguration : EntityConfiguration<Page>
{
public PageConfiguration()
{
HasMany(c => c.Flags).WithOptional()
.Map("page.flags",
(page, flag) => new
{
Page = page.Id,
Flag = flag.Id
});
}
}
When you use base type for your Template entity, you also have to model this inheritance in mapping. It means that you have to write configuration for Layout which will map Id and Flags and configuration for Template which will map Name. There is several approaches of mapping inheritance in EF. You should probably check Table per Hiearchy.
Edit: Based on your comment you are looking for Table per Class + examples for CTP4.
Edit2: Ok. I tested your scenario with navigation property defined in abstract parent class and it really doesn't work if you are trying to map it to multiple tables.

Categories