How to map in EF Core 6 a value object with derive classes? - entity-framework-core

I have an Order and an OrderState class, but I will implement state pattern, so I will have a base class State and derived classes for each state.
The classes would be this:
class Order
{
long Id;
Status State;
}
class Status
{
string abstract State;
public abstract void Method1();
}
class Status1 : Status
{
public Status1()
{
State = "Status1";
public ovderride Method1()
{
//do something
}
}
string override State;
}
class Status2 : Status
{
public Status1()
{
State = "Status2";
}
string override State;
public override void Method1()
{
// do something
}
}
In EF Core, I have a class to configure Order with Fluent API:
paramPedidoCompraConfiguracion
.OwnsOne(miOrder => miOrder.State, stateNavigationBuilder =>
{
sateNavigationBuilder.WithOwner();
stateNavigationBuilder.Property<string>(x => x.State)
.HasColumnName("State")
.HasColumnType("varchar(200)")
.IsRequired()
.IsUnicode(false)
.HasMaxLength(200);
});
}
But I get this error:
The corresponding CLR type for entity type 'Status' cannot be instantiated, and there is no derived entity type in the model that corresponds to a concrete CLR type.
I have read the documentation about this in Microsoft docs: https://learn.microsoft.com/en-us/ef/core/modeling/inheritance, in particular in the shared columns, because I want to share the column to avoid to have one column for each state.
This is the code in the documentation:
public class MyContext : DbContext
{
public DbSet<BlogBase> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasColumnName("Url");
modelBuilder.Entity<RssBlog>()
.Property(b => b.Url)
.HasColumnName("Url");
}
}
public abstract class BlogBase
{
public int BlogId { get; set; }
}
public class Blog : BlogBase
{
public string Url { get; set; }
}
public class RssBlog : BlogBase
{
public string Url { get; set; }
}
It is defining a dbSet for the base blog, but in my case I am using the state as value object, not as identity, so if I am not wrong, I shouldn't to create a dbSet for values objects, only for entities. So if it is correct, I don't know how to configure my value object with derived classes.
How could I do it?
Thanks.

Related

How do you map subclass properties to columns in Table per Hierarchy?

I have a TPH situation where I have an abstract base class and 8 derived classes from it by using a discriminator. Two of them share a list of sub classes.
public abstract class StepBase : FullAuditedEntity<Guid>
{
public int Order { get; set; }
public StepType StepType { get; set; }
}
The thing is I have two types which shares a SubClass
public class DestinationVesselStep : StepBase
{
public virtual List<DestinationVessel> VesselsDestination { get; set; }
}
public class LiquidNitrogenStep : StepBase
{
public virtual List<DestinationVessel> DestinationsBoxes { get; set; }
}
private static void ConfigureVesselsStep(ModelBuilder builder)
{
builder.Entity<DestinationVesselStep>(b =>
{
//Properties
b.HasMany(p => p.VesselsDestination).WithOne().HasForeignKey(x => x.StepId);
});
}
private static void ConfigureLiquidNitrogenStep(ModelBuilder builder)
{
builder.Entity<LiquidNitrogenStep>(b =>
{
//Properties
b.HasMany(p => p.DestinationsBoxes).WithOne().HasForeignKey(x => x.StepId);
});
}
But when I request a LiquidNitrogenStep with two or more destinationBoxes I get the following error:
System.InvalidOperationException : Sequence contains more than one element.
it works fine if I only have one destinationBox
I am expecting to get a LiquidNitrogenStep with all its destination boxes, the error do not happnd with DestinationVesselStep
DestinationVessel.StepId can't refer to both a DestinationVesselStep and LiquidNitrogenStep.
So either add separate foreign keys to DestinationVessel, eg LiquidNitrogenStepId, and DestinationVesselStepId, or make the relationships many-to-many, which uses separate linking tables for each relationship, instead of putting foreign keys on the target Entity.
private static void ConfigureVesselsStep(ModelBuilder builder)
{
builder.Entity<DestinationVesselStep>(b =>
{
//Properties
b.HasMany(p => p.VesselsDestination).WithMany( d => d.DestinationSteps);
});
}
private static void ConfigureLiquidNitrogenStep(ModelBuilder builder)
{
builder.Entity<LiquidNitrogenStep>(b =>
{
//Properties
b.HasMany(p => p.DestinationsBoxes).WithMany(d => d.LiquidNitrogenSteps);
});
}

