How to avoid N+1 in EF generated queries - c#

I used to generate all the helper tables like UsergroupUsers for many-to-many relations or relational Ids in POCO myself but now I want EF to take care of them. Now I don't think it's such a good idea after all.
Problem
When I try to get all UsergroupDynamicField for particular user it generates N+1 query for every usergroup user is in.
Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.
Code
public class User
{
...
public virtual ICollection<Usergroup> Usergroups { get; set; }
public IEnumerable<UserField> Fields
{
get
{
var fields = this.Usergroups.SelectMany(x => x.UsergroupDynamicFields); // N + 1 for every Usergroup
foreach (var field in fields)
{
yield return new UserField
{
Name = field.Name
};
}
}
}
}
Database

Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.
But the class that ends up implementing ICollection is EntityCollection<T>. This collection has a CreateSourceQuery() function that you can use:
var usergroupsQuery = ((EntityCollection<UserGroup>)this.Usergroups).CreateSourceQuery();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);
Update: as pointed out in the comments, ICollection<T> will only be implemented using EntityCollection<T> when change tracking is possible and enabled (non-sealed classes, and all relevant properties virtual). You can create a query another way:
var usergroupsQuery = db.Entry(this).Collection(u => u.Usergroups).Query();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);
Note that this requires that you have access to db somehow.

I try with something like
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
and it seems to be ok. I use EF5.
Of course... this is not a method in the User class. It requires to be able to invoke Include method on a DbSet object.
I hope this may help.
full solution
public class User {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<UserGroup> Groups { get; set; }
}
public class UserGroup {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<UserGroupDynamicField> DynFields { get; set; }
}
public class UserGroupDynamicField {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual UserGroup Group { get; set; }
}
public class UserGroupDynFEFCFConfiguration : EntityTypeConfiguration<UserGroupDynamicField > {
public UserGroupDynFEFCFConfiguration()
: base() {
HasRequired(x => x.Group);
}
}
public class UserGroupEFCFConfiguration : EntityTypeConfiguration<UserGroup> {
public UserGroupEFCFConfiguration()
: base() {
HasMany(x => x.Users).WithMany(y => y.Groups);
}
}
public class TestEFContext : DbContext {
public IDbSet<User> Users { get; set; }
public IDbSet<UserGroup> Groups { get; set; }
public TestEFContext(String cs)
: base(cs) {
Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new UserGroupDynFEFCFConfiguration());
modelBuilder.Configurations.Add(new UserGroupEFCFConfiguration());
}
}
class Program {
static void Main(String[] args) {
String cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext c = new TestEFContext(cs)) {
UserGroup g1 = new UserGroup {
Name = "G1",
DynFields = new List<UserGroupDynamicField> {
new UserGroupDynamicField { Name = "DF11"},
new UserGroupDynamicField { Name = "DF12"}
}
};
c.Groups.Add(g1);
UserGroup g2 = new UserGroup {
Name = "G2",
DynFields = new List<UserGroupDynamicField> {
new UserGroupDynamicField { Name = "DF21"},
new UserGroupDynamicField { Name = "DF22"}
}
};
c.Groups.Add(g2);
c.Users.Add(new User {
Name = "U1",
Groups = new List<UserGroup> { g1, g2 }
});
c.SaveChanges();
}
using (TestEFContext c = new TestEFContext(cs)) {
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
foreach (var v in res) {
Console.WriteLine(v.Name);
}
}
}
}

Related

InvalidOperationException: The instance of entity cannot be tracked - EF Core

