TPH Simple Config / Discriminator issue - entity-framework

I'm attempting a little EF with code first - and I can't figure out where I have gone wrong with the is simply example of my own. I'm just out of ideas, and would like to nail down where I'm going wrong...
First the simply POCO class representing a location - a location can be a RadioStation, or a Merchant. I haven't added additional fields (which will come later), so right now it's just a TPH in as simple config as I can make it.
namespace EFDataClasses.Entities
{
public class RadioStation : Location
{
public RadioStation()
{
}
}
public class Merchant : Location
{
public Merchant()
{
}
}
public class Location
{
public Location()
{
}
public int Loc_ID { get; set; }
public string Loc_Code { get; set; }
public string Loc_Name { get; set; }
public string Loc_Type {get;set;}
}
}
then the config class:
namespace EFDataClasses.Mapping
{
public class LocationMap : EntityTypeConfiguration<Location>
{
public LocationMap()
{
// Primary Key
this.HasKey(t => t.Loc_ID);
// Properties
this.Property(t => t.Loc_ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// Properties
this.Property(t => t.Loc_Code)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.Loc_Name)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.Loc_ID).HasColumnName("Loc_ID");
this.Property(t => t.Loc_Code).HasColumnName("Loc_Code");
this.Property(t => t.Loc_Name).HasColumnName("Loc_Name");
//my discriminator property
this.Property(t => t.Loc_Type).HasColumnName("Loc_Type").HasColumnType("varchar").HasMaxLength(50).IsRequired();
// Table & Column Mappings
this.Map(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type").HasValue("Location");
}
)
.Map<RadioStation>(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type").HasValue("RadioStation");
}
)
.Map<Merchant>(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type").HasValue("Merchant");
}
)
;
}
}
}
here is the context:
namespace EFDataClasses
{
public class MyContext : DbContext
{
static MyContext()
{
Database.SetInitializer<MyContext>(new DropCreateDatabaseAlways<MyContext>());
}
public DbSet<EFDataClasses.Entities.Location> Locations {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new LocationMap());
}
}
}
and finally the program class which attempts to add radio station..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace EFConsole
{
class Program
{
static void Main(string[] args)
{
var db = new EFDataClasses.MyContext();
db.Locations.Add(new EFDataClasses.Entities.RadioStation() { Loc_Name = "Radio Station Name 1", Loc_Code = "RD1" });
int chngs = db.SaveChanges();
System.Diagnostics.Debugger.Break();
}
}
}
The error that I'm getting is a validation error on Loc_Type saying that it's a required field. My impression here is that EF would fill that in when I select the appropriate type - and all my reading supports that.
If I do add the appropriate location type - EF give me another error....
arggghhh!
In the end I would like to make Location abstract but does that mean I can drop the hasvalue("Location")?
I would like to move on here, but I'm curious where I have done wrong. thanks!

