EntityFramework how to not map a class but do map it's inherited properties - entity-framework

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

Related

EF Core Fluent API, set IsRequired on all entities to generate a non-null db column

I'm working on a Razor pages web app which works directly with a db context...yes this is not ideal but is what I'm stuck with for the time being.
In the data model, each object inherits from a base entity class containing audit data, e.g.:
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
...etc.
public class Table1 : BaseEntity
{
public string TestItemName { get; set; }
}
In the database, I want CreatedBy to be required (not null), but I don't want to use the [Required] attribute since this will trigger the UI to validate the CreatedBy column. I don't want to expose this column in the UI and instead have service code which updates all of the audit properties based on Add/Insert.
What I'm looking for is a way via Fluent API which will give me the column type in the db that I need, e.g. NVARCHAR(MAX) NOT NULL.
I can accomplish this in the OnModelCreating method in the dbcontext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Table1>()
.Property(o => o.CreatedBy)
.IsRequired();
However this would require me to create a similar entry for every table in the model.
Is there code I can use in OnModelCreating which could accomplish this for all entities? Something like this (this is just pseudo-code, but looking to give an idea):
var entityTypes = modelBuilder.Model.GetEntityTypes().Select(o => o.GetType()).ToList();
entityTypes.ForEach(e =>
{
e.Property("CreatedBy").IsRequired();
});
Implement your entity configurations in discrete classes that implement IEntityTypeConfiguration. Your implementations should inherit from a base implementation that configures BaseEntity and the Configure method should be virtual with overriding implementations calling the base class' method:
public abstract class BaseEntityConfiguration<TEntity>
: IEntityTypeConfiguration<TEntity>
where TEntity : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(be => be.CreatedBy)
.IsRequired();
// etc
}
}
public class SomeEntityConfiguration : BaseEntityConfiguration<SomeEntity>
{
public override void Configure(EntityTypeBuilder<SomeEntity> builder)
{
// call base class method to configure BaseEntity properties
base.Configure(builder);
// configure remaining SomeEntity-specific properties/etc
builder.TestItemName.IsRequired();
}
}
You'll need to inform the model builder to use your configuration classes. For example, if your config classes are in the same assembly as your DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(YourDbContext).Assembly);
}

EntityFramework is naming my mapping table wrong

I have the following Entity class definition:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
Notice that table name is different than the class name. I also have a ClientUsers table which is a many-to-many mapping for clients and users. Problem is, when I try to access the webUser.Clients property I get the following exception:
"Invalid object name 'dbo.ClientWebUsers'."
Looks like Entity Framework is trying to guess the name of the third table, but it apparently was not smart enough to take into account the table attribute that I have there. How can I tell EF that it is ClientUsers and not ClientWebUsers? Also what rule does it follow to know which table name comes first and which one comes second in the new table name? I think it's not alphabetical order.
I'm using EF 5.0. Thanks!
From the looks of things you're using Code First, so I'll answer accordingly. If this is incorrect, please let me know.
I believe the convention being used to determine the name of the many-to-many table is determined by the order in which they occur as DbSet properties in your SomeContext : DbContext class.
As for forcing EntityFramework to name your table whatever you like, you can use the Fluent API in the OnModelCreating method of your SomeContext : DbContext class as follows:
public class DatabaseContext : DbContext
{
public DatabaseContext()
: base("SomeDB")
{
}
public DbSet<WebUser> Users { get; set; }
public DbSet<Client> Clients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WebUser>().HasMany(c => c.Clients)
.WithMany(p => p.WebUsers).Map(
m =>
{
m.MapLeftKey("ClientId");
m.MapRightKey("UserId");
m.ToTable("ClientUsers");
});
}
}
This assumes your classes are something like the following:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
public class Client
{
public int Id { get; set; }
public ICollection<WebUser> WebUsers { get; set; }
// more properties
}
Finally, here's an integration test (NUnit) demonstrating the functionality working. You may need to drop your database before running it as Code First should want to update/migrate/recreate it.
[TestFixture]
public class Test
{
[Test]
public void UseDB()
{
var db = new DatabaseContext();
db.Users.Add(new WebUser { Clients = new List<Client> { new Client() } });
db.SaveChanges();
var webUser = db.Users.First();
var client = webUser.Clients.FirstOrDefault();
Assert.NotNull(client);
}
}
Edit: Link to relevant documentation for the Fluent API
Rowan's answer (adding here for reference):
Here is the information on how to configure a many-to-many table (including specifying the table name). The code you are after is something like:
modelBuilder.Entity<WebUser>()
.HasMany(u => u.Clients)
.WithMany(c => c.WebUsers)
.Map(m => m.ToTable("ClientUsers");
~Rowan

In Entity Framework, is it possible to auto-map a column to an entity property? ID => [tablename]ID

I want to use .Id in my entity classes for the unique id, but our dba wants [tablename]Id in the database tables. Is there a way that Entity Framework can make this mapping automatically without having to create a new map file for every entity?
As long as I understand you correctly, you have something like:
public class Foo
{
public Int32 ID { get; set; }
// ...
}
public class Bar
{
public Int32 ID { get; set; }
// ...
}
And, without too much effort (or creating multiple entityTypeConfiguration<T> models) you'd like something along the lines of the following outcome:
Current Mapping Desired Mapping
[Foo] [Foo]
ID FooID
... ...
[Bar] [Bar]
ID BarID
... ...
For this, a few methods exist (and depend on which version of EF you're using). With that said, some approachable tactics:
ColumnAttribute
You can visit each entity model and decorate the ID property with the ColumnAttribute. This tells EF that, despite what we named the column, we want something else to be the name within the database. e.g.
public class Foo
{
[Column("FooID")]
public Int32 ID { get; set; }
// ...
}
public class Foo
{
[Column("BarID")]
public Int32 ID { get; set; }
// ...
}
The only problem here is that you're now going to every model and adding the attribute.
OnModelCreating & Fluent Mapping
Another method is to do the mapping but keep it all in one place. The OnModelCreating event is great for this kind of thing.
public class MyDbContext : DbContext
{
public Dbset<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnmodelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>()
.Property(x => x.ID).HasColumnName("FooID");
modelBuilder.Entity<Bar>()
.Property(x => x.ID).HasColumnName("BarID");
}
}
Again, the problem here is that you're creating a configuration for each entity.
Custom Conventions
As of EF6, you can use Custom Conventions which make things easier (Including developing your own convention that would make ID=TableNameID). Unfortunately I don't have the time to write an example, but the docs are pretty enlightening.
According to MSDN , both way should work.
Primary key detection is case insensitive. Recognized naming patterns
are, in order of precedence: 'Id' [type name]Id

Multiple level of inheritance in EF Code First Configuration

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.

ADO EF Code First Generic Intermediate Class Inheritance mapping

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.