This is my main domain Model.
public class Transform : IValue
{
public int Id { get; protected set; }
public string Target { get; set; }
public Transform() { }
}
}
And below is my Mapping Class.
Here I am creating table by using multiple domain classes like DefaultTransform, DirectTransform, InitialTransform and LoadTransform.
public class TransformMapping : IEntityTypeConfiguration<Transform>
{
public void Configure(EntityTypeBuilder<Transform> builder)
{
builder.ToTable("Transform");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).HasColumnName("TransformId").ValueGeneratedOnAdd();
builder.HasDiscriminator<string>("TransformType")
.HasValue<Transform>("Base")
.HasValue<DefaultTransform>("Default")
.HasValue<DirectTransform>("Direct")
.HasValue<InitialTransform >("Initial")
.HasValue<LoadTransform>("Load");
}
}
Below I have created Db context instance of Transform Class
public class TransRepository : DbContext
{
public virtual DbSet<Transform> Transforms { get; protected set; }
}
And want to add and update the data. But I am unable to get access of property of other domain classes like DefaultTransform, DirectTransform, InitialTransform and LoadTransform.
How can I update and add using above dbContext instance Transforms.
I am using DDD to implement a domain and using EF Core and SQL as infrastructure for persistence. I have a model which is implemented by "Composite Design Pattern". I use fake name that does not show actual model but consider there is a School entity that has many Rooms:
public class Person: AggregateRoot<long>
{
public long Id { get; set; }
public string Name { get; set; }
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Document is an abstract class that can be PersonDocument or CategoryDocument:
public abstract class Document: ValueObject
{
public string Title { get; set; }
}
and:
public class PersonDocument : Document
{
public string Code { get; set; }
}
public class CategoryDocument: Document
{
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Three tables will be generated in SQL. Here is my mapping:
public override void EntityTypeConfiguration(EntityTypeBuilder<Person> builder)
{
builder.ToTable("Persons");
builder.HasKey(w => w.Id);
builder.Property(w => w.Id)
.ValueGeneratedNever();
builder.Property(x => x.Name).IsRequired().HasMaxLength(500);
}
public class DocumentMapping : IEntityTypeConfiguration<Document>
{
public void Configure(EntityTypeBuilder<Document> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Documents").HasKey("Id");
builder.UsePropertyAccessMode(PropertyAccessMode.Field);
builder.Property(x => x.Title).IsRequired().HasMaxLength(500);
builder.HasOne<Person>().WithMany(x => x.Documents).HasForeignKey("PersonId");
}
}
public class PersonDocumentMapping : IEntityTypeConfiguration<PersonDocument>
{
public void Configure(EntityTypeBuilder<PersonDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("PersonDocuments");
//builder.HasBaseType<Document>();
builder.Property(x => x.Code).IsRequired().HasMaxLength(500);
}
}
public class CategoryDocumentMapping : IEntityTypeConfiguration<CategoryDocument>
{
public void Configure(EntityTypeBuilder<CategoryDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Categories");
// builder.HasBaseType<Document>();
builder.HasMany<Document>(x => x.Documents).WithOne().HasForeignKey("CategoryId");
}
}
result of mapping is this:
Tables and relations in database is as I expect. But when I try to fetch a person from database, desired person get fetched but its documents don't.
I can't resolve the One-to-One relationship between two classes using Shadow Properties as a Keys. Unfortunately I can't create real key properties into my classes due to I used 3rd party class library.
using Microsoft.EntityFrameworkCore;
using System;
namespace EFCoreTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Create database");
using (var context = new ClassDbContext())
{
if (!context.Database.EnsureDeleted())
{
Console.WriteLine(" => Can't delete database");
}
if (context.Database.EnsureCreated())
{
context.SaveChanges();
Console.WriteLine(" => Done");
}
else
{
Console.WriteLine(" => Can't create database");
}
}
Console.WriteLine("End");
}
}
public class ClassDbContext : DbContext
{
public virtual DbSet<ClassA> ClassA { get; set; }
public virtual DbSet<ClassB> ClassB { get; set; }
public ClassDbContext()
{
}
public ClassDbContext(DbContextOptions<ClassDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=XXX.XXX.XXX.XXX;Database=XXXXX;User ID=XXXXX;Password=XXXXX");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassA>().Property<int>("ClassAId");
modelBuilder.Entity<ClassB>().Property<int>("ClassBId");
modelBuilder.Entity<ClassB>().Property<int>("AnotherClassId");
modelBuilder.Entity<ClassB>().HasOne(p => p.AnotherClass).WithMany().HasForeignKey("AnotherClassId");
}
}
public class ClassA
{
public string PropertyA { get; set; }
}
public class ClassB
{
public string PropertyB { get; set; }
public ClassA AnotherClass { get; set; }
}
}
EF Core returns with the following error, which is very confused me:
System.InvalidOperationException: 'The relationship from 'ClassB.AnotherClass' to 'ClassA' with foreign key properties
{'AnotherClassId' : int} cannot target the primary key {'ClassAId'
: int} because it is not compatible. Configure a principal key
or a set of compatible foreign key properties for this relationship.'
It turns out that the automatic binding of properties to keys, implemented in EF Core through the naming of properties, does not work in this case!
They require you to force a key name before creating a connection!
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassA>().Property<int>("ClassAId");
modelBuilder.Entity<ClassA>().HasKey("ClassAId"); // <=== This is a SOLUTION
modelBuilder.Entity<ClassB>().Property<int>("ClassBId");
modelBuilder.Entity<ClassB>().Property<int>("AnotherClassId");
modelBuilder.Entity<ClassB>().HasOne(p => p.AnotherClass).WithMany().HasForeignKey("AnotherClassId");
}
Summary
I am trying to use Table Per Hierarchy inheritance in conjunction with a one to many relationship in a .Net Core 1.1 project. Both the Parent and child entities use inheritance.
I have a very simple entity model. I have a one base parent entity: Session which has two entities which extend from it: QuotingSession and BackOfficeSession. Both of these two parent entities contain a collection of child entities (a one to many relationship). The child entities are also built using inheritance. There is a base child entity: Policy. This base child entity is extended by two entities: QuotingPolicy and BackOfficePolicy. When I construct either of the Parent entities and attempt to save I receive this exception:
InvalidCastException: Unable to cast object of type 'EFTest.QuotingSession' to type 'EFTest.BackOfficeSession
However, when I tell entity framework to ignore one of the the child collections on either of the parents, the save works for both parent entities with no exception. For example:
var entity = modelBuilder.Entity<BackOfficeSession>();
entity.Ignore(c => c.Policies);
In addition if I do not configure one of the Parents (QuotingSession) at all, and just save the other Parent (BackOfficeSession), everything saves as excepted.
Repo
https://github.com/seantarogers/EFTest
Details
I am using Microsoft.EntityFrameworkCore 1.1.1 and Microsoft.EntityFrameworkCore.SqlServer 1.1.1 in a .Net core 1.1 project.
I have the following simple database schema:
My classes look like this:
1. Session
namespace EFTest
{
public abstract class Session
{
public int Id { get; private set; }
}
}
2. QuotingSession
using System.Collections.Generic;
namespace EFTest
{
public class QuotingSession : Session
{
public string QuotingName { get; private set; }
public List<QuotingPolicy> Policies { get; private set; }
private QuotingSession()
{
}
public QuotingSession(string name, List<QuotingPolicy> quotingPolicies)
{
QuotingName = name;
Policies = quotingPolicies;
}
}
}
3. BackOfficeSession
using System.Collections.Generic;
namespace EFTest
{
public class BackOfficeSession : Session
{
public List<BackOfficePolicy> Policies { get; private set; }
public string BackOfficeName { get; private set; }
private BackOfficeSession()
{
}
public BackOfficeSession(string name, List<BackOfficePolicy> policies)
{
BackOfficeName = name;
Policies = policies;
}
}
}
4. Policy
namespace EFTest
{
public abstract class Policy
{
public int Id { get; set; }
public int SessionId { get; set; }
}
}
5. QuotingPolicy
namespace EFTest
{
public class QuotingPolicy : Policy
{
public string QuotingPolicyName { get; private set; }
private QuotingPolicy()
{
}
public QuotingPolicy(string name)
{
QuotingPolicyName = name;
}
}
}
6. BackOfficePolicy
namespace EFTest
{
public class BackOfficePolicy : Policy
{
public string BackOfficePolicyName { get; private set; }
private BackOfficePolicy()
{
}
public BackOfficePolicy(string name)
{
BackOfficePolicyName = name;
}
}
}
7. EF DB Context and Fluent Configuration
using Microsoft.EntityFrameworkCore;
namespace EFTest
{
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<QuotingSession> QuotingSessions { get; set; }
public DbSet<BackOfficeSession> BackOfficeSessions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ConfigureSession(modelBuilder);
ConfigurePolicy(modelBuilder);
ConfigureQuotingSession(modelBuilder);
ConfigureBackOfficeSession(modelBuilder);
ConfigureBackOfficePolicy(modelBuilder);
ConfigureQuotingPolicy(modelBuilder);
}
public static void ConfigurePolicy(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<Policy>();
entity.ToTable("Policy", "dbo");
entity.HasKey(x => x.Id);
entity.HasDiscriminator<int>("SessionType")
.HasValue<QuotingPolicy>(1)
.HasValue<BackOfficePolicy>(2);
}
public static void ConfigureBackOfficePolicy(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<BackOfficePolicy>();
entity.Property(x => x.BackOfficePolicyName);
}
public static void ConfigureQuotingPolicy(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<QuotingPolicy>();
entity.Property(x => x.QuotingPolicyName);
}
public static void ConfigureSession(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<Session>();
entity.ToTable("Session", "dbo");
entity.HasKey(x => x.Id);
entity.HasDiscriminator<int>("SessionType")
.HasValue<QuotingSession>(1)
.HasValue<BackOfficeSession>(2);
}
public static void ConfigureBackOfficeSession(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<BackOfficeSession>();
entity.Property(x => x.BackOfficeName);
entity.HasMany(c => c.Policies).WithOne().HasForeignKey(c => c.SessionId);
// entity.Ignore(c => c.Policies); uncomment this to see it working
}
public static void ConfigureQuotingSession(ModelBuilder modelBuilder)
{
var entity = modelBuilder.Entity<QuotingSession>();
entity.Property(x => x.QuotingName);
entity.HasMany(c => c.Policies).WithOne().HasForeignKey(c => c.SessionId);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
}
}
8. To test it
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace EFTest
{
class Program
{
static void Main(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>();
const string conn = "Server=.\\SqlServer2014;Database=EFTest;Trusted_Connection=True"
optionsBuilder.UseSqlServer(conn);
using (var dbContext = new TestDbContext(optionsBuilder.Options))
{
var quotingPolicy = new QuotingPolicy("quotingPolicyname");
var quotingSession = new QuotingSession("quotingSessionName", new List<QuotingPolicy> {quotingPolicy});
dbContext.QuotingSessions.Add(quotingSession);
dbContext.SaveChanges(); // BLOWS UP HERE!
}
}
}
}
Thanks for your help
In EF Core 2.0, we have the ability to derive from IEntityTypeConfiguration for cleaner Fluent API mappings (source).
How can I extend this pattern to utilize a base entity? In the example below, how can I have a BaseEntityConfiguration to reduce duplication in LanguageConfiguration and MaintainerConfiguration, modifying properties that are in the BaseEntity only in the BaseEntityConfiguration? What would such a BaseEntityConfiguration look like; and how would it be used, if at all, in OnModelCreating()? See the TODOs in-code near the end of the example.
Example:
public abstract class BaseEntity
{
public long Id { get; set; }
public DateTime CreatedDateUtc { get; set; }
public DateTime? ModifiedDateUtc { get; set; }
}
public class Language : BaseEntity
{
public string Iso6392 { get; set; }
public string LocalName { get; set; }
public string Name { get; set; }
}
public class Maintainer : BaseEntity
{
public string Email { get; set; }
public string Name { get; set; }
}
public class FilterListsDbContext : DbContext
{
public FilterListsDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Language> Languages { get; set; }
public DbSet<Maintainer> Maintainers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//TODO: Possibly add something like BaseEntityConfiguration?
modelBuilder.ApplyConfiguration(new LanguageConfiguration());
modelBuilder.ApplyConfiguration(new MaintainerConfiguration());
}
}
public class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
public void Configure(EntityTypeBuilder<Language> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
public class MaintainerConfiguration : IEntityTypeConfiguration<Maintainer>
{
public void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
//TODO: Move this to something like BaseEntityConfiguration?
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
Something like this could work (untested)?
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
}
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void Configure(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
base.Configure(entityTypeBuilder);
}
}
There is another way to solve the problem, and that is to use Template Method Design Pattern. Like this:
public abstract class BaseEntityTypeConfiguration<TBase> : IEntityTypeConfiguration<TBase>
where TBase : BaseEntity
{
public void Configure(EntityTypeBuilder<TBase> entityTypeBuilder)
{
//Base Configuration
ConfigureOtherProperties(builder);
}
public abstract void ConfigureOtherProperties(EntityTypeBuilder<TEntity> builder);
}
public class MaintainerConfiguration : BaseEntityTypeConfiguration<Maintainer>
{
public override void ConfigureOtherProperties(EntityTypeBuilder<Maintainer> entityTypeBuilder)
{
entityTypeBuilder.Property(b => b.CreatedDateUtc).HasDefaultValueSql("CURRENT_TIMESTAMP");
}
}
With this way you don't need to write any single line in child configuration.
Another approach if you dont want to repeat the column Definitions for all of your Models that inherit from the same base Entity like this:
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity<Order>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
modelBuilder.Entity<Adress>()
.Property(b => b.CreatedDateTime)
.HasDefaultValueSql("CURRENT_TIMESTAMP ");
// …
}
is to find all the Entites that inhert from the base Entity, loop over them and call the generic Method as shown below, in which the redundant Logic is placed:
protected override void OnModelCreating(ModelBuilder modelBuilder){
foreach (Type type in GetEntityTypes(typeof(BaseEntity))){
var method = SetGlobalQueryMethod.MakeGenericMethod(type);
method.Invoke(this, new object[] { modelBuilder });
}
}
static readonly MethodInfo SetGlobalQueryMethod = typeof(/*your*/Context)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(t => t.IsGenericMethod && t.Name == "SetGlobalQuery");
public void SetGlobalQuery<T>(ModelBuilder builder) where T : BaseEntity{
builder.Entity<T>().Property(o => o.CreatedDateTime).HasDefaultValueSql("CURRENT_TIMESTAMP");
// Additional Statements
}
For the "GetEntityTypes" Method you need the Nuget Package „Microsoft.Extensions.DependencyModel“
private static IList<Type> _entityTypeCache;
private static IList<Type> GetEntityTypes(Type type)
{
if (_entityTypeCache != null && _entityTypeCache.First().BaseType == type)
{
return _entityTypeCache.ToList();
}
_entityTypeCache = (from a in GetReferencingAssemblies()
from t in a.DefinedTypes
where t.BaseType == type
select t.AsType()).ToList();
return _entityTypeCache;
}
private static IEnumerable<Assembly> GetReferencingAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
catch (FileNotFoundException)
{ }
}
return assemblies;
}
Its a bit hacky in my opinion, but works fine for me!
The source with more details:
https://www.codingame.com/playgrounds/5514/multi-tenant-asp-net-core-4---applying-tenant-rules-to-all-enitites
I'm late to the party, but this is what I did in the OnModelCreating method to achieve similar results.
Basically, I have (4) properties that inherit from a BaseEntity. Two of those are dates why two are strings.
For the dates, I wanted the default to be SQL's GETUTCDATE and the string to be "SystemGenerated." Using a static helper that allows me to retrieve the property name from BaseEntity in a strongly-typed manner, I grab the (4) property names. Then, I iterate over all of the iterate over all of the ModelBuilder entities after my primary mappings are set-up. This allows modelBuilder.Model.GetEntityTypes to return the entities that the modelBuidler is aware of. Then it's a matter of looking at the ClrType.BaseType to see if the type inherits from my BaseEntity and setting the defaults on the PropertyBuilder.
I tested this directly and through EF Migrations which confirmed that the proper SQL was generated.
var createdAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedAtUtc);
var lastModifiedAtUtc = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedAtUtc);
var createdBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.CreatedBy);
var lastModifiedBy = StaticHelpers.GetPropertyName<BaseEntity>(x => x.LastModifiedBy);
foreach (var t in modelBuilder.Model.GetEntityTypes())
{
if (t.ClrType.BaseType == typeof(BaseEntity))
{
modelBuilder.Entity(t.ClrType).Property(createdAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(lastModifiedAtUtc).HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity(t.ClrType).Property(createdBy).HasDefaultValueSql("SystemGenerated");
modelBuilder.Entity(t.ClrType).Property(lastModifiedBy).HasDefaultValueSql("SystemGenerated");
}
}
Here is the the static helper for getting property names for a given type..
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
if (expression.Body is MemberExpression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
else
{
var op = ((UnaryExpression)expression.Body).Operand;
return ((MemberExpression)op).Member.Name;
}
}