I am attempting to save date in multiple tables with a one-to-many relationship in using EF Core. When I do, I get this error:
InvalidOperationException: The instance of entity type 'OrganizationGroupEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Here is my code:
Request model:
public class Organization
{
public Organization()
{ }
public Organization(OrganizationEntity organizationEntity, List<OrganizationGroupEntity> organizationGroupEntities)
{
Id = organizationEntity.Id;
Name = organizationEntity.Name;
Groups = ToList(organizationGroupEntities);
}
public int Id { get; set; }
public string Name { get; set; }}
public List<OrganizationGroup> Groups { get; set; }
private List<OrganizationGroup> ToList(List<OrganizationGroupEntity> organizationGroupEntities)
{
return organizationGroupEntities.Select(
entity => new OrganizationGroup(entity)
).ToList();
}
}
public class OrganizationGroup
{
public OrganizationGroup()
{ }
public OrganizationGroup (OrganizationGroupEntity entity)
{
Id = entity.Id;
Group = entity.Group;
}
public int Id { get; set; }
public string Group { get; set; }
}
Entity models:
public class OrganizationEntity
{
public OrganizationEntity()
{ }
public OrganizationEntity(Organization model)
{
Id = model.Id;
Name = model.Name;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationGroupEntity
{
public OrganizationGroupEntity()
{ }
public OrganizationGroupEntity(int organizationId, OrganizationGroup model)
{
Id = model.Id;
OrganizationId = organizationId;
Group = model.Group;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int OrganizationId { get; set; }
public string Group { get; set; }
}
dbContext:
public DbSet<OrganizationEntity> Organizations { get; set; }
public DbSet<OrganizationGroupEntity> OrganizationGroups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<OrganizationEntity>()
.ToTable("Organizations", "dbo");
modelBuilder.Entity<OrganizationGroupEntity>()
.ToTable("OrganizationGroups", "dbo");
}
repository:
public async Task<Organization> UpdateOrganization(Organization request)
{
// Get the org entity
var organizationEntity = new OrganizationEntity(request);
// get the org groups entities
var groupEntities = request.Groups
.Select(
group => new OrganizationGroupEntity(request.Id, group)
).ToList();
// Get the group entities to remove
var oldEntities = GetOrganizationGroups(request.Id);
var entitiesToRemove = new List<OrganizationGroupEntity>();
foreach (var oldEntity in oldEntities.Result)
{
if (!groupEntities.Any(e => e.Id == oldEntity.Id))
{
entitiesToRemove.Add(oldEntity);
}
}
using (var transaction = _context.Database.BeginTransaction())
{
_context.Organizations.Update(organizationEntity);
_context.OrganizationGroups.UpdateRange(groupEntities); // <-- Fails here
_context.OrganizationGroups.RemoveRange(entitiesToRemove);
await _context.SaveChangesAsync();
transaction.Commit();
}
return request;
}
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}
It turns out when I was getting the current groupEntities in order to fins out what to remove I was initiating tracking on that table. Adding AsNoTracking() to GetOrganizationGroups solved my issue. Like so:
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.AsNoTracking()
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}

Entity Framework 6 custom many-to-many child with implicit insert and delete