Separate copy of DbContext class for unit testing?

I have a CatalogDbContext class.
I want to use Bogus library to seed fake data into the database that my unit tests will use.
The example provided in bogus's github repo makes use of the HasData method of the CatalogDbContext class to seed data into the tables.
However, I will not want this HasData method to be executed from the API - meaning, the HasData method should only be run if the DBContext is created from the Unit Tests.
Kindly advise how to achieve this?.
using Bogus;
using Catalog.Api.Database.Entities;
using Microsoft.EntityFrameworkCore;
namespace Catalog.Api.Database
{
public class CatalogDbContext : DbContext
{
public CatalogDbContext(DbContextOptions<CatalogDbContext> options) : base(options)
{
}
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfiguration(new CatalogBrandEntityTypeConfiguration());
builder.ApplyConfiguration(new CatalogTypeEntityTypeConfiguration());
builder.ApplyConfiguration(new CatalogItemEntityTypeConfiguration());
FakeData.Init(10);
builder.Entity<CatalogItem>().HasData(FakeData.CatalogItems);
}
}
internal class FakeData
{
public static List<CatalogItem> CatalogItems = new List<CatalogItem>();
public static void Init(int count)
{
var id = 1;
var catalogItemFaker = new Faker<CatalogItem>()
.RuleFor(ci => ci.Id, _ => id++)
.RuleFor(ci => ci.Name, f => f.Commerce.ProductName());
}
}
}

Property not being ignored in EntityTypeConfiguration?

