Using navigation to load entity 2 level deep - c#

I have a class
public class Level1
{
public int Id {get; set;}
public virtual List<Level2> Level2List {get; set;}
}
public class Level2
{
public int Id {get; set;}
public int Level3Id {get; set;}
public virtual Level3 Level3 {get; set;}
}
public class Level3
{
public int Id {get; set;}
public string Name {get; set;}
}
Using navigation properties i could load List<Level2> like this
var myList = _myLevel1Repository.GetSingle(x=>x.Id == Level1Id, x=>x.Level2List);
but how do I load Level3 and its properties that is linked with Level2?
PS: Lazy loading is not possible.
This is the Getsingle function
public T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navProps)
{
T item = null;
using (var context = new MyDbContext())
item = GetFilteredSet(context, navProps).AsNoTracking().FirstOrDefault(where);
return item;
}

Your GetSingle method should be this way:
public T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navProps)
{
T item = null;
using (var context = new MyDbContext())
{
IQueryable<T> query = context.Set<T>();
//Include the navigations properties as part of the query
if (navProps!= null)
{
query = navProps.Aggregate(query, (current, include) => current.Include(include));
}
item = query.Where(where).FirstOrDefault();
}
return item;
}
I don't know what are you doing in the GetFilteredSet method, but I guess you can reorganize the code I show above at your convenience. The key to include more than one level of nav. properties in EF is use the Include method. When you use this method you are going to load the nav. properties as part of your query (check eager loading section in this link). Now, there are two Include methods :
DbQuery.Include Method
Whit this method you need to pass the path of the nav. properties you want to load as an string, for example, in your case, it would be:
context.Set<Level1>.Include("Level2List.Level3");
DbExtensions.Include extension method
This is the method that I use in my above code where you can specify the related objects to include using a lambda expression. IMHO this is the best variant because is strongly typed, and if you change some of the nav. properties name in your entities, you are also going to receive a compilation error(s). In the link I share above, you can see all the patterns you can use to include different levels of nav. properties.
context.Set<Level1>.Include(l1=>l1.Level2List.Select(l2=>l2.Level3));
Returning to the initial problem, now you can use your GetSingle method to include more than one level this way:
var entity= _myLevel1Repository.GetSingle(x=>x.Id == Level1Id, x=>x.Level2List.Select(l2=>l2.Level3));

How about using include?
var mylevel1s = _db.Level1(x=>x.Id == Level1Id).Include(x=> x.Level2List.Select(a=>a.Level3));

Related

Nested expression building with linq and Entity Framework

