Does Entity Framework support generic relations? - c#

Does Entity Framework support generic relations?
E.g.
public class Comment<T> : Entity
where T : Entity
{
public string Text { get; set; }
public long EntityId { get; set; }
public T Entity { get; set; }
}
public class DocumentComment : Comment<Document> {
}
public class DeliveryComment : Comment<Delivery> {
}
UPDATE: I'll expand on my process as I tried this.
I tried creating configurations to make it work as I figured I needed to specify the types of variables but got an error when trying to add the migration.
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(nameof(Comment));
builder.Property(x => x.Id);
builder.HasKey(x => x.Id);
builder.HasOne(x => x.Entity)
.WithMany()
.HasForeignKey(x => x.EntityId)
.OnDelete(DeleteBehavior.ClientCascade)
}
}
public class DocumentCommentConfiguration : CommentConfiguration<DocumentComment,Document>
{
public Configure(EntityTypeBuilder<DocumentComment> builder)
{
}
}
public class DeliveryCommentConfiguration : CommentConfiguration<DeliveryComment,Delivery>
{
public Configure(EntityTypeBuilder<DeliveryComment> builder)
{
}
}

Actually using conventions based setup worked just fine for me, so (at least based on provided info) you don't need to provide IEntityTypeConfiguration's.
If you still want to then (apart from the compilation issues, cause currently provided code is not compliable) you need to fix the table name in CommentConfiguration<TU,T> (by default you can't map different entity types to the same table). For example:
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(typeof(TU).Name);
// ... rest of the config
}
}
Full code sample used for testing.
Also can be useful - inheritance in EF Core.

Related

EF and existing database and table, with a bunch of new tables