I have a simple entity class with a list of base classes, which is ignored in the model:
public class MyClass
{
public int Id {get;set;}
public List<BaseChild> BaseChildren {get; set;}
}
Which has this configuration:
public class MyClassConfiguration : IEntityTypeConfiguration<MyClass>
{
public void Configure(EntityTypeBuilder<MyClass> builder)
{
builder.Property(o => o.Id).UseHiLo();
builder.HasKey(o => o.Id);
builder.Ignore(o => o.BaseChildren);
}
}
I have classes that inherit from BaseChild and they all use this configuration:
public abstract class BaseChild
{
public int MyClassId { get; set; }
}
public abstract class BaseChildConfiguration<T> : IEntityTypeConfiguration<T> where T : BaseChild
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.HasKey(o => o.MyClassId);
builder.HasOne<MyClass>()
.WithOne()
.HasForeignKey<T>(o => o.MyClassId);
}
}
public class Instance : Component
{
public long Code { get; set; }
}
public class InstanceConfiguration : BaseChildConfiguration<Instance>
{
}
All the configurations are properly being applied in my OnModelCreating:
protected override void OnModelCreating(ModelBuilder mb)
{
mb.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
Yet for some reason I keep getting the following exception:
A key cannot be configured on 'Instance' because it is a derived type. The key must be configured on the root type 'BaseChild'. If you did not intend for 'BaseChild' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.

Change name of Identity Column for all Entities

I am in the process of creating a domain model and would like to have a "BaseEntity" class with an "Id" property (and some other audit tracking stuff). The Id property is the primary key and each Entity in my Domain Model will inherit from the BaseEntity class. Pretty straightforward stuff.....
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime LastUpdate { get; set; }
public string LastUpdateBy { get; set; }
}
public class Location : BaseEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
Using the example above, I would like to map the "Id" field to a "LocationId" column. I understand that I can use the modelBuilder to do this for each entity explicitly by doing something like this:
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
But I would like to do this for every Entity in my domain model and it would be ugly.
I tried the following bit of reflection but did not have any luck. For whatever reason, the compiler "cannot resolve symbol type":
foreach (var type in GetTypesInNamespace(Assembly.Load("Domain.Model"),"Domain.Model"))
{
modelBuilder.Entity<type>().Property(x=>x.Id).....
}
Is there a way to define a convention to override the default PrimaryKey convention to map my "Id" property to a "ClassNameId" property in the database? I am using Entity Framework 6.
You should take a look at Custom Code First Conventions. You need EF6 for it to work, but it looks like you're already using it.
Just to give you an overview, take a look at the following convention I've used to convert PascalCase names to underscore names. It includes a convention for id properties... It also includes an optional table name prefix.
public class UnderscoreNamingConvention : IConfigurationConvention<PropertyInfo, PrimitivePropertyConfiguration>,
IConfigurationConvention<Type, ModelConfiguration>
{
public UnderscoreNamingConvention()
{
IdFieldName = "Id";
}
public string TableNamePrefix { get; set; }
public string IdFieldName { get; set; }
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
var columnName = propertyInfo.Name;
if (propertyInfo.Name == IdFieldName)
columnName = propertyInfo.ReflectedType.Name + IdFieldName;
configuration().ColumnName = ToUnderscore(columnName);
}
public void Apply(Type type, Func<ModelConfiguration> configuration)
{
var entityTypeConfiguration = configuration().Entity(type);
if (entityTypeConfiguration.IsTableNameConfigured) return;
var tableName = ToUnderscore(type.Name);
if (!string.IsNullOrEmpty(TableNamePrefix))
{
tableName = string.Format("{0}_{1}", TableNamePrefix, tableName);
}
entityTypeConfiguration.ToTable(tableName);
}
public static string ToUnderscore(string value)
{
return Regex.Replace(value, "(\\B[A-Z])", "_$1").ToLowerInvariant();
}
}
You use it like this
modelBuilder.Conventions.Add(new UnderscoreNamingConvention { TableNamePrefix = "app" });
EDIT: In your case, the Apply method should be something like this:
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
if (propertyInfo.Name == "Id")
{
configuration().ColumnName = propertyInfo.ReflectedType.Name + "Id";
}
}
Try this out in your DbContext class;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name.Equals("Id"))
.Configure(c => c.HasColumnName(c.ClrPropertyInfo.ReflectedType.Name + "Id"));
}
int is the CLR Type of my Primary Key fields. I want to refer to all keys in code as Id but DBA's require keys to be Id with Table entity name prefix. Above gives me exactly what I want in my created database.
Entity Framework 6.x is required.
In Entity Framework 6 Code First:
modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
and update-database...
Change in model
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
to:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long role_id { get; set; }
Then remove this:
//modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
A start to the Dynamic approach if NOT using custom conventions
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
You can do this using reflection on the context. Pseudo Code as explanation:
Reflect Context to get a list of POCO names
For each POCO in a dbcontext.
Map Property Id -> string PocoName+Id
Here are the extensions I use for this type of solution.
// DBSet Types is the Generic Types POCO name used for a DBSet
public static List<string> GetModelTypes(this DbContext context) {
var propList = context.GetType().GetProperties();
return GetDbSetTypes(propList);
}
// DBSet Types POCO types as IEnumerable List
public static IEnumerable<Type> GetDbSetPropertyList<T>() where T : DbContext {
return typeof (T).GetProperties().Where(p => p.PropertyType.GetTypeInfo()
.Name.StartsWith("DbSet"))
.Select(propertyInfo => propertyInfo.PropertyType.GetGenericArguments()[0]).ToList();
}
private static List<string> GetDbSetTypes(IEnumerable<PropertyInfo> propList) {
var modelTypeNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.PropertyType.GenericTypeArguments[0].Name)
.ToList();
return modelTypeNames;
}
private static List<string> GetDbSetNames(IEnumerable<PropertyInfo> propList) {
var modelNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.Name)
.ToList();
return modelNames;
}
However, you will still need to employee dynamic lambda to finish.
Continue that topic here: Dynamic lambda example with EF scenario
EDIT:
Add link to another question that address the common BAse Config class approach
Abstract domain model base class when using EntityTypeConfiguration<T>
Piggybacking on #Monty0018 's answer but this just need to be updated a little if, like me, you're using Entity Framework 7 and/or SQLite.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
try
{
_builder = modelBuilder;
var typeName = typeof(T).Name;
_builder
.Entity(typeof(T))
.Property<int>("Id")
.ForSqliteHasColumnName(typeName + "Id");
}
catch (Exception e)
{
throw e;
}
}