The problem is that when you use a column as a discriminator for TPH mapping you cannot also map that column to a property in your class. This is because EF is now controlling the value of that column based on the type of .NET class. So you should remove this line that does the mapping of the Loc_Type property to the Location column:
// Remove this line:
this.Property(t => t.Loc_Type).HasColumnName("Loc_Type").HasColumnType("varchar").HasMaxLength(50).IsRequired();
If you need (or want) to specify an explicit column type, size, etc for the discriminator column then you can do that in the Map call. For example:
Map(m =>
{
m.ToTable("Location");
m.Requires("Loc_Type")
.HasValue("Location")
.HasColumnType("varchar")
.HasMaxLength(50)
.IsRequired();
}
You don't need to have any property in the class representing the location type. If you do want to get a string value equivalent of Loc_Type then you can use something like this in Location:
public virtual string Loc_Type
{
get { return "Location"; }
}
Then override it in the other classes. For example:
public override string Loc_Type
{
get { return "RadioStation"; }
}
The rest of the code looks fine.

I think you're over-complicating things a bit, and that's dangerous around CF.
With code-first, you have to stick to 'tried and tested' patterns that work, otherwise mixing things and constructs will get you into trouble soon enough.
First, why do you need to map and have Loc_Type at all, I mean in the class model? That's superfluous, you'll get that with your 'class' type really, your class type is the discriminator and vice versa.
And that's what the error is saying pretty much I think, either supply or 'drop the column from mapping' or something.
So just drop that from the 'Location' and it works w/o problems. Also you don't really need to specify discriminator most of the time unless you want to have your Db makes sense and used by other sides etc. which is ok though.
If you want you can make it abstract, Location - and that's normally done, and I think you can drop it then from the mappings, only 'real implemented' classes should be mapped as only those could be 'instantiated'.
And lastly, TPH is for various reasons not the most 'fortunate' solution, and the most error prone of all I think, TPT, TPC all work much more 'fluid'. You cannot have your 'child classes' 'properties be non null, etc. etc. there are posts on that.
hope this helps.
EDIT: if you'd like to control the 'hidden' discriminator I think you could try and manually adjust the migration file for that column specifically, to set it's size etc. If that is important (code first should pick the right values there I guess). If you don't change anything significant to the sense of how things work that should work.

Related

Automapper returns empty objects using ProjectTo<>

I'm new to Automapper, and trying to map a Entity Framework database object to a DTO.
My database object, OfficeLookup, contains Code and Description (among other properties I'm not concerned with).
My OfficeDto contains Id and Name properties.
The mapping:
private MapperConfiguration OfficeMapperConfiguration =>
new MapperConfiguration(
cfg =>
{
cfg.CreateMap<OfficeLookup, OfficeDto>()
.ForMember(dest => dest.Id, act => act.MapFrom(src => src.Code))
.ForMember(dest => dest.Name, act => act.MapFrom(src => src.Description));
});
And my code:
public IEnumerable<OfficeDto> GetOfficeDtos() => OfficeLookup.ProjectTo<OfficeDto>(OfficeMapperConfiguration); // returns an Ienumerable of empty OfficeDTOs
The strange thing is that I have another, more complex mapping for a different table which works fine. I don't understand why this map doesn't.
The expression generated by my mapping is
ObjectQuery<OfficeLookup>.MergeAs(MergeOption.AppendOnly).Select(dtoOfficeLookup => new OfficeDto())
which doesn't look right. The other mapping generates an expression which set the various properties correctly (with the exception of the Office value. I assume that once I can get this standalone mapping, I will be able to fix the nested mapping).
The problem should be in corresponding OfficeDto properties, and more specifically, the lack of property setter, in which case AutoMapper simply skips them from projection even though they have been mapped explicitly.
e.g. the issue is reproduced with the following class:
class OfficeDto
{
public int Id { get; }
public string Name { get; }
}
And adding property setters (even private) fixes it:
class OfficeDto
{
public int Id { get; private set; }
public string Name { get; set; }
}

How to create Mapping rules for custom entities in EF6?

I am new to Entity framework.I got an opportunity to work EF6 using code first approach .When through some concepts in google for creating mapping rules for custom columns i found one way OnModelCreating()
Is there any other way other than this so that we can create tables in db from code first approach.
If there is way..which is better in what context?
Yes, there is other way to map your classes and it better option. At least, I thonk so. You can create mapper for your model which inherits generic EntityTypeConfiguration and add this mapper OnModelCreating. This way your code will stay clean and its much more easier to manage mappings if you have a lot of models.
Model class:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
public int Age { get; set; }
}
Mapper class:
internal class PersonMap
: EntityTypeConfiguration<Person>
{
public PersonMap()
{
// Primary key
this.HasKey(m => m.Id);
// Properties
this.Property(m => m.FullName)
.HasMaxLength(50);
// Table & column mappings
this.ToTable("TABLE_NAME", "SCHEMA_NAME")
this.Property(m => m.Id).HasColumnName("ID");
this.Property(m => m.FullName).HasColumnName("FULL_NAME");
this.Property(m => m.Age).HasColumnName("AGE");
// Relationship mappings
// Map your naviagion properties here if you have any.
}
}
Then you add mapper at OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new PersonMap());
base.OnModelCreating(modelBuilder);
}

Table per Hierarchy (TPH) Discriminator Value