EF and existing database and table, with a bunch of new tables.
Context:
An old database has a Table User that the new application will use.
I used the entity DataModel Wizard to Map it this .
What I have now :
A bunch of new entities and their configuration:
public class Bundle
{
public Bundle() { Produits = new HashSet<Produit>(); }
public int BundleID { get; set; }
public string Designation { get; set; }
public ICollection<Produit> Produits { get; set; }
}
public class BundleConfiguration : EntityTypeConfiguration<Bundle>
{
public BundleConfiguration()
{
ToTable("PL_Bundle");
HasKey(e => e.BundleID);
Property(e => e.Designation).HasMaxLength(200);
}
}
A DbContext with an extension using reflection to map all available configuration.
public partial class FooBarDbContext : DbContext
{
public FooBarDbContext() : base("name=Model1")
=> Database.SetInitializer<FooBarDbContext>(null);
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ApplyAllConfigurations();
base.OnModelCreating(modelBuilder);
}
public DbSet<User> User { get; set; }
public DbSet<Bundle> Bundles { get; set; }
// [...]
}
public static class ModelBuilderExtensions
{
public static void ApplyAllConfigurations(this DbModelBuilder modelBuilder)
{
IEnumerable<Type> typesToRegister
= AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(t => t.GetTypes())
.Where(t => t.IsClass)
.Where(type => !string.IsNullOrEmpty(type.Namespace) &&
type.GetTypeInfo().BaseType != null &&
type.GetTypeInfo().BaseType.GetTypeInfo().IsGenericType &&
type.GetTypeInfo().BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
.ToArray();
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
}
}
In the calling application I am trying to test the Database and table to check everything works..
static void Main(string[] args)
{
var db = new FooBarCore.DbContext.FooBarDbContext();
var all = db.User.ToList(); // Work
var bundles1 = db.Bundles.ToList();
The previous line give me error either :
Nom d'objet 'dbo.PL_Bundle' non valide. => Invalid object name 'dbo.PL_Bundle'
What I tried:
Finding if ModelBuilderExtensions was an issue. But using classical modelBuilder.Configurations.Add(new BundleConfiguration());.
Or using fluent API directly in the OnModelCreating throw the same error.
Check the database => Table are not here.
Search for EF not create table, but CreateDatabaseIfNotExist, DropCreateDatabaseWhenModelChanges, DropCreateDatabaseAlways are scary, they all have this Drop Database in the name.
so, as stated in comment:
Nom d'objet 'dbo.PL_Bundle' non valide. => Invalid object name
'dbo.PL_Bundle'
this means, that EF thinks that there should be a table PL_Bundle, but there isn't. It isn't there, because you added new class into your model.
For EF to make proper change to the database, you should mechanism called Migrations
You have to enable migrations, generate migration and apply it to the database.
Good practice is to store the migrations also as SQL scripts (by using UpdateDatabase -script) and make system admin apply them manually to prevent any secret datalosses if you remove a column or a table for example.
You ship the scripts with new versions of software and it should crash until the migration is applied, stating in logs which one is needed.
Update your DbContext class to this:
public partial class FooBarDbContext : DbContext
{
public FooBarDbContext() : base("name=Model1")
=> Database.SetInitializer<FooBarDbContext>(null);
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Configurations for Bundle class
modelBuilder.Entity<Bundle>()
.ToTable("PL_Bundle")
.HasKey(e => e.BundleID);
.Property(e => e.Designation).HasMaxLength(200);
modelBuilder.ApplyAllConfigurations();
base.OnModelCreating(modelBuilder);
}
public DbSet<User> User { get; set; }
public DbSet<Bundle> Bundles { get; set; }
// [...]
}

Entity Framework Core - More than one Discriminator for derived classes

I have a model that is structurd as follows (fields ommited for brevity):
public enum ActivityType { Production, Downtime }
public enum ActivityStatus { Planned Actual }
public abstract class Activity
{
public virtual ActivityType ActivityType {get;}
public virtual ActivityStatus ActivityStatus {get;}
}
public abstract class PlannedActivity : Activity
{
public override ActivityStatus ActivityStatus => ActivityStatus.Planned;
}
public abstract class ActualActivity : Activity
{
public override ActivityStatus ActivityStatus => ActivityStatus.Actual;
}
Then the concrete 'Entity' classes
public class PlannedDowntime : PlannedActivity
{
public override ActivityType ActivityType => ActivityType.Downtime;
}
public class PlannedProduction : PlannedActivity
{
public override ActivityType ActivityType => ActivityType.Production;
}
public class ActualDowntime : ActualActivity
{
public override ActivityType ActivityType => ActivityType.Downtime;
}
public class ActualProduction : ActualActivity
{
public override ActivityType ActivityType => ActivityType.Production;
}
I'm trying to save all data to a single database table (i.e. TPH).
In EF6, the following code achieves the database and model structure that I'm looking for:
modelBuilder.Entity<Activity>()
.Map<PlannedProductionActivity>(m =>
{
m.Requires("ActivityType").HasValue("Production");
m.Requires("ActivityStatus").HasValue("Planned");
})
.Map<PlannedDowntimeActivity>(m =>
{
m.Requires("ActivityType").HasValue("Downtime");
m.Requires("ActivityStatus").HasValue("Planned");
})
.Map<ActualProductionActivity>(m =>
{
m.Requires("ActivityType").HasValue("Production");
m.Requires("ActivityStatus").HasValue("Actual");
})
.Map<ActualDowntimeActivity>(m =>
{
m.Requires("ActivityType").HasValue("Downtime");
m.Requires("ActivityStatus").HasValue("Actual");
});
But...I need to move this over to EF Core (2.1), and I'm struggling. The following code is the closest I can get, but when migrating it fails with the error 'Every concrete entity type in the hierarchy needs to have a unique discriminator value.
modelBuilder.Entity<Activity>()
.HasDiscriminator<ActivityStatus>("ActivityStatus")
.HasValue<PlannedActivity>(ActivityStatus.Planned)
.HasValue<ActualActivity>(ActivityStatus.Actual);
modelBuilder.Entity<Activity>()
.HasDiscriminator<ActivityType>("ActivityType")
.HasValue<PlannedDowntimeActivity>(ActivityType.Downtime)
.HasValue<ActualDowntimeActivity>(ActivityType.Downtime)
.HasValue<PlannedProductionActivity>(ActivityType.Production)
.HasValue<ActualProductionActivity>(ActivityType.Production);
Please can anyone cast some light on how to have more than one discriminator?
Well, seems a little odd to need two discriminators -
but you could structure it so that Activity has a discriminator and then the sub classes also have their own discriminators
alternatively make a complex discriminator
modelBuilder.Entity<Activity>()
.HasDiscriminator(x=> new {x.ActivityStatus, x.ActivityType})
.HasValue<PlannedDowntimeActivity>(new {ActivityStatus.Planned,ActivityType.Downtime} );
along the lines of above - but with extra values, have a bunch of ones like above throughout our own code base now

Entity Framework 6, cannot use Ignore method on the property

Currently, I'm using ASP Identity with MVC 5.I want to remove phone number field from the AspNetUsers table, but when I use add-migration command it causes the following error.
You cannot use Ignore method on the property 'PhoneNumber' on type
'Models.DbModel.User' because this type inherits from the type
'Microsoft.AspNet.Identity.EntityFramework.IdentityUser`
I have already read tons of questions on here, but all of them said you have to ignore property in your base class, however, I don't have any access to the base in this case.
How can I solve this problem?
Update: when I used fluent API inside the OnModelCreating method it worked, I don't want to use it this way so I separated the config class for each entity.
Below is my code:
Derived Entity Class
public class User: IdentityUser
{
public ICollection<Comment> Comments { get; set; }
}
Config class
public sealed class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
ToTable("dbo", "Users");
Ignore(x => x.PhoneNumber);
Ignore(x => x.PhoneNumberConfirmed);
}
}
Context Class
public class WebsiteContext : IdentityDbContext
{
public WebsiteContext()
: base("XYZ")
{
}
public DbSet<Comment> Comment { get; set; }
//public DbSet<User> User { get; set; }
public static WebsiteContext Create()
{
return new WebsiteContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new CommentConfig());
modelBuilder.Configurations.Add(new UserConfig());
}
}
Try the [NotMapped] attribute from
System.ComponentModel.DataAnnotations.Schema
This might get you around that limitation , it has been used to ignore Enums in the mapping, this might not be exactly what you want