I'm trying to make a service that returns a catalog based on the filters.
I've seen a few results on the internet, but not quite my issue. I hope you can help me with mine.
The issue is that this query build cannot be translated into a store expression:
'LINQ to Entities does not recognize the method 'System.Linq.IQueryable'1[App.Data.Models.Subgroup] HasProductsWithState[Subgroup](System.Linq.IQueryable'1[App.Data.Models.Subgroup], System.Nullable`1[System.Boolean])' method, and this method cannot be translated into a store expression.'
How can I make it so the query can be translated into a store expression.
Please don't suggest .ToList() as a answer as I don't want this to run in memory.
So what I have is:
bool? isActive = null;
string search = null;
DbSet<Maingroup> query = context.Set<Maingroup>();
var result = query.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(x => x.SortOrder)
.Select(x => new CatalogViewModel.MaingroupViewModel()
{
Maingroup = x,
Subgroups = x.Subgroups.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(y => y.SortOrder)
.Select(y => new CatalogViewModel.SubgroupViewModel()
{
Subgroup = y,
Products = y.Products.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(z => z.SortOrder)
.Select(z => new CatalogViewModel.ProductViewModel()
{
Product = z
})
})
});
return new CatalogViewModel() { Maingroups = await result.ToListAsync() };
In the code below you can see that I recursively call the extension to try and stack the expression. But when I walk through my code at runtime it does not enter the function again when
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
is called.
public static class ProductServiceExtensions
{
public static IQueryable<TEntity> HasProductsWithState<TEntity>(this IQueryable<TEntity> source, bool? state)
{
if (source is IQueryable<Maingroup> maingroups)
{
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Subgroup> subgroups)
{
return subgroups.Where(x => x.Products.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Product> products)
{
return products.Where(x => x.IsActive == state) as IQueryable<TEntity>;
}
return source;
}
public static IQueryable<TEntity> HasChildrenWithName<TEntity>(this IQueryable<TEntity> source, string search)
{
if (source is IQueryable<Maingroup> maingroups)
{
return maingroups.Where(x => search == null || x.Name.ToLower().Contains(search) || x.Subgroups.AsQueryable().HasChildrenWithName(search).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Subgroup> subgroups)
{
return subgroups.Where(x => search == null || x.Name.ToLower().Contains(search) || x.Products.AsQueryable().HasChildrenWithName(search).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Product> products)
{
return products.Where(x => search == null || x.Name.ToLower().Contains(search)) as IQueryable<TEntity>;
}
return source;
}
}
UPDATE
Missing classes:
public class Maingroup
{
public long Id { get; set; }
public string Name { get; set; }
...
public virtual ICollection<Subgroup> Subgroups { get; set; }
}
public class Subgroup
{
public long Id { get; set; }
public string Name { get; set; }
public long MaingroupId { get; set; }
public virtual Maingroup Maingroup { get; set; }
...
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public long SubgroupId { get; set; }
public virtual Subgroup Subgroup { get; set; }
...
public bool IsActive { get; set; }
}
The cause of your problem
You have to be aware between an IEnumerable and an IQueryable. An IEnumerable object has everything in it to enumerate over all the elements: you can ask for the first element of the sequence, and once you've got an element, you can ask for the next element, until there are no more elements.
An IQueryable seems similar, however, the IQueryable does not hold everything to enumerate the sequence. It holds an Expression and a Provider. The Expression is a generic form of what must be queried. The Provider knows who must execute the query (usually a database management system), how to communicate with this executor and which language to use (usually something SQL-like).
As soon as you start enumerating, either explicitly by calling GetEnumerator and MoveNext, or implicitly by calling foreach, ToList, FirstOrDefault, Count, etc, the Expression is sent to the Provider, who will translate it into SQL and call the DBMS. The returned data is presented as an IEnumerable object, which is enumerated, using GetEnumerator
Because the Provider has to translate the Expression into SQL, the Expression may only call functions that can be translated into SQL. Alas, the Provider does not know HasProductsWithState, nor any of your own defined functions, and thus can't translate it into SQL. In fact, the entity framework provider also does not know how to translate several standard LINQ functions, and thus they can't be used AsQueryable. See Supported and Unsupported LINQ methods.
So you'll have to stick to functions that return an IQueryable where the Expression contains only supported functions.
Class Description
Alas you forgot to give us your entity classes, so I'll have to make some assumptions about them.
Apparently have a DbContext with at least three DbSets: MainGroups, SubGroups and Products.
There seems to be a one-to-many (or possible many-to-many) relation between MaingGroups and SubGroups: every MainGroup has zero or more SubGroups.
It seems that there is also a one-to-many relation between SubGroups and Products: every SubGroup has zero or more Products.
Alas you forgot to mentions that return relation: does every Product belong to exactly one SubGroup (one-to-many), or does every Product belong to zero or more SubGroups (many-to-many`)?
If you've followed the entity framework code first conventions, you will have classes similar to this:
class MainGroup
{
public int Id {get; set;}
...
// every MainGroup has zero or more SubGroups (one-to-many or many-to-many)
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
class SubGroup
{
public int Id {get; set;}
...
// every SubGroup has zero or more Product(one-to-many or many-to-many)
public virtual ICollection<Product> Products{get; set;}
// alas I don't know the return relation
// one-to-many: every SubGroup belongs to exactly one MainGroup using foreign key
public int MainGroupId {get; set;}
public virtual MainGroup MainGroup {get; set;}
// or every SubGroup has zero or more MainGroups:
public virtual ICollection<MainGroup> MainGroups {get; set;}
}
Something similar for Product:
class Product
{
public int Id {get; set;}
public bool? IsActive {get; set;} // might be a non-nullable property
...
// alas I don't know the return relation
// one-to-many: every Productbelongs to exactly one SubGroup using foreign key
public int SubGroupId {get; set;}
public virtual SubGroup SubGroup {get; set;}
// or every Product has zero or more SubGroups:
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
And of cours your DbContext:
class MyDbContext : DbContext
{
public DbSet<MainGroup> MainGroups {get; set;}
public DbSet<SubGroup> SubGroups {get; set;}
public DbSet<Product> Products {get; set;}
}
This is all that entity framework needs to know to detect your tables, the columns in your tables and the relations between the tables (one-to-many, many-to-many, one-to-zero-or-one). Only if you want to deviate from the standard naming you'll need attributes of fluent API.
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many).
Note that although the SubGroups of a MainGroup is declared as a collection, if you query the SubGroups of the MaingGroup with Id 10 you'll still get an IQueryable.
Requirements
Given a queryable sequence of Products and a nullable Boolean State, HasProductsWithState(products, state) should return the queryable sequence of Products that have a value of IsActive equal to State
Given a queryable sequence of SubGroups and a nullable Boolean State, HasProductsWithState(subGroups, state) should return the queryable sequence of SubGroups that have at least one Product that "HasProductsWithState(Product, State)1
Given a queryable sequence of MainGroups and a nullable Boolean State, HasProductsWithState(mainGroups, state) should return the queryable sequence of MainGroups, that contains all MainGroups that have at least one SubGroup that HasProductsWithState(SubGroup, State)
Solution
Well If you write the requirements like this, the extension methods are easy:
IQueryable<Product> WhereHasState(this IQueryable<Product> products, bool? state)
{
return products.Where(product => product.IsActive == state);
}
Because this function does not check whether a Product has this state, but returns all Product that have this state, I chose to use a different name.
bool HasAnyWithState(this IQueryable<Product> products, bool? state)
{
return products.WhereHasState(state).Any();
}
Your code will be slightly different if IsActive is a non-nullable property.
I'll do something similar with SubGroups:
IQueryable<SubGroup> WhereAnyProductHasState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subgroups.Where(subGroup => subGroup.Products.HasAnyWithState(state));
}
bool HasProductsWithState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subGroups.WhereAnyProductHasState(state).Any();
}
Well, you'll know the drill by now for MainGroups:
IQueryable<MainGroup> WhereAnyProductHasState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return maingroups.Where(mainGroup => mainGroup.SubGroups.HasProductsWithState(state));
}
bool HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups.WhereAnyProductHasState(state).Any();
}
If you look really closely, you'll see that I didn't use any self-defined function. My function calls will only change the Expression. The changed Expression can be translated into SQL.
I've separated the function into a lot of smaller functions, because you didn't say whether you want to use HasProductsWithState(this IQueryable<SubGroup>, bool?) and HasProductsWithState(this IQueryable<Product>, bool?).
TODO: do something similar for similar for HasChildrenWithName: separate into smaller functions that contain only LINQ functions, and nothing else
If you'll only call HasProductsWithState(this IQueryable<MainGroup>, bool?) you can do it in one function, using `SelectMany:
IQueryable<MainGroup> HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups
.Where(mainGroup => mainGroup.SelectMany(mainGroup.SubGroups)
.SelectMany(subGroup => subGroup.Products)
.Where(product => product.IsActive == state)
.Any() );
}
But when I walk through my code at runtime it does not enter the function again when
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state)
Welcome to the world of expression trees!
x => x.Subgroups.AsQueryable().HasProductsWithState(state)
is lambda expression (Expression<Func<...>) with body
x.Subgroups.AsQueryable().HasProductsWithState(state)
The body is expression tree, in other words - code as data, hence is never executed (except if compiled to delegate as in LINQ to Objects).
It's easily overlooked since visually lambda expressions look like delegates. Even Harald in their answer after all explanations that one should not use custom methods, as a solution actually provides several custom methods with the rationale "I didn't use any self-defined function. My function calls will only change the Expression. The changed Expression can be translated into SQL". Sure, but if your functions are called! Which of course does not happen when they are inside expression tree.
With that being said, there is no good general solution. What I can offer is solution for your particular problem - transforming custom methods which receive IQueryable<T> plus other simple parameters and return IQueryable<T>.
The idea is to use custom ExpressionVisitor which identifies the "calls" to such method inside expression tree, actually calls them and replaces them with the result of the call.
The problem is to call
x.Subgroups.AsQueryable().HasProductsWithState(state)
when we have no actual x object. The trick is to call them with fake queryable expression (like LINQ to Objects Enumerable<T>.Empty().AsQueryble()) and then use another expression visitor to replace the fake expression with the original expression in the result (pretty much like string.Replace, but for expressions).
Here is the sample implementation of the above:
public static class QueryTransformExtensions
{
public static IQueryable<T> TransformFilters<T>(this IQueryable<T> source)
{
var expression = new TranformVisitor().Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class TranformVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name.StartsWith("Has")
&& node.Type.IsGenericType && node.Type.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& node.Arguments.Count > 0 && node.Arguments.First().Type == node.Type)
{
var source = Visit(node.Arguments.First());
var elementType = source.Type.GetGenericArguments()[0];
var fakeQuery = EmptyQuery(elementType);
var args = node.Arguments
.Select((arg, i) => i == 0 ? fakeQuery : Evaluate(Visit(arg)))
.ToArray();
var result = (IQueryable)node.Method.Invoke(null, args);
var transformed = result.Expression.Replace(fakeQuery.Expression, source);
return Visit(transformed); // Apply recursively
}
return base.VisitMethodCall(node);
}
static IQueryable EmptyQuery(Type elementType) =>
Array.CreateInstance(elementType, 0).AsQueryable();
static object Evaluate(Expression source)
{
if (source is ConstantExpression constant)
return constant.Value;
if (source is MemberExpression member)
{
var instance = member.Expression != null ? Evaluate(member.Expression) : null;
if (member.Member is FieldInfo field)
return field.GetValue(instance);
if (member.Member is PropertyInfo property)
return property.GetValue(instance);
}
throw new NotSupportedException();
}
}
static Expression Replace(this Expression source, Expression from, Expression to) =>
new ReplaceVisitor { From = from, To = to }.Visit(source);
class ReplaceVisitor : ExpressionVisitor
{
public Expression From;
public Expression To;
public override Expression Visit(Expression node) =>
node == From ? To : base.Visit(node);
}
}
Now all you need is to call .TransformFilters() extension methods at the end of your queries, for instance in your sample
var result = query.AsQueryable()
// ...
.TransformFilters();
You can also call it on intermediate queries. Just make sure the call is outside expression tree :)
Note that the sample implementation is processing static methods having first parameter IQueryable<T>, returning IQueryable<T> and name starting with Has. The last is to skip Queryable and EF extension methods. In the real code you should use some better criteria - for instance the type of the defining class, or custom attribute etc.