I have parent object (LoanApplication) with a child (LoanApplicationQualificationTypes) that is a custom many-to-many table. The reason I have a custom is that it has two audit columns that need to be populated (ModifiedBy, ModifiedDate).
To get the children that were added or removed from the child collection to be persisted in the database correctly, I had to explicitly handle.
Below is the code (simplified by removing other properties that were germane to the question).
Parent (part of many-to-many):
[Serializable]
[Table("LoanApplication")]
public class LoanApplication : BaseDomainModelWithId, ILoanApplication
{
[Key]
[Column("LoanApplicationId")]
public override int? Id { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual ICollection<LoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
IReadOnlyCollection<ILoanApplicationQualificationTypes> ILoanApplication.LoanApplicationQualificationTypes
{
get
{
var loanApplicationQualificationTypes = new List<ILoanApplicationQualificationTypes>();
if (LoanApplicationQualificationTypes == null) return loanApplicationQualificationTypes;
loanApplicationQualificationTypes.AddRange(LoanApplicationQualificationTypes);
return loanApplicationQualificationTypes.AsReadOnly();
}
set
{
foreach (var item in value)
{
LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)item);
}
}
}
public LoanApplication() : base()
{
LoanApplicationQualificationTypes = new List<LoanApplicationQualificationTypes>();
}
}
public interface ILoanApplication : IDomainModel, ILoanApplicationBase, IKeyIntId
{
IReadOnlyCollection<ILoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
}
Object part of many-to-many:
[Serializable]
[Table("QualificationType")]
public class QualificationType : IQualificationType
{
[Key]
[Column("QualificationTypeId")]
public override int? Id { get; set; }
[Required]
public string TypeName { get; set; }
[Required]
public bool IsActive { get; set; }
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public QualificationType() : { }
}
Custom Many-to-Many:
[Serializable]
[Table("LoanApplicationQualificationTypes")]
public class LoanApplicationQualificationTypes : ILoanApplicationQualificationTypes
{
[Key]
[Column(Order = 1)]
public int? LoanApplicationId { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual LoanApplication LoanApplication { get; set; }
ILoanApplication ILoanApplicationQualificationTypes.LoanApplication
{
get
{
return this.LoanApplication;
}
set
{
this.LoanApplication = (LoanApplication)value;
}
}
[Required]
[Key]
[Column(Order = 2)]
public int QualificationTypeId { get; set; }
[ForeignKey("QualificationTypeId")]
public virtual QualificationType QualificationType { get; set; }
IQualificationType ILoanApplicationQualificationTypes.QualificationType
{
get
{
return this.QualificationType;
}
set
{
this.QualificationType = (QualificationType)value;
}
}
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public LoanApplicationQualificationTypes() { }
}
Update method in LoanApplication Repository:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = UserOrProcessName;
// Add / Remove LoanApplicationQualificationTypes and populate audit columns
if (entity.LoanApplicationQualificationTypes?.Count > 0)
{
var existingItems = db.LoanApplicationQualificationTypes.Where(q => q.LoanApplicationId == entity.Id.Value).ToList();
var newItems = entity.LoanApplicationQualificationTypes.Where(q => existingItems.All(e => e.QualificationTypeId != q.QualificationTypeId));
var deletedItems = existingItems.Where(q => entity.LoanApplicationQualificationTypes.All(e => e.QualificationTypeId != q.QualificationTypeId));
foreach (var newItem in newItems)
{
newItem.ModifiedBy = UserOrProcessName;
newItem.ModifiedDate = DateTime.UtcNow;
db.LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)newItem);
}
foreach (var deletedItem in deletedItems)
{
db.LoanApplicationQualificationTypes.Remove((LoanApplicationQualificationTypes)deletedItem);
}
// Need to clear to avoid duplicate objects
((LoanApplication)entity).LoanApplicationQualificationTypes.Clear();
}
db.Entry(entity).State = EntityState.Modified;
db.SaveChanges();
}
return true;
}
Is there a way implement the Update without the explicitly handling adds/updates?
The way I understand it, the question is how to apply the (potential) modifications to the link table without explicitly detecting added/removed links. Also I assume the other part of the link must exist.
It's possible with the following sequence of operations:
First load the actual entity from the database into context, including the links:
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
This will allow change tracker to determine the correct add/update/delete link operations for you later.
Then apply the primitive master data changes:
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
Finally, replace the links with the ones from the incoming entity. To avoid navigation property references pointing to different objects (and in particular to prevent EF trying to create the new records for the other side objects of the relation), do not use directly the incoming objects, but create stub objects with only FK properties set:
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
And that's it. At this point the change tracker has all the necessary information to produce the correct commands when you call db.SaveChanges().
One thing to mention. If you look at db.ChangeTracker.Entries at this point, you'll probably notice that all the old links are marked as Deleted, all the incoming as Added and there are no Modified entries. Don't worry. EF is smart enough and will convert Deleted + Added pairs with the same PK to single update commands.
The whole method:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
if (dbEntity == null) return false;
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
db.SaveChanges();
return true;
}
}

Entity Framework 7 AddRange() Not Adding Foreign Entities