We're using the fluent API to map a domain model to an existing database.
The database has a column called Discriminator, but the value stored in the column is not the same as the Type that the row relates to.
Given:
public class Vehicle
{
public int Id { get; set; }
}
public class Car : Vehicle
{
public int Doors { get; set; }
}
public class Bike : Vehicle
{
public bool Stubby { get; set; }
}
Can anyone tell me why this works:
public class VehicleMapping : EntityTypeConfiguration<Vehicle>
{
public VehicleMapping()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Map<Car>(x => x.Requires("Discriminato").HasValue("C"));
Map<Bike>(x => x.Requires("Discriminato").HasValue("B"));
}
}
and this does not (just the requires bit has changed)
public class VehicleMapping : EntityTypeConfiguration<Vehicle>
{
public VehicleMapping()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Map<Car>(x => x.Requires("Discriminator").HasValue("C"));
Map<Bike>(x => x.Requires("Discriminator").HasValue("B"));
}
}
EDIT
Sorry, to clarify what I'm asking:
Why is it not possible to use the default column name, but change the value/type that's stored in that column?
Is it possible?
Is there a workaround if not? (other than changing our schema)
Entity Framework TPH by default uses a column called Discriminator and your second code block is messing with that column. From MSDN:
By default, the discriminator column is added to the table with the
name “Discriminator” and the CLR type name of each type in the
hierarchy is used for the discriminator values. You can modify the
default behavior by using the fluent API.
So if you need to modify it, you need to choose your own name for the column which is what your first code block is doing and calling it Discriminato.

Changing EF6 source code for conversion of short to bool

What is the feasibility of modifying the mapping code to convert a short of value zero or non-zero to false or true, if the boolean destination property is marked with an attribute in the POCO model?
I mean, this is supposed to be one of the advantages of EF being open sourced, and would be for in house use only.
Any tips on where in the code I would look would be appreciated, but this question is really more general and I'd like to hear anything anyone has to say on this.
With regard to the General comments please.
I dont know to make the EF change, but dealing with similar issues is not an uncommon issue in EF.
Not all standard types are supported by EF.
You can have a helper field in your POCO class.
So one field is the actual DB field, but no used outside of POCO.
The help field is NOTMAPPED or ignored in fluent API.
You access the DB via you helper and execute any required casting.
A simple example. Or the reverse if I got helper and DB field types back to front.
[NotMapped]
public virtual bool IsVisible { set; get; } // Helper Field NOT on DB
public int Test { get { return IsVisible ? 1 : 0; } // on DB, but set and get via helper only.
set { IsVisible = (value != 0); } }
Edit: Power Fluent API
Here is a snippet that outlines how you have code that runs for every mapped poco in a consistent way.
public class MyDbContext : DbContext
// model building, set breakpoint so you know when this is triggered
// it is important this ISNT called everytime, only on model cache.
// in my case that is app pool recycle.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
// use the CONFIG add feature to better organize and allow use of inheritance when mapping
// I will use snippets and statics to keep it simple.
modelBuilder.Configurations.Add(XYZMap.Map()); // POCO map
modelBuilder.Configurations.Add(ABCMAP.Map()); // poco map
modelBuilder.Configurations.Add(XXXMap.MAP()); // poco map
// etc for your POCO set
// Note, no need to declare DBset<xyz> XYZs {get;set;} !!!!
public static class XYZMap {
public static BaseEntityIntConfiguration<PocoXYZ> Map() {
//see return object !
var entity = new BaseEntityLongConfiguration<PocoXYZ>();
//entity.Property()... // map away as usual POCO specifc
///entity.HasRequired()...// property and relationships as required
// do nothing for default
return entity;
}
}
}
// all tables with int key use this base config. do it once never again
public class BaseEntityIntConfiguration<T> : BaseEntityConfiguration<T> where T : BaseObjectInt {
public BaseEntityIntConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) {
// Primary Key
this.HasKey(t => t.Id);
// Properties
//Id is an int allocated by DB
this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated
// optimistic lock is also added here, Specific to out poco design
this.Property(t => t.RowVersion)
.IsRequired()
.IsFixedLength()
.HasMaxLength(8)
.IsRowVersion();
// any other common mappings/ rules ??
}
}
public class BaseEntityConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObject {
public BaseEntityConfiguration() {
this.ApplyAttributeRules(); // <<<<< Here is where I apply SYSTEM WIDE rules
}
}
public static void ApplyAttributeRules<T>(this EntityTypeConfiguration<T> entity) where T : BaseObject {
// so this will be called for each mapped type
foreach (var propertyInfo in typeof (T).GetProperties()) {
// I use reflection to look for properties that meet certain criteria.
// eg string. I want as NVARCHAR 4000 not NVCAHR max so i can index it.
if (propertyInfo.UnderLyingType().FullName == "System.String") {
SetStringLength(BosTypeTool.StringLengthIndexable, propertyInfo.Name, entity);
continue;
}
SetStringLength(4000, propertyInfo.Name, entity);
}
}
private static void SetStringLength<TModelPoco>(int length, string propertyName,
EntityTypeConfiguration<TModelPoco> entity) where TModelPoco : BaseObject {
var propLambda = DynamicExpression.ParseLambda<TModelPoco, String>(propertyName);
entity.Property(propLambda).HasMaxLength(length);
// dynamic library from Microsoft.... http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
}
// get underlying type incase it is nullable
public static Type UnderLyingType(this PropertyInfo propertyInfo) {
return Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
}