Find Max(Id) of DbSet<T> where T is unknown

How do I find the biggest Id of a DbSet.Set<T>()?
Note: not DbSet<TEntity>.
I don't know the type at runtime.
Context: I have 20 tables/entities, which I'm using a generic method to do processing.
The process involves looking up the biggest Id of that table/entity and comparing it with the record at hand.
If the record's id is bigger than the database's, than it would be inserted into the database.
So far I've tried using reflection:
DbSet<T> table = DbContext.Set<T>();
var lastRecord = table.LastOrDefault(); // throws not supported error
var idProperty = lastRecord.GetType().GetProperties()
.FirstOrDefault(p => p.Name.Equals("Id");
int maxId = (int)idProperty.GetValue(lastRecord);
I've also tried using an interface cast:
interface ICommonEntity
{ // this interface allows the generic method
string StringId { get;} // to know how to handle entity Id's of
int? IntId { get; } // different types (string vs int).
}
var whatever = table.OrderByDescending(e => (e as ICommonEntity).IntId).FirstOrDefault();
int maxId = (whatever as ICommonEntity).IntId ?? 0;
But the above yields the following error:
The 'TypeAs' expression with an input of type xx is not supported. and a check of type yy. Only entity types and complex types are supported in LINQ to Entities queries
Additional data: All my entities have the column/property Id of type int.
Web searches that I've done mainly point to solutions that the type is known e.g. TEntity, db.Users.xxx() etc..
Update
In response to Ian's answer, I can't use Id directly. Why?
One of my entity has a field named Id, but is of type string.
class EntityStringId : ICommonEntity
{
public string Id { get; set; }
public string StringId => Id;
public int? IntId => null;
}
class EntityIntId : ICommonEntity
{
public int Id { get; set; }
public string StringId => null;
public int? IntId => Id;
}
And if I try to use IntId for ordering,
private void SomeMethod<T>(string file)
//where T : class // original
//where T : ICommonEntity // cannot. DbContext.Set<T>(); requires class
where T : class, ICommonEntity // throws exception
{
var table_T = DbContext.Set<T>();
var maxId = table_T.Max(e => e.IntId); // throws exception ↓
}
The specified type member 'IntId' is not supported in LINQ to Entities.
Only initializers, entity members, and entity navigation properties are supported.
For a better picture, my method's logic:
private void ProcessCsvToDb<T>(
DbSet<T> table,
T csvRecord) where T : class
{
var iRecord = csvRecord as ICommonEntity;
T dbRecord = null;
if (!string.IsNullOrEmpty(iRecord.StringId))
{
dbRecord = table.Find(iRecord.StringId);
}
else if (iRecord.IntId != null)
{
dbRecord = table.Find(iRecord.IntId);
}
}
In order to do this without a base class/interface, you will need to manually compose the expression:
public static IOrderedQueryable<int> OrderById(Type entityType)
{
var dbSet = context.Set(entityType);
var item = Expression.Parameter(entityType, "item");
var property = Expression.Property(item, "Id");
var lambda = Expression.Lambda<Func<T, int>>(property, item);
// the above generates:
// item => item.Id
return dbSet.OrderByDescending(lambda);
}
You can build expression to sort by Id, but DynamicQueryable class does it for you:
DbSet<T> table = assignFromSomeWhere();
var maxId = table.OrderBy("Id desc").FirstOrDefault();
DynamicQueryable also gives you different extension methods (dynamic Where, Select). Obviously it is bigger satisfaction to build expressions on your own, but sometimes it is very complicated and this library helps a lot.
If you have an interface, as discussed in comments, is there any reason you can't do this to avoid the cast:
public static int? GetMaxId<T>(DBSet<T> dbSet)
where T : ICommonEntity
{
return dbSet.OrderByDescending(e => e.Id).FirstOrDefault();
}

Expression<TDelegate> on EF Include Statement

Hello so I am trying to make an some a bit more dynamic, that said i would like to be able to pass in an expression that will include the entities that I am trying to include. when i am trying to do this i keep getting an error that says:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have googled this error and saw that they were doing what i can get to work:
context.Contacts.Include(contact=>contact.PhoneNumber)
what I am trying to do is this:
Func<IEntity, IEntity> func = (contact) => ((Contact)contact).PhoneNumber;
Expression<Func<IEntity, IEntity>> expression = (contact)=> func(contact);
context.Contacts.Include(expression);
can someone please expain what I am doing wrong and why?
public interface IEntity
{
int Id { get; set; }
}
public class Contact:IEntity
{
public int Id {get;set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public int PhoneNumberId { get; set; }
public PhoneNumber PhoneNumber { get; set; }
}
public class PhoneNumber:IEntity
{
public int Id { get; set; }
public string Number { get; set; }
}
UPDATE:
I have a repository class that looks at the type of class and uses reflection to get the correct DbSet and returns a IQueryable.
public IQueryable Get(IEntity t)
{
var setMethod = typeof(DbContext)
.GetMethod(nameof(DbContext.Set))
.MakeGenericMethod(t.getType());
var query = (IQueryable)setMethod.Invoke(db, null);
var results = query...
}
I have a table control that goes and gets the correct data using the typeRepository. So I am trying to be able to include entities to that table.
When you don't have a generic type argument, but simple Type parameter, you'd better use the non generic DbContext and IQueryable services provided by EF.
First, you don't need reflection - DbContext provides non generic Set method with Type argument:
public virtual DbSet Set(Type entityType)
As for Include, you can simply use the non generic Include extension method with string argument:
public static IQueryable Include(this IQueryable source, string path)
So the method in question can be implemented like this:
IQueryable query = db.Set(t.GetType());
if (t is Contact)
query = query.Include(nameof(Contact.PhoneNumber));
Not the best OOP practices, but works for the chosen design.
I believe your issue relates to the delegate type you are assigning to your Func. IE: Func<IEntity, IEntity> func
In which case IEntity does not have a navigation property defined of type IEntity
Try assigning the concrete type for your implementation
Func<Contact, PhoneNumber> func

Update Entity property in EF where property is another entity

I am using Entity Framework 6 and I need to update the properties of a entity.
I have the following entities:
public class File
{
public Int32 Id { get; set; }
public Byte Data { get; set; }
public DateTime Updated { get; set; }
public virtual Mime Mime { get; set; }
}
public class Mime
{
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<File> Files { get; set; }
}
Then I used the following:
_repository.Update<File>(file, x => x.Data, x => x.Mime, x => x.Updated);
The repository method is the following:
public void Update<T>(T entity,
params Expression<Func<T, Object>>[] properties)
where T : class
{
_context.Set<T>().Attach(entity);
foreach (var property in properties)
{
MemberExpression expression =
property.Body is MemberExpression ?
(MemberExpression)property.Body :
(MemberExpression)(((UnaryExpression)property.Body)
.Operand);
_context.Entry<T>(entity)
.Property(expression.Member.Name).IsModified = true;
}
}
This works fine for Data and Updated properties but not for Mime. I get the error:
The property 'Mime' on type 'File' is not a primitive or complex property. The Property method can only be used with primitive or complex properties. Use the Reference or Collection method.
Is it possible to make this work and integrate it on my repository method?
Yes, I think that can be done. The problem here is that I didn't see any easy way to check whenever a property is part of the table, or is it navigational property. Thus it's hard to call the right behavior.
If you're interested, take a look at EF6 source code, InternalEntityEntry.cs -> Property(..) which does huge amount of property validation through metadata.
The main idea is to basically scan your conceptual model, and determine whenever the property is navigational property(eg if the property leads to another table), or if it's complex/primitive.
According to that, you call the right functionality.
var propertyName = expression.Member.Name;
var propertyType = __get_property_type__(propertyName);
if(propertyType==Property || propertyType==Complex)
{
_context.Entry<T>(entity)
.Property(propertyName).IsModified = true;
continue;
}
if(propertyType==Navigational){
// hm, do we need Attach it first?!
// not sure.. have to test first.
dynamic underlyingReference = entity.GetType()
.GetProperty(propertyName)
.GetValue(entity, null);
_context.Entry(underlyingReference).State = EntityState.Modified;
}
The catch here is to have __get_property_type__ that works. There's Microsoft.Data.Edm.dll that let's you work with the conceptual model, but it's not that easy I think.
This is the way how EF6 detects if we're dealing with reference property or not:
EdmMember member;
EdmEntityType.Members.TryGetValue(propertyName, false, out member);
var asNavProperty = member as NavigationProperty;
// if asNavProperty!=null, we have navigation property.
100% Gerts point. I see no reason to approach the problem they way you have.
Anyway, to answer the question. You have another answer there. Potentially useful.
Whats missing is this:
How to get a list of managed types from the context.
public static IList<Type> GetContextManagedTypes(DbContext context) {
ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
IEnumerable<EntityType> managedTypes = workspace.GetItems<EntityType>(DataSpace.OSpace);
var typeList = new List<Type>();
foreach (var managedType in managedTypes) {
var pocoType = managedType.FullName.GetCoreType();
typeList.Add(pocoType);
}
return typeList;
}

Entity Framework - Navigation Properties Not Saving

I'm having huge difficulties getting my navigation properties to work in EF Code First. As an abstracted example, I have:
public class Parent{
public int ParentID {get; set;}
public virtual List<NamedChild> Children {get; set;}
public Parent(){}
public void Init(int ParentID, List<UnnamedChild> Children){
this.ParentID = ParentID;
this.Children = Children.ConvertAll(x => new NamedChild(x, ""));
}
}
public class NamedChild{
public int ChildID {get; set;}
public string Name {get; set;}
public NamedChild(UnnamedChild c, string Name){
this.ChildID = c.ChildID;
this.Name = Name;
}
}
public class UnnamedChild{
public int ChildID {get; set;}
public UnnamedChild(int ChildID){
this.ChildID = ChildID;
}
}
and then later...
List<UnnamedChild> children = GetChildrenFromSomewhere();
Parent p = db.Parents.Create();
p.Init(1, children);
db.Parents.Add(p);
db.SaveChanges();
Now if I'm debugging I can look into the current DbSet and it shows that there is 1 Parent, and its "Children" property is set to a List of 2 NamedChild. This is good, this is what it should be. However, if I stop the program and re-run it, when I look in the DbSet there is still 1 Parent, but its "Children" property has been set to null.
In summary, immediately after saving it the values are right, but as soon as I re-load the DB Context those values are missing (nulls). I am running the most recent EF with LazyLoading enabled.
It should be noted that if I use .Include(), it will populate those null values with the proper NamedChild list, but I need this to work with LazyLoading.
I think EF is probably unable to create a proxy for NamedChild objects, and can't perform any lazy loading as a result.
One of the requirements for creating a proxy class is that your POCO must have a public/protected constructor without parameters.
This may solve your problem:
public class NamedChild
{
public int ChildID {get; set;}
public string Name {get; set;}
protected NamedChild() {}
public NamedChild(UnnamedChild c, string Name)
{
this.ChildID = c.ChildID;
this.Name = Name;
}
}
I believe you already meet all the other requirements for lazy loading proxies.
Full Requirements here:
http://msdn.microsoft.com/en-us/library/vstudio/dd468057%28v=vs.100%29.aspx
Although I don't think it should technically matter, I've noticed that EF seems to prefer ICollections to other list/array types. Try:
public virtual ICollection<NamedChild> Children {get; set;}
Also, I'm a little confused about what you're trying to achieve with your custom constructors. It seems that all you're doing is initializing the properties on the instance. If that's the case, a custom constructor is not needed. Just use the class initialization syntax:
x => new NamedChild { ChildId = x.ChildId, Name = "" }

Categories