Entity Framework Table Per Class Inheritance

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.

Entity Framework 4.1 RC: Code First EntityTypeConfiguration inheritance issue

I am trying to use a common EntityTypeConfiguration class to configure the primary key for all of my entities, so that each derived configuration class does not repeat itself. All of my entities implement a common interface IEntity (which says that each entity must have an Id property of type int).
My configuration base class looks like this:
public class EntityConfiguration<TEntity> : EntityTypeConfiguration<TEntity>
where TEntity : class , IEntity {
public EntityConfiguration() {
HasKey( e => e.Id );
Property( e => e.Id ).HasDatabaseGeneratedOption( DatabaseGeneratedOption.Identity );
}
}
Each entity then has it's own specific configuration class extending this one like this:
public class CustomerConfiguration : EntityConfiguration<Customer> {
public CustomerConfiguration() : base() {
// Entity specific configuration here
}
}
It compiles fine, but the problem I am having is that at runtime I get the following Exception being raised when EF 4.1 RC tries to create the model:
System.InvalidOperationException was
unhandled Message=The key component
'Id' is not a declared property on
type 'Customer'. Verify that it has
not been explicitly excluded from the
model and that it is a valid primitive
property. Source=EntityFramework
If I change the CustomerConfiguration class to extend from EntityTypeConfiguration<Customer> and repeat the primary key configuration then it works fine, but I lose the ability to share common configuration (DRY principal is the motivation).
Am I doing something wrong here?
Is there another way to share common configuration between entities?
For reference here are the other classes involved:
public interface IEntity {
int Id { get; set; }
}
public class Customer : IEntity {
public virtual int Id { get; set; }
public virtual string name { get; set; }
}
Thanks!
It looks like these configurations has some problem with interface. It works if you change IEntity to EntityBase:
public class EntityBase
{
public virtual int Id { get; set; }
}
public class Customer : EntityBase
{
public virtual string Name { get; set; }
}
public class EntityConfiguration<TEntity> : EntityTypeConfiguration<TEntity>
where TEntity : EntityBase
{
public EntityConfiguration()
{
HasKey(e => e.Id);
Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class CustomerConfiguration : EntityConfiguration<Customer>
{
public CustomerConfiguration()
: base()
{
...
}
}
I do not think that you need to go through all of this. EF 4.1 Code First uses a lot of convention over configuration and via this, the Id property of an entity is configured as the primary key. So by implementing the IEntity interface on your entities you are setting them up with the Id as the primary key.
Here is a link to the ADO.NET Team Blog that explains how the primary key convention works - Conventions for Code First
You could just create a static method on a class and pass the entity into it. For example:
public class CustomerConfiguration : EntityConfiguration<Customer>
{
public CustomerConfiguration()
: base()
{
...
EntityConfiguration.Configure(this);
}
}
public static class EntityConfiguration
{
public static void Configure<TEntity>(EntityTypeConfiguration<TEntity> entity) where TEntity : EntityBase
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
I have similar issue with EF5.0 when i have generic abstract class with Id property and implementation for abstract members and self defined properties.
look like entity framework code first is looking only for mapped class properties.
i have tried to use reflector - seems i am right, but don't sure about this for 100%.
And, fortunately, have found solution for this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Entity<MyEntity>()
.Map(m =>
{
**m.MapInheritedProperties();**
});
}
so in my case: to map also properties from base class i have to add one line of code m.MapInheritedProperties()...

Categories