I've got the following requirement that works well in the OO space but I can't seem to get it to map back to the DB using ADO EF code first.
I have numrous products each will have different aspects (attributes but not in the sense of code attributes). For instance ring would have aspects such as mineral type = gold etc whilst a diamond would have an aspec of clarity = VVSI1.
As you can see the products very greatly in thier composition and I want a dynamic way of growing my system.
As such I've created a product class:
public class Product
{
public int id { get; set; }
public string Name { get; set; }
private List<ProductAspect> aspects = new List<ProductAspect>();
public List<ProductAspect> Aspects { get { return aspects; } set { aspects = value; } }
}
It has a list of ProductAspect which is the base class for all aspects moving forward:
public class ProductAspect
{
public int id { get; set; }
public string AspectName { get; set; }
}
I then inherit from the ProductAspect using a generic which alows me to be specific (strongly typed) about my Aspect Value:
public abstract class ProductAspect<T> : ProductAspect
{
public T AspectValue { get; set; }
}
I then create some Aspects that will allow me to decorate my product:
public class StringAspect : ProductAspect<string> { };
public class DecimalAspect : ProductAspect<decimal> { };
public class ImageAspect : ProductAspect<byte[]> { };
I then give the DbContext a try and have tried both TPH and TPC inheritance mappings.
Neither seem to work. The DB model that get's generated doesn't create a foriegn key to the StringAspect or DecimalAspect tables from the Aspect Table.
public class IxamDataContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProductAspect> Aspects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
AspectMapping(modelBuilder);
}
private void AspectMapping(DbModelBuilder mb)
{
//TPH
//mb.Entity<ProductAspect>()
// .Map<StringAspect>(m => m.Requires("type").HasValue("sa"))
// .Map<DecimalAspect>(m => m.Requires("type").HasValue("da"));
//TPC
//mb.Entity<StringAspect>().ToTable("StringAspect");
//mb.Entity<DecimalAspect>().ToTable("DecimalAspect");
}
}
Resulting in the following exception for this Seeding code:
Product p = new Product();
p.Name = "Diamond";
p.Aspects.Add(new StringAspect() { AspectName = "History", AspectValue = "Old and long" });
p.Aspects.Add(new DecimalAspect() { AspectName = "Weight", AspectValue= 96.5M });
context.Products.Add(p);
context.SaveChanges();
Excpetion:
EntityType 'StringAspect' does not
exist in the EntitySet
'IxamDataContext.Aspects'. Parameter
name: entity
Any ideas from the EF code first pros out there?
Entity framework doesn't support intermediate non mapped types in inheritance hierarchy. It means that you can't have this inheritance: A (mapped) -> B (not mapped) -> C (mapped). EF also doesn't support mapping generic types. It means that you must remove your generic intermediate class from the hierarchy and move AspectValue to derived types with correct type.
Maybe it's to late, but I would offer you using ComplexType attribute it will allows you to extend your types as you wish.
Related
Is there any way to configure this and get it to work in EF? I'd like to use this scenario if possible, but haven't found any way to do this without getting a the error "The DELETE statement conflicted with the REFERENCE constraint"
I have seen suggestions for handling this using inheritance. Such as in this post... However, is this really not possible to configure using the fluent API?
Multiple collections of same type in entity framework
Here is my test case....
public class ToolSet
{
public int Id { get; set; }
public virtual ICollection<Tool> Tools { get; set; }
}
public class Tool
{
public int Id { get; set; }
public virtual ICollection<Fluid> HeavyFluid { get; set; }
public virtual ICollection<Fluid> LightFluid { get; set; }
}
public class Fluid
{
public int Id { get; set; }
public double Density { get; set; }
}
public class ExampleContext : DbContext
{
public DbSet<ToolSet> ToolSets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ToolSet>().HasMany(x => x.Tools).WithRequired().WillCascadeOnDelete(true);
modelBuilder.Entity<Tool>().HasMany(x => x.HeavyFluid).WithOptional().WillCascadeOnDelete(true);
modelBuilder.Entity<Tool>().HasMany(x => x.LightFluid).WithOptional().WillCascadeOnDelete(false);
}
}
public class SeedDb : DropCreateDatabaseAlways<ExampleContext>
{
public override void InitializeDatabase(ExampleContext context)
{
base.InitializeDatabase(context);
var heavyFluids = new List<Fluid> { new Fluid { Density = 1 }, new Fluid { Density = 2 } };
var lightFluids = new List<Fluid> { new Fluid { Density = .1 }, new Fluid { Density = .2 } };
var toolSet = new ToolSet
{
Tools = new List<Tool>
{
new Tool{HeavyFluid =heavyFluids, LightFluid = lightFluids}
}
};
context.ToolSets.Add(toolSet);
context.SaveChanges();
}
}
[TestClass]
public class UnitTest1
{
[TestInitialize]
public void TestInitialize()
{
Database.SetInitializer(new SeedDb());
}
[TestMethod]
public void TestMethod1()
{
using (var a = new ExampleContext())
{
var toRemove = a.ToolSets.First();
a.ToolSets.Remove(toRemove);
a.SaveChanges();
Assert.IsFalse(a.ToolSets.Any());
}
}
}
am not sure if what you are seeking is possible. you want EF To Deferenciate between two collections of the same type,both optional,one with cascading on delete and the other no.
EntityFramework map Fluid to One Table and only one table,this table has some foreign keys columns to represent the relationship with the the Tool.according to your example you will end up having two foreign keys in the same column to the same type(Tool_Id and Tool_Id1 : one of them will be empty at a time).the Fluent Api is a validation Api ,no more no less.so it can't help accomplishing what you want. I think your best friends are enums and Inheritance as in the link you provided (wich i don't understand why you don't want to use them).
one way i can think now, wich i didn't test and i don't either recommand it even if it works. is having some sort of column that can take one of two values, L or H and work around it in your code to deferentiate between Heavy and Light Tools.
what i said now ,is just an opinion and there might be solutions that i don't know about.let's wait and see.
We use EntityFramework 6.1 with CodeFirst in our web mvc application (StdWebApp). Now we want to make a new custom version of this application (CustomWebApp) .
The CustomWebApp will use most of the code of the standard one, in it's domain model it will extend the Person class.
In CustomDomain we make implement a new DbContext that must connect with the database of the custom app (CustomSqlDb).
In (C#) code there is no problem that there is a Person in Domain and in CustomDomain. However we have not been able to devise a mapping for Person in the Custom DbContext that will:
Create a single "Person" table.
Contains fields form "CustomDomain.Person" AND those from "Domain.Person".
We tried some variants like this:
modelBuilder.Entity<Person>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Person");
}
);
using this document as our inspiration msdn mapping types
But EF complains about the simple name beeing equal.
Obviously we could rename the "Person" in "CustomDomain" to "PersonCustom" but that could lead to a lot of silly names if we have to do this again in the future like "PersonCustomExtraSpecial" etc.
Thoughts anyone?
UPDATE
we tried the solution suggested by mr100, here is the complete code:
namespace Domain
{
public class Person
{
public int Id { get; set; }
public string Stuff { get; set; }
}
}
namespace CustomDomain
{
public class Person : Domain.Person
{
public string ExtraStuff { get; set; }
}
}
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
This still result in the error
The type 'CustomDomain.Person' and the type 'Domain.Person' both have the same simple name of 'Person' and so cannot be used in the same model. All types in a given model must have unique simple names. Use 'NotMappedAttribute' or call Ignore in the Code First fluent API to explicitly exclude a property or type from the model.
So we added the following code:
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Domain.Person>();
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
Still same result.
To achieve this your DbContext class in CustomWebApps should have property People defined like this:
public DbSet<CustomDomain.Person> People {get; set;}
and no property:
public DbSet<Domain.Person> People {get; set;}
even if it comes from StdWebApp DbContext class from which CustomWebApp DbContext class may derive (if that is the case for you). Additionally you may set properly table name:
modelBuilder.Entity<Person>().ToTable("Person");
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;
}
}
I have an abstract base class for a few entities I'm defining. One of those derived entities is actually a non-abstract base class to another entity.
Following this code:
public abstract class BaseReportEntry {
public int ReportEntryId { get; set;}
public int ReportBundleId { get; set; } //FK
public virtual ReportBundle ReportBunde { get; set; }
}
//A few different simple pocos like this one
public PerformanceReportEntry : BaseReportEntry {
public int PerformanceAbsolute { get; set; }
public double PerformanceRelative { get; set; }
}
//And one with a second level of inheritance
public ByPeriodPerformanceReportEntry : PerformanceReportEntry {
public string Period { get; set; }
}
I'm using a base EntityTypeConfiguration:
public class BaseReportEntryMap<TReportEntry> : EntityTypeConfiguration<TReportEntry>
where TReportEntry : BaseReportEntry
{
public BaseReportEntryMap()
{
this.HasKey(e => e.ReportEntryId);
this.HasRequired(e => e.ReportsBundle)
.WithMany()
.HasForeignKey(e => e.ReportsBundleId);
}
}
Presumably this works fine for the one-level of inheritance but throw the following error for that one case where it has a second level:
The foreign key component 'ReportsBundleId' is not a declared property on type 'ByPeriodPerformanceReportEntry'
public class ByPeriodPerformanceReportEntryMap : BaseReportEntryMap<ByPeriodPerformanceReportEntry>
{
public ByPeriodPerformanceReportEntryMap ()
: base()
{
this.Property(e => e.Period).IsRequired();
this.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("ByPeriodPerformanceReportEntries");
});
}
}
Here's ReportBundle class if needed
public class ReportsBundle
{
public int ReportsBundleId { get; set; }
public virtual ICollection<PerformanceReportEntry> PerformanceReportEntries{ get; set; }
public virtual ICollection<ByPeriodPerformanceReportEntry> ByPeriodPerformanceReportEntries{ get; set; }
}
The problem is not so much the second level of inheritance but that PerformanceReportEntry (the base of ByPeriodPerformanceReportEntry) is an entity while BaseReportEntry (the base of PerformanceReportEntry) is not.
Your mapping would work if PerformanceReportEntry would not be an entity - i.e. its mapping is not added to the model builder configuration and you have no DbSet for this type and it would not occur in a navigation collection in ReportsBundle.
Deriving the configuration from BaseReportEntryMap<ByPeriodPerformanceReportEntry> is not possible in this case - and it is not necessary because the mapping for the base properties already happened by the BaseReportEntryMap<PerformanceReportEntry>. Therefore you can use
public class ByPeriodPerformanceReportEntryMap
: EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
But I have doubt that the resulting model is as you would expect it. I don't know what the PerformanceReportEntries and ByPeriodPerformanceReportEntries collections in ReportsBundle are supposed to express. Do you expect that ByPeriodPerformanceReportEntries is a collection filtered by the subtype? Do you expect that PerformanceReportEntries contains only the ReportsEntries that are PerformanceReportEntrys but not ByPeriodPerformanceReportEntrys? Do you expect that PerformanceReportEntries contains all entries including the ByPeriodPerformanceReportEntries?
Anyway, BaseReportEntry.ReportBundle is a navigation property mapped in PerformanceReportEntry (not in ByPeriodPerformanceReportEntry). That means that the inverse navigation property in class ReportsBundle must refer to PerformanceReportEntry which is the PerformanceReportEntries navigation collection. ByPeriodPerformanceReportEntries will introduce a second one-to-many relationship between ReportsBundle and ByPeriodPerformanceReportEntry (without a navigation property in ByPeriodPerformanceReportEntry). The inverse navigation property of ByPeriodPerformanceReportEntries will NOT be BaseReportEntry.ReportBundle.
My feeling is that you should not have the ReportsBundle.ByPeriodPerformanceReportEntries collection, but I am not sure what you want to achieve exactly.
Edit
Refering to your comment that you only have these two Report types your mapping is way too complicated in my opinion. I would do the following:
Remove the BaseReportEntry class and move its properties into PerformanceReportEntry. It makes no sense to have a base class that only one single other class derives from.
Remove the ByPeriodPerformanceReportEntries from ReportsBundle, so that ReportsBundle will be:
public class ReportsBundle
{
public int ReportsBundleId { get; set; }
public virtual ICollection<PerformanceReportEntry>
PerformanceReportEntries { get; set; }
}
Remove the BaseReportEntryMap and move the mapping into PerformanceReportEntryMap. Derive this map from EntityTypeConfiguration<PerformanceReportEntry>.
Correct the mapping. Currently it is wrong because you don't specify the inverse navigation property in WithMany. PerformanceReportEntryMap should look like this:
public class PerformanceReportEntryMap
: EntityTypeConfiguration<PerformanceReportEntry>
{
public PerformanceReportEntryMap()
{
this.HasKey(e => e.ReportEntryId);
this.HasRequired(e => e.ReportsBundle)
.WithMany(b => b.PerformanceReportEntries)
.HasForeignKey(e => e.ReportsBundleId);
}
}
Derive ByPeriodPerformanceReportEntryMap from EntityTypeConfiguration<ByPeriodPerformanceReportEntry> and specify only mappings for properties that are declared in ByPeriodPerformanceReportEntry, not again for the base properties. That already happened in PerformanceReportEntryMap. You don't need and can't specify it again because it will cause exactly the exception you had.
Use Table-Per-Hierarchy (TPH) inheritance instead of Table-Per-Concrete-Type (TPC), especially if you only have a few properties declared in ByPeriodPerformanceReportEntry. TPC is more difficult to use because it has problems with database-generated identities and with polymorphic associations (which you have in your relationship between PerformanceReportEntry and ReportsBundle). The problems are explained in more details here. TPH instead offers the best performance. ByPeriodPerformanceReportEntryMap would then look like this:
public class ByPeriodPerformanceReportEntryMap
: EntityTypeConfiguration<ByPeriodPerformanceReportEntry>
{
public ByPeriodPerformanceReportEntryMap()
{
this.Property(e => e.Period).IsRequired();
}
}
No explicit configuration for TPH is necessary because it is the default inheritance mapping.
I have no clue how i can get an existing object structure based on the following classes (simplified) into a database using Entity Framework (EF is a constraint, i have to use it).
public abstract class WahWahProperty
{
public string Name { get; set; }
public abstract Type PropertyType { get; }
}
// ----------------
public class WahWahProperty<T> : WahWahProperty
{
public T Value { get; set; }
public override Type PropertyType
{
get { return typeof(T); }
}
}
// ----------------
public class WahWahContainer
{
public List<WahWahContainer> Children { get {...}; }
public List<WahWahContainer> Parents { get {...}; } // multiple "Parents" allowed
public List<WahWahProperty> Properties { get {...}; }
//... some more props here ...
}
Any ideas?
The EF doesn't support generic Entity types (which seems to be what you are doing).
Although we have made a change in EF 4.0 (not in Beta1) so you will be able to use a non-generic class derived from a generic class as an Entity.
Anyway hope this helps
Alex
Program Manager Entity Framework Team
Entity Framework Tips