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

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

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);
});
}

How to map in EF Core 6 a value object with derive classes?

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.

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());
}
}
}

EF6 with SQLite - not restoring lists

I am using EF6 to read and write rules for some validation to an SQLite DB. Code First approach. I successfully wrote the rules out to DB. As I tried to read them now, the complex rules which contain child rules seem to have empty lists of child rules, despite database mirroring that correctly. What is wrong there?
The class hierarchy is:
abstract Rule - abstract class with "Validate" method.
abstract ComplexRule: Rule - a rule which can have child rules
SuperRule, OrRule, AndRule : ComplexRule implementations
BasicRule: Rule - direct implmentation which does some pattern matching.
The Table as written out in DB is so:
RuleId,Condition,Value,Mode,Discriminator,ComplexRule_RuleId
1,,,,SuperRule,
2,*USA*,*PATT1*,1,BasicRule,1
3,*CHN*,*PATT2*,1,BasicRule,1
Reading code looks like this:
using (var db = new RuleModel())
{
var q = db.Rules.OfType<SuperRule>).FirstOrDefault();
}
it results in a SuperRule which Children are empty. The db.Rules contains all 3 rules, but there's no association between BasicRule and SuperRule.
The RuleModel (DbContext) looks like this:
public class RuleModel : DbContext
{
public RuleModel() : base("name=RuleModel")
{
}
public virtual DbSet<Rule> Rules { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var sqliteConnectionInitializer = new SqliteCreateDatabaseIfNotExists<RuleModel>(modelBuilder);
Database.SetInitializer(sqliteConnectionInitializer);
}
}
The Rules look like this:
public abstract class Rule
{
[Key]
public int RuleId { get; set; }
public abstract RuleResult Match(Mapping m);
}
public abstract class ComplexRule: Rule
{
public IList<Rule> ChildRules { set; get; }
public ComplexRule()
{
ChildRules = new List<Rule>();
}
}
public class OrRule : ComplexRule
{
public override RuleResult Match(Mapping m)
{
// Some logics
}
public OrRule() : base() { }
}
public class SuperRule: OrRule
{
public override RuleResult Match(Mapping m)
{
//some logics
}
public SuperRule() : base()
{
}
}
public class BasicRule : Rule
{
public string Condition { set; get; }
public string Value { set; get; }
public RuleMode Mode { set; get; }
public BasicRule(string condition, string value, RuleMode mode = RuleMode.MODE_ANYWHERE)
{
Condition = condition;
Value = value;
Mode = mode;
}
public BasicRule() { }
public override RuleResult Match(Mapping m)
{
// logics
}
}

Schema invalid and types cannot be loaded because the assembly contains EdmSchemaAttribute

Getting the following error:
Schema specified is not valid. Errors:
The types in the assembly 'x, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null' cannot be loaded because the assembly contains
the EdmSchemaAttribute, and the closure of types is being loaded by
name. Loading by both name and attribute is not allowed.
What does this error mean exactly?
I'm trying to shoe-horn into my application an EF model from an existing database.
Before this application was based on CodeFirst and using the repository pattern but for the life of me I can't get this working.
Before I had:
public class BaseModelContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
But in a EF model-first scenario (one where tables already exist in the db), I had to remove these as it didn't seem to like having a repository pattern on DbSet properties.
So I stripped these out, and the repository can then use repository on the classes already defined on the .designer.cs context class (the EF model). This has the EdmSchemaAttribute set inside the generated code.
So how do I get my repository pattern to work in the model-first scenario? What does the above error mean exactly?
EDIT
Added new code:
public class BaseModelContext : DbContext
{
// public DbSet<Location> Locations { get; set; }
public BaseModelContext(string nameOrConnection)
: base(nameOrConnection)
{
}
public BaseModelContext()
{
}
}
public class VisitoriDataContext : BaseModelContext
{
public VisitoriDataContext()
: base("visitoriDataConnection")
{
}
}
public interface IVisitoriDataContextProvider
{
VisitoriDataContext DataContext { get; }
}
public class VisitoriDataContextProvider : IVisitoriDataContextProvider
{
public VisitoriDataContext DataContext { get; private set; }
public VisitoriDataContextProvider()
{
DataContext = new VisitoriDataContext();
}
}
public class VisitoriRepository<T> : IRepository<T> where T : class
{
protected readonly IVisitoriDataContextProvider _ctx;
public VisitoriRepository(IVisitoriDataContextProvider ctx)
{
_ctx = ctx;
}
public T Get(int id)
{
return _ctx.DataContext.Set<T>().Find(id);
}
}
public interface ILocationRepo : IRepository<Location>
{
IEnumerable<Location> GetSuggestedLocationsByPrefix(string searchPrefix);
}
public class LocationRepo : VisitoriRepository<Location>, ILocationRepo
{
public LocationRepo(IVisitoriDataContextProvider ctx)
: base(ctx)
{
}
public IEnumerable<Location> GetSuggestedLocationsByPrefix(string searchPrefix)
{
return Where(l => l.name.Contains(searchPrefix)).ToList();
}
}
The error means that you cannot combine code first mapping (data annotations and fluent API) and EDMX mapping (with EntityObjects!) for entity with the same name. These two approaches are disjunctive.
The rest of your question is not clear.
Btw. building mapping from existing database is called database first not model first.
Decorate the assembly containing the GILayerModel type with [assembly: EdmSchema] attribute.
In my case, I had a class that derived from an entity (code-first class) in another assembly, and I was adding an instance of this class to the DBContext:
in DBEntities project:
public class GISLayer
{
[Key]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int GISLayerId { get; set; }
[StringLength(200)]
public string LayerName { get; set; }
public List<GISNode> Nodes { get; set; }
}
in the second assembly:
public class GISLayerModel : DBEntities.GISLayer
{
public new List<GISNodeModel> NodesModel { get; set; }
}
and the cause of error:
[WebMethod]
public void SaveGISLayers(GISLayerModel[] layers)
{
using (DBEntities.DBEntities db = new DBEntities.DBEntities())
{
foreach (var l in layers)
{
if (l.GISLayerId > 0)
{
db.GISLayers.Attach(l); //attaching a derived class
db.Entry(l).State = System.Data.EntityState.Modified;
}
else
db.GISLayers.Add(l); //adding a derived class
SaveGISNodes(l.NodesModel.ToArray(), db);
}
db.SaveChanges();
}
}
So, I used AutoMapper to copy properties of derived class to a new instance of base class:
DBEntities.GISLayer gl = AutoMapper.Mapper.Map<DBEntities.GISLayer>(l);
if (gl.GISLayerId > 0)
{
db.GISLayers.Attach(gl);
db.Entry(gl).State = System.Data.EntityState.Modified;
}
else
db.GISLayers.Add(gl);
That solved the problem.