Table-per-Hierarchy and Many to Many relationship

this may seem very easy to solve, but the complication I'm having is that I have a table per hierarchy to store all of the entities and I'm not able to create the relationships over the same table. Here is what I have in the DB and classes:
I have just one table named BaseObject with an ID, name and type. I will create two classes for those entities stored in there. Master and Component. The type column is the discriminator. I have another table to store the relationships between both: A master can have many components and a component also can have many other components.
This is the code I have for the classes:
public partial class BaseObject
{
public BaseObject()
{
}
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<int> Type { get; set; }
}
public class MasterObject : BaseObject
{
public virtual ICollection<ComponentObject> Components { get; set; }
public MasterObject()
{
this.Components = new List<ComponentObject>();
}
}
public class ComponentObject : BaseObject
{
public virtual ICollection<MasterObject> MasterObjects { get; set; }
public ComponentObject()
{
this.MasterObjects = new List<MasterObject>();
}
}
And these are the mappings:
public class BaseObjectMap : EntityTypeConfiguration<BaseObject>
{
public BaseObjectMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Table & Column Mappings
this.ToTable("BaseObject");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Name).HasColumnName("Name");
//configure the inheritance in here
this.Map<MasterObject>(m => m.Requires("Type").HasValue(1));
this.Map<ComponentObject>(m => m.Requires("Type").HasValue(2));
}
}
public class MasterObjectMap : EntityTypeConfiguration<MasterObject>
{
public MasterObjectMap()
{
this.HasMany<ComponentObject>(t => t.Components)
.WithMany(t => t.MasterObjects)
.Map(c =>
{
c.ToTable("ObjectComponents");
c.MapLeftKey("ComponentObjectID");
c.MapRightKey("BaseObjectID");
});
}
}
public class ComponentObjectMap : EntityTypeConfiguration<ComponentObject>
{
public ComponentObjectMap()
{
this.HasMany<MasterObject>(t => t.MasterObjects)
.WithMany(t => t.Components)
.Map(m =>
{
m.ToTable("ObjectComponents");
m.MapLeftKey("BaseObjectID");
m.MapRightKey("ComponentObjectID");
});
}
}
The thing is that when quering the DB, I can get a Master by accessing the DBSet Masters, but the Component collection always gives a non-sense exception saying that "Problem in mapping fragments starting at line 6:Condition member 'BaseObject.Type' with a condition other than 'IsNull=False' is mapped. Either remove the condition on BaseObject.Type or remove it from the mapping."
I don't understand what's happening. Of course if the classes where pointing each to a table, this would be very easy, but I suspect that is the root of my problem.
Also, I'm just starting with EF. I wanted to create the classes based on my existing DB that I wouldn't like to modify at all. Unless it's really needed. Please guide me if what I'm trying to do is right or wrong, or what should I do first to fully implement EF on this project that is currently using NHibernate.
Any help here? Thanks
Finally I found the issue thanks to this answer: EF4.1 Exception creating Database with table-per-hierarchy inheritance
The problem is in the mapping of the Base class. When using a base class and a discriminator to get the children classes, the discriminator MUST NOT BE a property in neither instance. Just removed the discriminator as property and it worked fine. In my example, column 'Type' is the discriminator.
Hope this helps to someone else.