Okay, I know I have to be doing something wrong here because the performance times I'm getting are so different its shocking. I've been considering using the code first option of entity in an existing project of mine so I've been trying to do some performance test just to see how it compares. I'm using MSpec to run the tests against a remote development database.
Here are my tests:
public class query_a_database_for_a_network_entry_with_linq : ipmanagement_object {
protected static NetINFO.IPM_NetworkMaster result;
Because of = () => {
var db = new NetINFODataContext();
result = db.IPM_NetworkMasters.SingleOrDefault(c => c.NetworkID == 170553);
};
It should_return_an_ipm_networkmaster_object = () => {
result.ShouldBeOfType(typeof(NetINFO.IPM_NetworkMaster));
};
It should_return_a_net_ou_object_with_a_networkid_of_4663 = () => {
result.IPM_OUIDMaps.First().NET_OU.NET_OUID.ShouldEqual(4663);
};
}
public class query_a_database_for_a_network_entry_with_entity_code_first : ipmanagement_object {
protected static NetInfo.Core.Models.CTP.IPM_NetworkMaster result;
Because of = () => {
NetInfo.Core.Models.CTP.NetInfoDb db = new NetInfo.Core.Models.CTP.NetInfoDb();
result = db.IPM_NetworkMasters.SingleOrDefault(c => c.NetworkID == 170553);
};
It should_return_an_ipm_networkmaster_object = () => {
result.ShouldBeOfType(typeof(NetInfo.Core.Models.CTP.IPM_NetworkMaster));
};
It should_return_a_net_ou_object_with_a_networkid_of_4663 = () => {
result.NET_OUs.First().NET_OUID.ShouldEqual(4663);
};
}
As you can see from the datacontext with linq-to-sql I can't access object directly that have a many to many relationship. I have to use the intermediate lookup table. Which is one of the things I like about Entity framework. However when I run these test the linq test never takes longer than 4 seconds to complete (database is remote). Where the entity test takes almost 8 seconds every time. Not for sure why there is such a huge difference?? Here is excerpts of my POCO classes and my dbcontext:
DbContext:
public class NetInfoDb : DbContext {
public NetInfoDb() : base("NetINFOConnectionString") { }
public DbSet<IPM_NetworkMaster> IPM_NetworkMasters { get; set; }
public DbSet<IPM_NetworkType> IPM_NetworkTypes { get; set; }
public DbSet<NET_OU> NET_OUs { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) {
modelBuilder.Entity<IPM_NetworkMaster>()
.HasMany(a => a.NET_OUs)
.WithMany(b => b.IPM_NetworkMasters)
.Map(m => {
m.MapRightKey(a => a.NET_OUID, "NET_OUID");
m.MapLeftKey(b => b.NetworkID, "NetworkID");
m.ToTable("IPM_OUIDMap");
});
}
}
IPM_NetworkMaster:
public class IPM_NetworkMaster {
public int NetworkID { get; set; }
<snip>
public virtual ICollection<NET_OU> NET_OUs { get; set; }
}
NET_OU:
public class NET_OU {
public int NET_OUID { get; set; }
<snip>
public virtual ICollection<IPM_NetworkMaster> IPM_NetworkMasters { get; set; }
}
As everyone has mentioned, you need to profile your queries. Assuming you are using SQL Server, you can just spool up SQL Server Profiler and compare the queries and execution plans.
As with any performance issue, you must measure first. With your scenario, you have to do more. You have to measure twice with each technology and make sure you are comparing apples to apples. If you can rule out the sql being generated you will then have to measure the application code, to possibly rule any bottlenecks there.
I suspect it will be the generated queries though.
Related
It is just an illustratory example, I understand the relations in this example do not make sense perse, but it plots relations in a way I need the solution. So please do not comment about that.
I am searching for a solution in which I can ignore saving a navigational property;
public class ClassRoom {
public Guid Id { get; set; }
public Guid? ClassRoomInformationId { get; set; }
public virtual ClassRoomInformation { get; set; }
public virtual Collection<Student> Students { get; set;
}
public class Student {
public Guid Id { get; set; }
public Guid? ClassRoomId { get; set; }
public Guid? StudentInformationId { get; set; }
public virtual StudentInformation { get; set; }
}
public class StudentEntityConfiguration : IEntityTypeConfiguration<Student> {
public void Configure(EntityTypeBuilder<Student> builder) {
builder.ToTable("Student");
builder.HasKey(s => s.Id);
builder.HasOne(s => s.StudentInformation)
.WithOne()
.HasForeignKey<Student>(s => s.StudentInformationId);
}
}
public class ClassRoomEntityConfiguration : IEntityTypeConfiguration<ClassRoom> {
public void Configure(EntityTypeBuilder<ClassRoom> builder) {
builder.ToTable("ClassRoom");
builder.HasKey(c => c.Id);
builder.HasOne(c => c.ClassRoomInformation)
.WithOne()
.HasForeignKey<ClassRoom>(c => c.ClassRoomInformationId);
builder.HasMany(c => c.Students)
.WithOne()
.HasForeignKey(c => c.ClassRoomInformation);
}
}
To clearify my question (Using EF 2.2); I want to update the student through it's own StudentRepository. And when I save a classroom through the ClassRoomRepository and the student might change in any way, I do not want that change to be persisted (even though it is included to be able to 'view' the data).
I have tried to add the following to the ClassRoomEntityConfiguration:
//BeforeSaveBehavior neither works
builder.Property(c => c.Students).Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;
However this gives the following exception:
... Cannot be used as a property on ... because it is configured as a navigation.'
Another thing I tried is setting the componentmodel readonly attribute on the Students list in the ClassRoom. This seems to be ignored as well.
I call this the goldilocks problem. You have a hierarchy of objects (Customer, Order, OrderDetails) and you only want to save at "just the right level" of the object-graph.
A work around is to load the object......change only thing things that you care about, then save it.
In the below, I am NOT saving the inputItem.
I am using inputItem to set a small subset of the values of the foundEntity.
public async Task<MyThing> UpdateAsync(MyThing inputItem, CancellationToken token)
{
int saveChangesAsyncValue = 0;
MyThing foundEntity = await this.entityDbContext.MyThings.FirstOrDefaultAsync(item => item.MySurrogateKey == inputItem.MySurrogateKey, token);
if (null != foundEntity)
{
/* alter JUST the things i want to update */
foundEntity.MyStringPropertyOne = inputItem.MyStringPropertyOne;
foundEntity.MyStringPropertyTwo = inputItem.MyStringPropertyTwo;
this.entityDbContext.Entry(foundEntity).State = EntityState.Modified;
saveChangesAsyncValue = await this.entityDbContext.SaveChangesAsync(token);
/* an exception here would suggest another process changed the "context" but did not commit the changes (usually by SaveChanges() or SaveChangesAsync() */
if (1 != saveChangesAsyncValue)
{
throw new ArgumentOutOfRangeException(string.Format("The expected count was off. Did something else change the dbcontext and not save it? {0}", saveChangesAsyncValue), (Exception)null);
}
}
else
{
ArgumentOutOfRangeException argEx = new ArgumentOutOfRangeException(string.Format(" SAD FACE {0} ", entity.MyThingKey), (Exception)null);
this.logger.LogError(argEx);
throw argEx;
}
return foundEntity;
}
SIDE NOTE:
2.2 is no longer supported (see link below). Dot Net Core 2.2 End of Lifetime is listed as "December 23, 2019"
You should upgrade to 3.1 or downgrade to 2.1. (downgrading is counter intuitive I know).
See
https://dotnet.microsoft.com/platform/support/policy/dotnet-core
I'm trying to create an entity object that has many to many relationships with other entities. The relationships are indicated as follows.
public class Change {
// Change Form Fields
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ChangeId { get; set; }
public string ChangeTitle { get; set; }
public string ChangeType { get; set; }
public DateTime DateSubmitted { get; set; }
public DateTime TargetDate { get; set; }
//Many to Many Collections
public virtual ICollection<Change_CriticalBankingApp> Change_CriticalBankingApps { get; set; } = new List<Change_CriticalBankingApp>();
public virtual ICollection<Change_ImpactedBusiness> Change_ImpactedBusinesses { get; set; } = new List<Change_ImpactedBusiness>();
public virtual ICollection<Change_ImpactedService> Change_ImpactedServices { get; set; } = new List<Change_ImpactedService>();
public virtual ICollection<Change_TestStage> Change_TestStages { get; set; } = new List<Change_TestStage>();
public virtual ICollection<Change_TypeOfChange> Change_TypeOfChanges { get; set; } = new List<Change_TypeOfChange>();
And the DbContext set up is as follows
public class ChangeContext : DbContext {
public ChangeContext(DbContextOptions<ChangeContext> options) : base(options) {
Database.Migrate();
}
public DbSet<Change> Change { get; set; }
public DbSet<TestStage> TestStage { get; set; }
public DbSet<TypeOfChange> TypeOfChange { get; set; }
public DbSet<CriticalBankingApp> CriticalBankingApp { get; set; }
public DbSet<ImpactedBusiness> ImpactedBusiness { get; set; }
public DbSet<ImpactedService> ImpactedService { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Change_CriticalBankingApp>().HasKey(t => new { t.ChangeId, t.CriticalBankingAppId });
modelBuilder.Entity<Change_ImpactedBusiness>().HasKey(t => new { t.ChangeId, t.ImpactedBusinessId });
modelBuilder.Entity<Change_ImpactedService>().HasKey(t => new { t.ChangeId, t.ImpactedServiceId });
modelBuilder.Entity<Change_TestStage>().HasKey(t => new { t.ChangeId, t.TestStageId });
modelBuilder.Entity<Change_TypeOfChange>().HasKey(t => new { t.ChangeId, t.TypeOfChangeId });
}
}
Where I start running into problems is I'm not generating an Id using Entity Framework, the primary key is an identity in SQL Server 2012 and I get that back once the insert is completed, as opposed to using a GUID (which I've read pretty much everywhere is super frowned upon in the DBA world).
So what ends up happening is I either try and do the insert and it tries to insert the many to many relationships with changeId in the junction table being null (because it isn't generated yet) or when I try what I have below to do an insert and an update in one post method. It errors out because the ChangeId key value is already being tracked. Here is what I'm attempting below.
Controller method
public IActionResult CreateChange([FromBody] ChangeModel change) {
if (change == null) {
return BadRequest();
}
//Remove many to many from Change to insert without them (as this can't be done until primary key is generated.
List<Change_CriticalBankingAppModel> criticalApps = new List<Change_CriticalBankingAppModel>();
criticalApps.AddRange(change.Change_CriticalBankingApps);
List<Change_ImpactedBusinessModel> impactedBusinesses = new List<Change_ImpactedBusinessModel>();
impactedBusinesses.AddRange(change.Change_ImpactedBusinesses);
List<Change_ImpactedServiceModel> impactedServices = new List<Change_ImpactedServiceModel>();
impactedServices.AddRange(change.Change_ImpactedServices);
List<Change_TestStageModel> testStages = new List<Change_TestStageModel>();
testStages.AddRange(change.Change_TestStages);
List<Change_TypeOfChangeModel> changeTypes = new List<Change_TypeOfChangeModel>();
changeTypes.AddRange(change.Change_TypeOfChanges);
change.Change_CriticalBankingApps.Clear();
change.Change_ImpactedBusinesses.Clear();
change.Change_ImpactedServices.Clear();
change.Change_TestStages.Clear();
change.Change_TypeOfChanges.Clear();
//Map Change model to change entity for inserting
var changeEntity = Mapper.Map<Change>(change);
_changeRepository.AddChange(changeEntity);
if (!_changeRepository.Save()) {
throw new Exception("Creating change failed on save.");
}
var changetoReturn = Mapper.Map<ChangeModel>(changeEntity);
//Iterate through Many to many Lists to add generated changeId
foreach (var criticalApp in criticalApps) {
criticalApp.ChangeId = changetoReturn.ChangeId;
}
foreach (var impactedBusiness in impactedBusinesses) {
impactedBusiness.ChangeId = changetoReturn.ChangeId;
}
foreach (var impactedService in impactedServices) {
impactedService.ChangeId = changetoReturn.ChangeId;
}
foreach (var testStage in testStages) {
testStage.ChangeId = changetoReturn.ChangeId;
}
foreach (var changeType in changeTypes) {
changeType.ChangeId = changetoReturn.ChangeId;
}
//Add many to many lists back to change to update
changetoReturn.Change_CriticalBankingApps = criticalApps;
changetoReturn.Change_ImpactedBusinesses = impactedBusinesses;
changetoReturn.Change_ImpactedServices = impactedServices;
changetoReturn.Change_TestStages = testStages;
changetoReturn.Change_TypeOfChanges = changeTypes;
changeEntity = Mapper.Map<Change>(changetoReturn);
_changeRepository.UpdateChange(changeEntity);
if (!_changeRepository.Save()) {
throw new Exception("Updating change with many to many relationships failed on save.");
}
changetoReturn = Mapper.Map<ChangeModel>(changeEntity);
return CreatedAtRoute("GetChange",
new { changeId = changetoReturn.ChangeId },
changetoReturn);
}
Relevant Repository methods
public Change GetChange(int changeId) {
return _context.Change.FirstOrDefault(c => c.ChangeId == changeId);
}
public void AddChange(Change change) {
_context.Change.Add(change);
}
public void UpdateChange(Change change) {
_context.Change.Update(change);
}
public bool ChangeExists(int changeId) {
return _context.Change.Any(c => c.ChangeId == changeId);
}
I encounter this error on the update attempt.
I understand that if I were to have entity framework generate the guid instead of having the database generate the identity int that I would have a much easier time with this but a requirement for this project is to not use Guid's.
Any help on how to successfully process this would be greatly appreciated.
EDIT: In case it helps, here is the http post I'm using with postman.
{
"changeTitle": "Test",
"changeType": "Test",
"dateSubmitted": "02/12/2018",
"targetDate": "02/12/2018",
"change_CriticalBankingApps": [
{
"criticalBankingAppId" : 1,
"description" : "Very critical"
},
{
"criticalBankingAppId" : 2,
"description" : "Moderately critical"
}
],
"change_impactedBusinesses": [
{
"ImpactedBusinessId" : 1
},
{
"ImpactedBusinessId" : 2
}
]
}
The error you are getting has nothing to do with the guid vs db identity.
You are getting it because you are:
Fetching an entity from the database
Creating new entity (not tracked) from within your controller (the mapper does this)
Try to update the entity that is not tracked by entity framework
The update will try to add the entity to the EF repository, but will fail because it already contains an entity with the given ID.
If you plan to make changes to an entity, you need to make sure entity framework tracks the entity prior to calling the update method.
If EF does not track your entity, it does not know which fields have been updated (if any).
Edit:
If you want to get rid of the error, you could detach your original entity. Make sure you do it prior to mapping the changetoReturn back into your changeEntity.
dbContext.Entry(entity).State = EntityState.Detached;
But since your new entity won't be tracked, I don't think anything will be updated (EF does not know what has been changed).
Edit 2:
Also take a look at this to get your changes back into your original entity.
Change this:
changeEntity = Mapper.Map<Change>(changetoReturn);
Into this:
Mapper.Map(changetoReturn, changeEntity);
Using Automapper to update an existing Entity POCO
add new entities via joint table...that way, entities are tracked both in the joint table and their individual respective tables
Ok, whether this is an elegant solution is up for debate, but I was able to detach the entity state from changeEntity after doing the initial insert as follows
_changeRepository.AddChange(changeEntity);
_changecontext.Entry(changeEntity).State = EntityState.Detached;
Then after reattaching all of the many to many lists back to changeToReturn, I created a new Change entity and added that entity state, and updated on that as follows.
var newChangeEntity = Mapper.Map<Change>(changeToReturn);
_changecontext.Entry(newChangeEntity).State = EntityState.Added;
_changeRepository.UpdateChange(newChangeEntity);
Then I returned this mapped back to a view model.
It seems hacky and perhaps through a deeper understanding of entity framework I'll discover a much better way of going about this but this works for now.
The example is as follows: I have a database with all kinds of information about code reviews: reviewers, file infos, revision infos, ... I am using Entity Framework for data access. With regards to that, I have following models (simplified):
public class ReviewItem {
public virtual ICollection<ExpandedRevision> ExpandedRevisions { get; set; }
public virtual int? AuthorId { get; set; }
}
// ...
public class ExpandedRevision {
public int ChangedLines { get; set; }
public string FilePath { get; set; }
}
Now I want to get the total number of changed lines that a particular user reviewed. I do it like that:
public int GetLinesReviewed(int userId) {
using (var ctx = new CrucibleDBContext()) {
return ctx.ReviewItems
.Include(file => file.ExpandedRevisions)
.Where(file => file.AuthorId == userId)
.Sum(file => file
.ExpandedRevisions
.Where(er => !er.FilePath.Contains("third party") &&
!er.FilePath.Contains("ThirdParty")
)
.Sum(er => er.ChangedLines)
});
}
}
So the most interesting part in the above example is this:
.Where(er => !er.Path.Contains("third party") &&
!er.Path.Contains("ThirdParty")
)
Remembering that this is a simplified example, there are actually much more lines there.
I need such piece of code in a lot of places, so what I tried to do is to create a function that will encapsulate and centralize this logic. But due to the fact that ExpandedRevisions is declared as ICollection<ExpandedRevision>, the compiler does not allow me to pass Expression<Func<ExpandedRevision, bool>> there, but only Func<ExpandedRevision, bool>. That results in materialization of the whole thing, which is obviously something I don't want.
Question: how do I achieve code reusability in this case without having to load all the things into memory?
I'm trying to use some classes from another assembly in my own project as entities that I can persist using EF7, rather than writing a series of very similar classes that are more database-friendly.
Simplified versions look like this:
interface IMediaFile
{
string Uri { get; }
string Title { get; set; }
}
class CMediaFile : IMediaFile
{
public CMediaFile() { }
public string Uri { get; set; }
public string Title { get; set; }
}
//The following types are in my project and have full control over.
interface IPlaylistEntry
{
IMediaFile MediaFile { get; }
}
class CPlaylistEntry<T> : IPlaylistEntry where T : IMediaFile
{
public CPlaylistEntry() { }
public T MediaFile { get; set; }
}
There are multiple implementations of IMediaFile, I am showing only one. My PlaylistEntry class takes a generic argument to enable different traits for those various implementations, and I just work with the IPlaylistEntry.
So I've started to model it like so:
var mediaFile = _modelBuilder.Entity<CMediaFile>();
mediaFile.Key(e => e.Uri);
mediaFile.Index(e => e.Uri);
mediaFile.Property(e => e.Title).MaxLength(256).Required();
var mediaFilePlaylistEntry = _modelBuilder.Entity<CPlaylistEntry<CMediaFile>>();
mediaFilePlaylistEntry.Key(e => e.MediaFile);
mediaFilePlaylistEntry.Reference(e => e.MediaFile).InverseReference();
As a simple test, I ignore the CPlaylistEntry<> and just do:
dbContext.Set<CMediaFile>().Add(new CMediaFile() { Uri = "irrelevant", Title = "" });
dbContext.SaveChanges()
This throws:
NotSupportedException: The 'MediaFile' on entity type 'CPlaylistEntry' does not have a value set and no value generator is available for properties of type 'CMediaFile'. Either set a value for the property before adding the entity or configure a value generator for properties of type 'CMediaFile'`
I don't even understand this exception, and I don't see why CPlaylistEntry is appearing when I'm only trying to store a CMediaFile entity. I'm guessing this is related to my model definition - specifically defining the primary key of the CPlaylistEntry as not a simple type, but a complex type - another entity. However I would expect EF to be smart enough to work out that it all boils down to a string Uri, because that complex type has its own primary key declared already, and I have declared the property as a foreign key to that type.
Is it possible to model these classes in EF without radically redesigning them to look closer to what corresponding database tables might be? I've worked with EF6 database-first in the past, so this is my first attempt into a code-first pattern, and I'm really hoping that I can isolate the mess that a database might look like to just my model definition, and keep "clean" classes that I interact with in .NET.
If more explanation of these types and their relationship is required, just ask - I'm attempting to keep this brief.
Doubt this is currently supported (unsure if it eventually will or not).| I've tried to recreate your model with slight changes and when trying to create the database I get:
System.NotSupportedException: The property 'PlaylistEntry`1MediaFile'
cannot be mapped because it is of type 'MediaFile' which is currently
not supported.
Update 1
I think that the fact that you are putting MediaFile as a key is creating problems. I've done a few changes to your model. I hope this will not break anything negative on your end:
public interface IPlaylistEntry<T>
where T : IMediaFile
{
T MediaFile { get; set; }
}
public class PlaylistEntry<T> : IPlaylistEntry<T>
where T : IMediaFile
{
public int Id { get; set; }
public string PlaylistInfo { get; set; } //added for testing purposes
public T MediaFile { get; set; }
}
Mappings:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ForSqlServer().UseIdentity();
builder.Entity<MediaFile>().ForRelational().Table("MediaFiles");
builder.Entity<MediaFile>().Key(e => e.Uri);
builder.Entity<MediaFile>().Index(e => e.Uri);
builder.Entity<MediaFile>().Property(e => e.Title).MaxLength(256).Required();
builder.Entity<PlaylistEntry<MediaFile>>().ForRelational().Table("MediaFileEntries");
builder.Entity<PlaylistEntry<MediaFile>>().Key(e => e.Id);
builder.Entity<PlaylistEntry<MediaFile>>().Reference(e => e.MediaFile).InverseReference();
}
Usage:
var mediaFile = new MediaFile() {Uri = "irrelevant", Title = ""};
context.Set<MediaFile>().Add(mediaFile);
context.SaveChanges();
context.Set<PlaylistEntry<MediaFile>>().Add(new PlaylistEntry<MediaFile>
{
MediaFile = mediaFile,
PlaylistInfo = "test"
});
context.SaveChanges();
This works and saves the correct data to the database.
You can retrieve the data using:
var playlistEntryFromDb = context.Set<PlaylistEntry<MediaFile>>()
.Include(plemf => plemf.MediaFile).ToList();
Update 2
Since you do not want to have an identity as key, you can add a Uri property to your playlistentry class that will be used for the relationship between PlaylistEntry and MediaFile.
public class PlaylistEntry<T> : IPlaylistEntry<T>
where T : IMediaFile
{
public string Uri { get; set; }
public string PlaylistInfo { get; set; }
public T MediaFile { get; set; }
}
Here is what the mapping in this case would look like:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<MediaFile>().ForRelational().Table("MediaFiles");
builder.Entity<MediaFile>().Key(e => e.Uri);
builder.Entity<MediaFile>().Index(e => e.Uri);
builder.Entity<MediaFile>().Property(e => e.Title).MaxLength(256).Required();
builder.Entity<PlaylistEntry<MediaFile>>().ForRelational().Table("MediaFileEntries");
builder.Entity<PlaylistEntry<MediaFile>>().Key(e => e.Uri);
builder.Entity<PlaylistEntry<MediaFile>>().Reference(e => e.MediaFile).InverseReference().ForeignKey<PlaylistEntry<MediaFile>>(e => e.Uri);
}
Usage to insert data stays the same:
var mediaFile = new MediaFile() { Uri = "irrelevant", Title = "" };
context.Set<MediaFile>().Add(mediaFile);
context.SaveChanges();
context.Set<PlaylistEntry<MediaFile>>().Add(new PlaylistEntry<MediaFile>
{
MediaFile = mediaFile,
PlaylistInfo = "test"
});
context.SaveChanges();
This code above will put "irrelevant" in the PlaylistEntry Uri property since it is used as the foreign key.
And to retrieve data:
var mediaFiles = context.Set<PlaylistEntry<MediaFile>>().Include(x => x.MediaFile).ToList();
The join will occur on the Uri field in both tables.
This is my situation, very much simplified.
My classes;
public class ClassBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class ClassMiddle1 : ClassBase
{
}
public class ClassMiddle2 : ClassBase
{
public Guid Token { get; set; }
}
public class ClassA : ClassMiddle1
{
public string UserId { get; set; }
public string Username { get; set; }
}
public class ClassB : ClassMiddle2
{
public string Username { get; set; }
}
And my OnModelCreating;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassBase>()
.Map(m => {
m.Properties(p => new { p.Id});
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle1>()
.Map<ClassMiddle1>(m =>
{
m.Properties(p => new { });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassMiddle2>()
.Map<ClassMiddle2>(m =>
{
m.Properties(p => new { p.Token });
m.ToTable("TableBase");
});
modelBuilder.Entity<ClassA>()
.Map<ClassA>(m =>
{
m.Properties(p => new
{
p.UserId,
p.Username
});
m.ToTable("TableA");
});
modelBuilder.Entity<ClassB>()
.Map<ClassB>(m =>
{
m.Properties(p => new
{
p.Username
});
m.ToTable("TableB");
}).Property(p => p.Username).HasColumnName("User");
}
This works fine but the Discriminator column is by default Discriminator, NVARCHAR(128). I read that it is possible to define this column myself using something like below.
m.Requires("ClassType").HasValue(1);
I turned my possibilities inside out but all times running into a dead end. Anyone having a suggestion how to do it?
I will end with another question. As our hierarchy pretty much are as above but even more derivated classes like C, D, E, F and so on to... say P. We found out that EF are making this incredibly big database query (~150K). Anyone else ran into this scenario?
I am hoping with changing Discriminator to at least minimize this. By that I say we have a very neat class hierarchy but an ugly query set.
Late answer how the actual solution went. Only writing it down here because the documentation around this was not that easy to find.
My solution ended up like below...
modelBuilder.Entity<ClassBase>()
.Map(m => {
...
m.Requires("Discriminator").HasValue(1)
});
Regarding your "incredibly big database query": There are indeed performance and query generation issues with TPT inheritance mapping. There still doesn't seem to be a fix for those problems, only this vague announcement (August 2010):
The good news is that we are working
on these issues so that EF no longer
generates unnecessary SQL. The bad
news is that it will take some time
before the fix is delivered in a
future release.
(Quote from the linked article above.)