EF5 Code first TPH Mapping error using DBSet.Find()

When using Entity Framework 5 Code First, with Table Per Hierarchy.
This combined with a Repository and Unit of Work (tried several implementations).
I'm having the following error:
(34,10) : error 3032: Problem in mapping fragments starting at lines 19, 34:EntityTypes T, T are being mapped to the same rows in table T. Mapping conditions can be used to distinguish the rows that these types are mapped to.
I have resolved this issue using the following guide:
Entity Framework 4.3 - TPH mapping and migration error
This works when using a general look-up of all records, then no errors.
When using the DBSet<T>.Find(id), I receive the above error message.
When using DBSet<T>.Where(t => t.id == id) all works fine.
Please does anyone have the solution for this problem?
public class TDataContext : DbContext
{
// Models
public abstract class BaseTrackable
{
public DateTime DateModified { get; set; }
}
public abstract class ParentClass : BaseTrackable
{
public int ParentId { get; set; }
public string ParentString { get; set; }
}
public class Foo : ParentClass
{
public string FooString { get; set; }
}
public class Bar : ParentClass
{
public string BarString { get; set; }
}
// Configuration
public class ParentConfiguration : EntityTypeConfiguration<ParentClass>
{
public ParentConfiguration()
{
ToTable("Parent");
}
}
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Map(m => m.Requires("FooIndicator").HasValue(true));
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
Map(m => m.Requires("BarIndicator").HasValue(true));
}
}
public DbSet<ParentClass> Parent { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations
.Add(new ParentConfiguration())
.Add(new FooConfiguration())
.Add(new BarConfiguration());
}
}
public class Controller
{
TDataContext _context = new TDataContext();
// Repository function
public T GetById<T>(object id) where T : class
{
var dbset = _context.Set<T>();
return dbset.Find(id);
}
public IQueryable<TDataContext.Foo> GetFiltered(Expression<Func<TDataContext.Foo, bool>> filter)
{
var dbset = _context.Set<TDataContext.Foo>();
return dbset.Where(filter);
}
// Final call
// Which fails..
public TDataContext.Foo Get(int id)
{
return this.GetById<TDataContext.Foo>(id);
}
// This works...
public TDataContext.Foo GetWhere(int id)
{
return this.GetFiltered(f => f.ParentId == id).FirstOrDefault();
}
}
Found something that solves my problem partially...
When adding another indicator to the tables, there is no more error, example:
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Map(m => {
m.Requires("FooIndicator").HasValue(true);
m.Requires("BarIndicator").HasValue<short>(1);
});
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
Map(m => {
m.Requires("BarIndicator").HasValue(true);
m.Requires("FooIndicator").HasValue<short>(0);
});
}
}
Wouldn't be better
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Map(m => m.Requires("Type").HasValue("Foo"));
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
Map(m => m.Requires("Type").HasValue("Bar");
}
}
In this way FooConfiguration doesn't need to know anything about BarConfiguration and visa versa. I had this issue when migrating from EF 4.3 to 5.0 and I think what has changed was the discriminator database columns are not nullable in EF 5.0. I think it makes much more sense for them to be not nullable and in general it might be better to have only one discrimanotor column for each derived type as opposed to one column per type (as it was in EF 4.3)
-Stan