Shadow Properties to access related entity with Table-Per-Hierarchy inheritance model - c#

I'm building a menu system for a website in ASP.Net Core. Let's assume I have a couple of database tables, one for Pages and one for Articles, although it only really matters that they are different entities. Each of them have a Name and Permalink property.
In my menu, which I want to also store in the database, I want to refer to the Name and Permalink of each entity. I have devised a simple menu class/model structure as follows:
Abstract MenuItem
public abstract class MenuItem
{
[Key]
public int MenuItemId { get; set; }
public int MenuPosition { get; set; }
public abstract string Name { get; }
public abstract string Permalink { get; }
}
Concrete ArticleMenuItem
public class ArticleMenuItem : MenuItem
{
public ArticleMenuItem() {}
public ArticleMenuItem(Article article)
{
Article = article;
}
public string Name => Article.Name;
public string Permalink => Article.Permalink;
[Required]
public int ArticleId { get; set; }
[ForeignKey("ArticleId")]
public Article Article { get; set; }
}
Concrete PageMenuItem
public class PageMenuItem : MenuItem
{
public PageMenuItem() {}
public PageMenuItem(Page page)
{
Page = page;
}
public string Name => Page.Name;
public string Permalink => Page.Permalink;
[Required]
public int PageId { get; set; }
[ForeignKey("PageId")]
public Page Page{ get; set; }
}
I then override onModelCreating(ModelBuilder modelBuilder) for the relevant DbContext as I don't want to make the individual DbSet<T>'s available:
modelBuilder.Entity<PageMenuItem>();
modelBuilder.Entity<ArticleMenuItem>();
As well as add the relevant DbSet<T> for the menu:
public virtual DbSet<MenuItem> MenuItems { get; set; }
Add a couple of sample records to the database when the app loads (assume I've got some articles and pages initialised too):
List<MenuItem> items = new List<MenuItem>()
{
new PageMenuItem(pages[0]) { MenuPosition = 1 },
new ArticleMenuItem(articles[0]) { MenuPosition = 2 }
};
items.ForEach(item => context.MenuItems.Add(item));
A simple repository method to get the menu items from the database:
public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems;
With all this in place I was hoping that I could get the Name and Permalink for each item as follows (in a view, for instance):
#foreach (MenuItem item in Model)
{
#item.Name
}
Sadly, this results in a null object exception, and then I remembered EF Core doesn't support lazy loading. So I want to eagerly load the shadow properties, specifically the related entities, when I get the menu items in the repository.
There are two approaches to accessing shadow properties. The first approach I took updating my repository method looked like this:
public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems
.Include(item => context.Entry(item).Property("Page").CurrentValue)
.Include(item => context.Entry(item).Property("Article").CurrentValue)
This results in:
InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpression1' to type 'System.Linq.Expressions.MemberExpression'.
Casting to (Page) and (Aticle) respectively results in:
InvalidOperationException: The property expression 'item => Convert(value(InfoSecipediaWeb.Infrastructure.EntityFramework.ApplicationDbContext).Entry(item).Property("Page").CurrentValue)' is not valid. The expression should represent a property access: 't => t.MyProperty'.
The second method for accessing shadow properties only seems to enable accessing a single property value:
public static TProperty Property<TProperty>([NotNullAttribute] object entity, [NotNullAttribute][NotParameterized] string propertyName);
However, giving it a try:
public IEnumerable<MenuItem> GetAllMenuItems() => _context.MenuItems
.Include(item => EF.Property<Page>(item, "Page"))
.Include(item => EF.Property<Article>(item, "Article"));
Results in:
InvalidOperationException: The property expression 'item => Property(item, "Page")' is not valid. The expression should represent a property access: 't => t.MyProperty'.
I'd like to know whether it is possible to use shadow properties for navigation with an inheritance model? If so, how do I include the related entities so that it is accessible in my concrete MenuItem classes? e.g. for public string Name => Page.Name.

Unfortunately currently there is no syntax for eager loading derived class properties (note that they are different from shadow properties). This along with the lack of lazy loading leaves the explicit loading to be the only option. See for instance ef-core load collection property of nested tph inherited member how you can use it for a single item, for collection of items I'm afraid you have to materialize the result into a list, then using explicit loading of the concrete types and rely on EF navigation property fix up.
For your example it could be something like this:
public IEnumerable<MenuItem> GetAllMenuItems()
{
var menuItems = _context.MenuItems.ToList();
_context.MenuItems.OfType<ArticleMenuItem>().Include(e => e.Article).Load();
_context.MenuItems.OfType<PageMenuItem>().Include(e => e.Page).Load();
return menuItems;
}
Another workaround (did I say only one) is to use manual union query, which basically kills the TPH idea:
public IEnumerable<MenuItem> GetAllMenuItems() =>
_context.MenuItems.OfType<ArticleMenuItem>().Include(e => e.Article)
.AsEnumerable() // to avoid runtime exception (EF Core bug)
.Concat<MenuItem>(
_context.MenuItems.OfType<PageMenuItem>().Include(e => e.Page));

Related

Programmatically obtain metadata about indexes and constraints from Entity Framework 6's object context

I'm working with unique indexes in a code-first environment. They are defined like this:
modelBuilder.Entity<Foo>().HasIndex(foo => foo.Bar).IsUnique()
Using Entity Framework's metadata model, I would like to programmatically get a list of indexes (or constraints) given a specified type (Foo in this case).
Doing similar things with EF have proven to be relatively easy. For example, you can get a list of property names that are the primary key of a specified set, like so:
Entity Framework code first. Find primary key. Entity Framework also convieniently exposes navigation properties through similar methods: EF5 How to get list of navigation properties for a domain object.
Is there something similar available for (unique) indexes?
EDIT: Example DbContext:
public class Foo
{
public int Id { get; set; }
[Index]
public int SomeNumber { get; set; }
}
public class FooContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public FooContext() : base("DefaultConnection") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Using this DbContext, #Kahbazi's method of obtaining the PropertyIndexes doesn't work, it will be an empty collection.
When I use the fluent API instead of an explicit DbSet, with the following code:
modelBuilder.Entity<Foo>().HasIndex(foo => foo.SomeNumber);
... then "Configuration" (see #Kahbazi's answer) will disappear from the MetadataProperties, resulting in System.InvalidOperationException: 'Sequence contains no matching element'
You can use this code to get the indexes
Context context = new Context();
IObjectContextAdapter objectContextAdapter = (IObjectContextAdapter)context;
object configuration = objectContextAdapter.ObjectContext.CreateObjectSet<Foo>()
.EntitySet
.MetadataProperties
.Where(x => x.Name == "Configuration")
.First()
.Value;
var propertyIndexes = configuration.GetType().GetProperty("PropertyIndexes", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(configuration);
var list = (System.Collections.IEnumerable)propertyIndexes;
foreach (var item in list)
{
Console.WriteLine(item);
}
But since most of the classes are internal, you have to use Reflection

C# EntityFramework 6.0 - How to use Where Statement in EntityTypeConfiguration?

I have 2 classes like this:
Parent.cs
public class Parent
{
public int Id {get;set;}
public virtual ICollection<Child> Children { get; set; }
}
Child.cs
public class Child
{
public int Id {get;set;}
public ItemStatusType ItemStatusTyp { get; set; }
public int ParentId {get;set;}
[ForeignKey("ParentId")]
public virtual Parent Parent { get; set; }
}
ItemStatusType.cs
public enum ItemStatusType
{
Active = 1,
Deactive = 2,
Deleted = 3
}
What I want is to somehow retrieve always the active ones and not the deleted ones. Since I am not deleting the record physically, I'm merely updating the ItemStatusType to Deleted status.
So, when I say ParentObj.Children I only wish to retrieve the active ones without further using Where condition.
Here is so far what I've done but giving an exception on runtime that I stated afterwards:
public class ParentConfiguration : EntityTypeConfiguration<Parent>
{
public ParentConfiguration()
{
HasMany(c => c.Children.Where(p => p.ItemStatusTyp != ItemStatusType.Deleted).ToList())
.WithRequired(c => c.Parent)
.HasForeignKey(c => c.ParentId)
;
}
}
Runtime Exception:
The expression 'c => c.Children.Where(p => (Convert(p.ItemStatusTyp)
!= 3)).ToList()' is not a valid property expression. The expression
should represent a property: C#: 't => t.MyProperty' VB.Net:
'Function(t) t.MyProperty'.
I had to use ToList after the expression, otherwise it does not compile.
What is the proper what to do what I want?
Thanks in advance,
You cannot use Where or any other logic in fluent property mapping - that's just configuration.
Basically you cannot solve what you need in declarative way.
There are some workarounds which you can use for first-level entities, like implement your own extension(s) MySet<T> which will return .Set<T>.Where(x => x.ItemStatusType != ItemStatusType.Deleted) and use it everywhere, but that won't solve filtering issue for child collections.
You can go hard way and prepare a separate set of entities to use for selecting data, which basically should be based on database views or stored procedures; you will have to create separate view for every entity, so you will be able to combine selecting in any relations based on these views-entities.
For inserting though you will need to have entities mapped over "real" tables. No sure if it worth it but it might in some cases.

EF Update Many-to-Many in Detached Scenario

I was trying to create a generic method to update an Entity and all it's collection properties from a detached object. For example:
public class Parent{
public int Id { get; set; }
public string ParentProperty { get; set; }
public List<Child> Children1 { get; set; }
public List<Child> Children2 { get; set; }
}
public class Child{
public int Id { get; set; }
public string ChildProperty { get; set; }
}
So, my first intention was to use something like this:
Repository<Parent>.Update(parentObj);
It would be perfect have a magic inside this method that update Parent properties and compare the list of Children of the parentObj to the current values in database and add/update/remove them accordingly, but it's too complex to my knowledge about EF/Reflection/Generic... and so I tried a second more easier way like this:
Repository<Parent>.Update(parentObj, parent => parent.Children1
parent => parent.Children2);
This method would be a little harder to use, but yet acceptable. But how I think the second parameter had to be params Expression<Func<TEntity, ICollection<TRelatedEntity>>>[] relatedEntities I had problems to specify multiple TRelatedEntity. So my try was to 3rd step with no success yet...
Now I tried to call a method to update Parent and a sequence of methods to update Childreen, like this:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And the code:
public virtual void Update(TEntity entityToUpdate)
{
context.Entry(entityToUpdate).State = EntityState.Modified;
}
public virtual void UpdateChild<TRelatedEntity>(TEntity entityToUpdate, Func<TEntity, object> keySelector, Expression<Func<TEntity, ICollection<TRelatedEntity>>> relatedEntitySelector) where TRelatedEntity: class
{
var entityInDb = dbSet.Find(keySelector.Invoke(entityToUpdate));
var current = relatedEntitySelector.Compile().Invoke(entityToUpdate);
var original = relatedEntitySelector.Compile().Invoke(entityInDb);
foreach (var created in current.Except(original))
{
context.Set<TRelatedEntity>().Add(created);
}
foreach (var removed in original.Except(current))
{
context.Set<TRelatedEntity>().Remove(removed);
}
foreach (var updated in current.Intersect(original))
{
context.Entry(updated).State = EntityState.Modified;
}
context.Entry(entityInDb).State = EntityState.Detached;
}
First problem was to get original values, because when I call dbSet.Find the entity is already in context (context.Entry(entityToUpdate).State = EntityState.Modified;).
So I tried to change order calling first Child:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And now I have the error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
In summary, it would be very nice the first way, but I would be satisfied with the second/third too.
Thanks very much
Edit 1
Please, I need a native solution or using Automapper (which we already use in the project), because my customer don't like external dependencies and if we need to adapt something to the project, like working with Attached objects to update their related entities, so GraphDiff mencioned in the comments doesn't fit our needs (and VS 2015 RC crashed when I tried to install the package for tests)
Have you considered getting the object from the DB and using AutoMapper to modify all the property values?
I mean:
var obj = GetObjectFromDB(...);
AutoMapObj(obj, modifiedObj);
SaveInDb();

Entity Framework does not eager load when using Include()

I have a simple model and a simple query. I'm trying to get EF to eager load my navigation properties:
// Document object has a navigation property "DocType" of type DocType
// DocType object has a navigation property "Documents" of type ICollection<Documents> that is NOT virutal
context.Set<Document>().Where(d=>d.Id == id).Include(d=>d.DocType).SingleOrDefault();
The problem is that this doesn't actually eager load DocType. The stranger thing is that excluding the Include() call does load the DocType property, but as a second query.
I've looked all around and applied every fix I found:
Added a call to Include()
Removed virtual from both navigation properties
Any idea what's going on here? Is it possible to coerce EF to merge this into a single query that's eager loaded?
EDIT: This is my data model:
namespace Data.Models {
class Document {
public int Id { get; set;}
public int TypeId { get; set; }
public DocType DocType { get; set; }
}
class DocType {
public int Id { get; set; }
public string FullName { get; set; }
public ICollection<Document> Documents { get; set; }
}
}
namespace Data.Mappings {
internal class DocumentTypeConfiguration : EntityTypeConfiguration<Document> {
public DocumentTypeConfiguration() {
ToTable("ProsDocs");
HasKey(m => m.Id);
Property(m => m.Id)
.HasColumnName("ProsDocId")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(m => m.TypeId)
.HasColumnName("ProsDocTypeId")
.HasMaxLength(3);
HasRequired(d => d.DocType)
.WithMany(dt=>dt.Documents)
.WithForeignKey(d=>d.TypeId);
}
}
internal class DocTypeTypeConfiguration : EntityTypeConfiguration<DocType> {
public DocTypeTypeConfiguration() {
ToTable("DocType");
HasKey(m => m.Id);
Property(m => m.Id)
.HasColumnName("DocTypeId")
.HasMaxLength(4);
Property(m => m.FullName)
.HasColumnName("DocTypeDesc")
.HasMaxLength(255);
}
}
}
The oddest thing is that when I call:
context.Set<Document>().Find(id);
The DocType properties are populated, but EF does this by executing two separate queries. Is it possible to design this in such a way the EF understands that this can be accomplished with one query?
EDIT 2: This question seems to address the same problem, but only states the calling Include() fixes it, which is not working in my case.
To include navigation properties I use this syntax (with quotes in the Include):
context.Documents.Where(d=>d.Id == id).Include("DocType").SingleOrDefault();
In general case, using string instead of expression (as mention #vanraidex) is not a good practice. In general case. However, when using third party providers (e.g. Oracle Provider) it can be the only way to get correct sql (with joins).
So, if you using special Data Provider and .Include() method doesn't work, try to use string instead of expression.
context.Documents.Where(d=>d.Id == id).Include("DocType").SingleOrDefault();

Base Types with EntityFramework CodeOnly

I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework. I am having a lot of trouble with 'base types' in the Code Only model of the Entity Framework.
When I try to run this code using a DbContext with a DbSet<Template>, I get the following error.
A The navigation property 'Flags' is mapped to two different join tables 'page.flags' and 'template.flags'. Only one mapping of the navigation property may exist
What this says to me is that I cannot map inherited properties. This is quite breaking to a lot of object oriented code design. Is there a known remedy? I realize I can make Layout non-abstract, and have a backing for it, but it's very obvious this is not the intention of the domain model. The abstract class is a foundational base, not the stored model.
I would like to add, if I put the IList<Flag> in the Template class, this code runs. The Id field still works, even through inheritance. I do not understand why this is happening. Can someone enlighten me?
public abstract class Layout
{
public virtual int Id
{
get;
set;
}
public virtual IList<Flag> Flags
{
get;
set;
}
}
public class Template : Layout
{
public virtual string Name
{
get;
set;
}
}
public class Page: Layout
{
}
public class LayoutConfiguration : EntityConfiguration<Layout>
{
public LayoutConfiguration()
{
HasKey(u => u.Id);
Property(u => u.Id).IsIdentity();
MapHierarchy().Case<Page>(c => new
{
c.Id
}).ToTable("Pages");
MapHierarchy().Case<Template>(c => new
{
c.Id,
c.Name
}).ToTable("Templates");
}
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
Property(o => o.Name).HasMaxLength(64).IsUnicode();
HasMany(u => u.Flags).WithOptional()
.Map("template.flags",
(template, flag) => new {
Template = template.Id,
Flag = flag.Id
});
MapSingleType(c => new {
c.Id,
c.Name
}).ToTable("templates");
}
}
public class PageConfiguration : EntityConfiguration<Page>
{
public PageConfiguration()
{
HasMany(c => c.Flags).WithOptional()
.Map("page.flags",
(page, flag) => new
{
Page = page.Id,
Flag = flag.Id
});
}
}
When you use base type for your Template entity, you also have to model this inheritance in mapping. It means that you have to write configuration for Layout which will map Id and Flags and configuration for Template which will map Name. There is several approaches of mapping inheritance in EF. You should probably check Table per Hiearchy.
Edit: Based on your comment you are looking for Table per Class + examples for CTP4.
Edit2: Ok. I tested your scenario with navigation property defined in abstract parent class and it really doesn't work if you are trying to map it to multiple tables.

Categories