I've been using EF Code First for awhile but this is my first time with EF7.
I have the following Model classes where Venue has a one-to-many relationship to Show:
public class Show
{
public int Id { get; set; }
public Venue Venue { get; set; }
//...
}
public class Venue
{
public int Id { get; set; }
public string Name {get; set; }
//...
public List<Show> Shows { get; set; }
}
I set up the DBContext like this:
public class NettlesContext : DbContext
{
public DbSet<Show> Shows { get; set; }
public DbSet<Venue> Venues { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
new ShowConfiguration(builder.Entity<Show>());
new ImageConfiguration(builder.Entity<Image>());
}
}
public class ShowConfiguration
{
public ShowConfiguration(EntityTypeBuilder<Show> builder)
{
builder.Property(p => p.Id).IsRequired();
builder.Property(p => p.Title).IsRequired();
}
}
public class VenueConfiguration
{
public VenueConfiguration(EntityTypeBuilder<Venue> builder)
{
builder.Property(p => p.Id).IsRequired();
builder.Property(p => p.Name).IsRequired();
}
}
Then in some start up code I initialize the database like this:
private static void AddShows(NettlesContext db)
{
var shows = new List<Show>()
{
new Show()
{
Title = "Portland Country Dance Community Contra Dance",
Venue = new Venue()
{
Name = "Fulton Community Center",
},
},
new Show()
{
Title = "Portland Roadhouse Contra Dance",
Venue = new Venue()
{
Name = "Milwaukie Community Club",
},
},
};
db.Shows.AddRange(shows);
db.SaveChanges();
}
The Shows table is properly initialized except that the VenueId is null. The Venue table is entirely empty.
What's going on?
There is a second argument to DbSet.Add.
Add(TEntity entity, GraphBehavior behavior = GraphBehavior.IncludeDependents)
Although the default is to IncludeDependents (aka children entities), EF7's behavior of Add() is not identifying Venue as a child of Show. In your OnModelCreating you need to specify the relationship between Venue and Show. See Relationships in the EF7 Docs.
Example:
modelBuilder.Entity<Venue>(entityBuilder =>
{
entityBuilder
.HasMany(v => v.Shows)
.WithOne(s => s.Venue)
.HasForeignKey(s => s.VenueId);
});
Even with this however, you will still need to call .Add on the new instances of Venue because Show is not a dependent (child) of Venue.
private static void AddShows(NettlesContext db)
{
var fulton = new Venue()
{
Name = "Fulton Community Center",
};
var club = new Venue()
{
Name = "Milwaukie Community Club",
};
db.Venues.Add(fulton);
db.Venues.Add(club);
var shows = new List<Show>()
{
new Show()
{
Title = "Portland Country Dance Community Contra Dance",
Venue = fulton,
},
new Show()
{
Title = "Portland Roadhouse Contra Dance",
Venue = club
},
};
context.Shows.AddRange(shows);
}
Worth noting: this behavior of .Add() has been a source of confusion in EF7 RC1 and its behavior may be changing in EF7 RC2. See https://github.com/aspnet/EntityFramework/pull/4132
You need to update your model as this
public class Venue
{
public int Id { get; set; }
public string Name {get; set; }
//...
public List<Show> Shows { get; set; }
}
public class Show
{
public int Id { get; set; }
public int VenueId { get; set; }
[ForeignKey("VenueId")]
public Venue Venue { get; set; }
//...
}
ForegnKey attribute is used to define the foriegn key of the relation between two tables

Mapping a single child

The mapping below works, but I was wondering if it can be done with less configuration. I've tried playing around with ForAllMembers and ForSourceMember but I haven't found anything that works so far.
Classes
public class User
{
[Key]
public int ID { get; set; }
public string LoginName { get; set; }
public int Group { get; set; }
...
}
public class UserForAuthorisation
{
public string LoginName { get; set; }
public int Group { get; set; }
}
public class Session
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public virtual User User { get; set; }
...
}
Configuration
Mapper.CreateMap<Session, UserForAuthorisation>()
.ForMember(u => u.LoginName, m => m.MapFrom(s => s.User.LoginName))
.ForMember(u => u.Group, m => m.MapFrom(s => s.User.Group));
Query
UserForAuthorisation user = this.DbContext.Sessions
.Where(item =>
item.ID == SessionID
)
.Project().To<UserForAuthorisation>()
.Single();
Edit This works for the reverse.
Mapper.CreateMap<UserForAuthorisation, User>();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u));
var source = new UserForAuthorisation()
{
Group = 5,
LoginName = "foo"
};
var destination = Mapper.Map<Session>(source);
Unfortunately, Reverse() isn't the easy solution, mapping doesn't work.
Mapper.CreateMap<UserForAuthorisation, User>().ReverseMap();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u)).ReverseMap();
var source = new Session()
{
User = new User()
{
Group = 5,
LoginName = "foo"
}
};
var destination = Mapper.Map<UserForAuthorisation>(source);
I can see only one option to do less configurations. You can use benefit of flattering by renaming properties of UserForAuthorisation class to:
public class UserForAuthorisation
{
public string UserLoginName { get; set; }
public int UserGroup { get; set; }
}
In this case properties of nested User object will be mapped without any additional configuration:
Mapper.CreateMap<Session, UserForAuthorisation>();

Table per Concrete Type (TPC) Mapping in EF4.4

I'm trying to achieve a TPC design on EF 4.4.
I have a set of classes that are already mapped to existing tables, and adding a new set with the same structure, that are to be mapped to different tables, with disjoint IDs.
So here's pretty much the new design for the old classes (without the new type of hierarchy that I'm going to add).
public abstract class HierarchyLevel
{
public virtual int Id { get; set; }
public string Name { get; set; }
}
public class MainHierarchyLevel : HierarchyLevel { }
public abstract class HierarchyItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual HierarchyLevel Level { get; set; }
}
public class MainHierarchyItem : HierarchyItem { }
public abstract class HierarchyTreeItem
{
public int Id { get; set; }
public virtual HierarchyTreeItem ParentTreeItem { get; set; }
public virtual HierarchyItem Parent { get; set; }
public virtual HierarchyItem Child { get; set; }
}
public class MainHierarchyTreeItem : HierarchyTreeItem { }
No matter what I do with the configuration, EF always makes a table name up, e.g. I get a query like
SELECT
[Extent1].[Id] AS [Id],
'0X0X' AS [C1],
[Extent2].[Name] AS [Name],
[Extent2].[Description] AS [Description],
[Extent2].[Level_Id] AS [Level_Id]
FROM [dbo].[HierarchyItems] AS [Extent1]
INNER JOIN [dbo].[HierarchyItems1] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]
Where HierarchyItems1 is made up by EF.
Here's an example of what i'm doing with the configuration:
public class HierarchyItemsConfiguration : EntityTypeConfiguration<HierarchyItem>
{
public HierarchyItemsConfiguration()
{
Property(hierarchyItem => hierarchyItem.Id).HasColumnName("Id").IsRequired();
Property(hierarchyItem => hierarchyItem.Name).HasColumnName("Name").IsRequired();
Property(hierarchyItem => hierarchyItem.Description).HasColumnName("Description").IsOptional();
}
}
public class MainHierarchyItemsConfiguration : EntityTypeConfiguration<MainHierarchyItem>
{
public MainHierarchyItemsConfiguration()
{
Map(mb =>
{
mb.MapInheritedProperties();
mb.ToTable("HierarchyItems");
});
ToTable("HierarchyItems");
HasKey(hierarchyItem => hierarchyItem.Id);
HasRequired(hierarchyItem => hierarchyItem.Level).WithMany().Map(e => e.MapKey("Level_Id"));
}
}
Any ideas on how to configure this correctly?
Thank you!
Change your config like this:
public class MainHierarchyLevelConfiguration : EntityTypeConfiguration<MainHierarchyLevel>
{
public MainHierarchyLevelConfiguration()
{
Map(mb =>
{
mb.MapInheritedProperties();
mb.ToTable("MainHierarchyLevels");
});
}
}
public class MainHierarchyItemsConfiguration : EntityTypeConfiguration<MainHierarchyItem>
{
public MainHierarchyItemsConfiguration()
{
Map(mb =>
{
mb.MapInheritedProperties();
mb.ToTable("MainHierarchyItems");
});
HasKey(hierarchyItem => hierarchyItem.Id);
HasRequired(hierarchyItem => hierarchyItem.Level).WithMany().Map(e => e.MapKey("Level_Id"));
}
}
public class HierarchyContext : DbContext
{
public DbSet<MainHierarchyLevel> MainHierarchyLevels { get; set; }
public DbSet<MainHierarchyItem> MainHierarchyItems { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new MainHierarchyLevelConfiguration());
modelBuilder.Configurations.Add(new MainHierarchyItemsConfiguration());
}
}
When running the following test program, 2 tables will be created - one for each of the two concrete classes MainHierarchyLevel and MainHierarchyItem.
using (var db = new HierarchyContext())
{
var level = new MainHierarchyLevel { Name = "Level1" };
level = db.MainHierarchyLevels.Add(level);
var mh = new MainHierarchyItem { Name = "m1", Description = "m1 desc", Level = level };
db.MainHierarchyItems.Add(mh);
db.SaveChanges();
var query = from m in db.MainHierarchyItems
orderby m.Name
select m;
Console.WriteLine("All mains in the database:");
foreach (var item in query)
{
Console.WriteLine(item.Name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}

Categories

Resources