EntityFramework: Eager loading with excludes instead of includes? - c#

My data model has a lot of nested entities and I would like to eagerly load the whole object tree ... except for a view entities that will be explicitly loaded on demand.
Using include paths I have to specify many paths and every time I add a new entity I will have to adapt those include paths. I currently use following method of my repository to load all entities of a type:
public virtual IQueryable<TEntity> All(string commaSeperatedIncludePropertyPaths = "")
{
IQueryable<TEntity> initialQuery = Context.Set<TEntity>();
string[] includePaths = commaSeperatedIncludePropertyPaths.Split(new[] { ','}, StringSplitOptions.RemoveEmptyEntries);
return includePaths.Aggregate(initialQuery, (currentQuery, includeProperty) => currentQuery.Include(includeProperty));
}
The passed include paths already fill a whole screen.
Therefore I would prefer that the EntityFramework automatically loads all navigation properties eagerly, except for the ones I specify with exclude paths:
public virtual IQueryable<TEntity> All(string commaSeperatedExcludePropertyPaths = "")
{
//... how to implement?
}
The exclude paths would help to avoid circular dependencies and to filter out the few entities I don't want to load eagerly. Specifying excludes instead of includes would reduce boilerplate code for me.
Is this possible with EF 6.1.3 or planned for EF 7? If not, what are reasons against that option?
Did anyone already try to read entity meta data and apply it for "auto eager loading" and failed?
Related (old) questions and articles:
Overview on options for loading navigation properties:
https://msdn.microsoft.com/en-us/magazine/hh205756.aspx
Auto eager load
Entity framework auto eager load
Entity Framework - Is there a way to automatically eager-load child entities without Include()?
Entity framework linq query Include() multiple children entities
Type save includes
Entity Framework .Include() with compile time checking?

Below is a first draft for a solution. I still have to find out if it's practicable ... and I'll consider to rework the loading approach (as Lanorkin suggested), too. Thank you for your comments.
Edit
It turned out that, while excludes might make sense when developing an application ...doing many changes to the domain model..., excludes are not more elegant than includes for a "real world example" that I just considered.
a) I went through my entities and counted the number of included and excluded navigation properties. The average number of excluded properties was not significantly smaller then the number of included properties.
b) If I do consider a distinct navigation property "foos" for the exclusions, I will be forced to consider exclusions for the sub entities of type Foo ... if I do not want to use its properties at all.
On the other hand, using inclusions, I just need to specify the navigation property "foos" and do not need to specify anything else for the sub entities.
Therefore, while excludes might save some specs for one level, they dent to require more specs for the next level ... (when excluding some intermediate entities and not only entities that are located at the leaves of the loaded object tree).
c) Furthermore, the includes/excludes might not only depend on the type of the entity but also on the path that is used to access it. Then an exclude needs to be specified like "exclude properties xy when loading the entity for one purpose and exclude properties z when loading the entity for another purpose".
=> As a result of this considerations I will go on using inclusions.
I implemented type save inclusions that are based on inclusion dictionaries instead of strings:
private static readonly Inclusions<Person> _personInclusionsWithCompanyParent = new Inclusions<Person>(typeof(Company))
{
{e => e.Company, false},
{e => e.Roles, true}
};
I have a method that creates the query from a list of inclusions. That method also checks if all existing navigation properties are considered in the dictionaries. If I add a new entity and forget to specify corresponding inclusions, an exception will be thrown.
Nevertheless, here is an experimental solution for using excludes instead of includes:
private const int MAX_EXPANSION_DEPTH = 10;
private DbContext Context { get; set; } //set during construction of my repository
public virtual IQueryable<TEntity> AllExcluding(string excludeProperties = "")
{
var propertiesToExclude = excludeProperties.Split(new[]
{
','
},
StringSplitOptions.RemoveEmptyEntries);
IQueryable<TEntity> initialQuery = Context.Set<TEntity>();
var elementType = initialQuery.ElementType;
var navigationPropertyPaths = new HashSet<string>();
var navigationPropertyNames = GetNavigationPropertyNames(elementType);
foreach (var propertyName in navigationPropertyNames)
{
if (!propertiesToExclude.Contains(propertyName))
{
ExtendNavigationPropertyPaths(navigationPropertyPaths, elementType, propertyName, propertyName, propertiesToExclude, 0);
}
}
return navigationPropertyPaths.Aggregate(initialQuery, (current, includeProperty) => current.Include(includeProperty));
}
private void ExtendNavigationPropertyPaths(ISet<string> navigationPropertyPaths,
Type parentType,
string propertyName,
string propertyPath,
ICollection<string> propertiesToExclude,
int expansionDepth)
{
if (expansionDepth > MAX_EXPANSION_DEPTH)
{
return;
}
var propertyInfo = parentType.GetProperty(propertyName);
var propertyType = propertyInfo.PropertyType;
var isEnumerable = typeof(IEnumerable).IsAssignableFrom(propertyType);
if (isEnumerable)
{
propertyType = propertyType.GenericTypeArguments[0];
}
var subNavigationPropertyNames = GetNavigationPropertyNames(propertyType);
var noSubNavigationPropertiesExist = !subNavigationPropertyNames.Any();
if (noSubNavigationPropertiesExist)
{
navigationPropertyPaths.Add(propertyPath);
return;
}
foreach (var subPropertyName in subNavigationPropertyNames)
{
if (propertiesToExclude.Contains(subPropertyName))
{
navigationPropertyPaths.Add(propertyPath);
continue;
}
var subPropertyPath = propertyPath + '.' + subPropertyName;
ExtendNavigationPropertyPaths(navigationPropertyPaths,
propertyType,
subPropertyName,
subPropertyPath,
propertiesToExclude,
expansionDepth + 1);
}
}
private ICollection<string> GetNavigationPropertyNames(Type elementType)
{
var objectContext = ((IObjectContextAdapter)Context).ObjectContext;
var entityContainer = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
var entitySet = entityContainer.EntitySets.FirstOrDefault(item => item.ElementType.Name.Equals(elementType.Name));
if (entitySet == null)
{
return new List<string>();
}
var entityType = entitySet.ElementType;
return entityType.NavigationProperties.Select(np => np.Name)
.ToList();
}

Related

How can I implement multiple Include in Entity Framework?

I use Entity framework 6. I have a Transaction object with several navigation properties. It is easy to implement eager loading using multiple Include.
var aa = db.Transactions.Include(p => p.Account).Include(p => p.Instrument);
How can I implement the same if the fields to be included are parameters?
var aa = db.Transactions.IncludeMore(delegatesToBeIncluded);
If delegatesToBeIncluded is null then there is nothing to be included.
https://stackoverflow.com/a/38823723/5852947 This is similar what I want but it uses string instead of delegates.
https://stackoverflow.com/a/35889204/5852947 This is also interesting.
How to pass lambda 'include' with multiple levels in Entity Framework Core? This focuses on multiple level (I have one level)
https://stackoverflow.com/a/52156692/5852947 This is promising also.
Which direction should I go?
Revision 1: Why I need this?
Based on the elements of aa new objects will be created. I realized that at each object creation EF reads the DB (lazy loading is used). It is just 50 ms, but it is repeated n times.
This function is implemented in a template class, so Transactions is also a parameter.
Revision 2: In the full code there is filtering (pagination to be exact), and ToList() at then end. The tricky part that it is implemented in a template function. dbTableSelector is a delegate: readonly Func<MainDbContext, DbSet<TDbTable>> dbTableSelector;
var myList = dbTableSelector(db).Where(WhereCondition).
Skip(numberOfSkippedRows).Take(PageSize).OrderBy(OrderByCondition).ToList();
After that I transform each element of myList to another type of object. This is where lazy loading is activated one by one for each element. That is why I try to use Include. If dbTableSelector(db) returns Transactions I have to Include different elements when it returns let us say Instruments. So IncludeMore should have a List parameter which defines the fields to be included.
Here is the solution. It is based on this.
public static class IQueryableExtensions
{
public static IQueryable<T> IncludeMultiple<T, TProperty>(this IQueryable<T> query,
Expression<Func<T, TProperty>>[] includeDelegates) where T : class
{
foreach (var includeDelegate in includeDelegates)
query = query.Include(includeDelegate);
return query;
}
}
This is the calling:
var pathsA = new Expression<Func<ViewTransaction, object>>[2] { p => p.Account, p => p.Instrument };
var pathsB = new Expression<Func<ViewTransaction, object>>[1] { p => p.Account};
var pathsC = Array.Empty<Expression<Func<ViewTransaction, object>>>();
var a = db.ViewTransactions.IncludeMultiple(pathsA).Single(e => e.Id == 100);

Duplicating Entity Framework entities

I have this EF query:
var records = mydata.Where(a => some condition).ToList();
Records is a list of Record objects. Record is a database table that has a one to many relationship called "relation".
The EF object represents that member variable as Collection<Relation>.
After the where above, I see that "relation" member variable contains a collection of 18 entities.
I need to duplicate those Record objects, so that I am detaching them first, this way:
var detached = this.DetachEntities(records, db, "RecordId");
Where this.DetachEntities is defined this way:
private T DetachEntity<T>(T entity, Repositories.GestionActivosEntities db, string keyName) where T : class
{
db.Entry(entity).State = EntityState.Detached;
if (entity.GetType().GetProperty(keyName) != null)
{
entity.GetType().GetProperty(keyName).SetValue(entity, 0);
}
return entity;
}
private List<T> DetachEntities<T>(List<T> entities, Repositories.GestionActivosEntities db, string keyName) where T : class
{
foreach (var entity in entities)
{
this.DetachEntity(entity, db, keyName);
}
return entities;
}
After I detached the entities, I am doing:
db.Record.AddRange(detached);
The problem, is that, after I detached the list, the relation is not copied also, resulting that "relation" member variable contains no elements.
How can I keep the relation elements after detaching?
EDIT:
This shows the debugging session:
The breakpoint is placed in the line where the query is done. The cursor shows the line after the query was executed.
This screenshot shows the child records ValorCampo and Workflow
This screenshot shows the entity after detaching it. Note the child records are not present in the detached object
As I have told, the problem only is when detaching. Child records are not kept. Database rows remain untouched.
I had the same problem, unfortunately navigation properties are lost after detaching an Item or when entity state is changed to detached.
what you can do is clone the entity
one way to do this is : Context.Entry(your_entity).CurrentValues.ToObject();
however this will not clone the navigation properties either
if you fully want to clone an object among with navigation properties
the easiest way for me to achieve it was using automapper library for c#
Below is a sample usage:
var config = new MapperConfiguration(cfg => cfg.CreateMap<originalObject, T>());
var mapper = new Mapper(config);
// or
var mapper = config.CreateMapper();
T clonedObject = mapper.Map<T>(originalObject);
after you clone and detach the original object, you can add
db.Record.AddRange(clonedObject );
and below is a generic extension to do it
public static object Map<T>(this T source)
{
var fullName = source.GetType().FullName;
var sourceType = source.GetType();
var baseType = ObjectContext.GetObjectType(source.GetType());
var config = new MapperConfiguration(cfg =>
cfg.CreateMap(sourceType, baseType));
var mapper = config.CreateMapper();
var entity = mapper.Map(source, sourceType, baseType);
return entity;
}
where you can call it like
var clonedObject = originalObject.Map();
Hope this helps!

How to combine Find() and AsNoTracking()?

How to combine Find() with AsNoTracking() when making queries to an EF context to prevent the returned object from being tracked. This is what I can't do
_context.Set<Entity>().AsNoTracking().Find(id);
How can I do that? I am using EF version 6.
Note: I do not want to use SingleOrDefault(), or Where. I just can't because the parameter Id is generic and it's a struct and I can not apply operator == for generics in that case.
So instead of using AsNoTracking() what you can do is Find() and then detach it from the context. I believe that this gives you the same result as AsNoTracking() besides the additional overhead of getting the entity tracked. See EntityState for more information.
var entity = Context.Set<T>().Find(id);
Context.Entry(entity).State = EntityState.Detached;
return entity;
Edit: This has some potential issues, if the context hasn't loaded some relationships, then those navigation properties will not work and you will be confused and frustrated why everything is returning null! See https://stackoverflow.com/a/10343174/2558743 for more info. For now on those repositories I'm overriding the FindNoTracking() methods in my repositories that I need that in.
<context>.<Entity>.AsNoTracking().Where(s => s.Id == id);
Find() does not make sense with AsNoTracking() because Find is supposed to be able to return tracked entities without going to database.. your only option with AsNoTracking is either Where or First or Single...
The accepted answer has the issue that if the item you are trying to find is already being tracked, it will return that item then mark it as untracked (which may mess up other parts of the code).
Akos is on the right track with his suggestion to build the expression yourself, but the example only works for entities that have a single primary key (which covers most cases).
This extension method works in EF Core and effectively matches the signature for the DbSet<T>.Find(object []). But it is an extension method for DbContext instead of DbSet because it needs access to the Entity's metadata from the DbContext.
public static T FindNoTracking<T>(this DbContext source, params object[] keyValues)
where T : class
{
DbSet<T> set = source.Set<T>();
if (keyValues == null || !keyValues.Any())
{
throw new Exception("No Keys Provided.");
}
PropertyInfo[] keyProps = GetKeyProperties<T>(source);
if (keyProps.Count() != keyValues.Count())
{
throw new Exception("Incorrect Number of Keys Provided.");
}
ParameterExpression prm = Expression.Parameter(typeof(T));
Expression body = null;
for (int i = 0; i < keyProps.Count(); i++)
{
PropertyInfo pi = keyProps[i];
object value = keyValues[i];
Expression propertyEx = Expression.Property(prm, pi);
Expression valueEx = Expression.Constant(value);
Expression condition = Expression.Equal(propertyEx, valueEx);
body = body == null ? condition : Expression.AndAlso(body, condition);
}
var filter = Expression.Lambda<Func<T, bool>>(body, prm);
return set.AsNoTracking().SingleOrDefault(filter);
}
public static PropertyInfo[] GetKeyProperties<T>(this DbContext source)
{
return source.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(p => p.PropertyInfo).ToArray();
}
you can then use the method directly on the DbContext. For example, if your entity has a composite key consisting of two strings:
context.FindNoTracking<MyEntity>("Key Value 1", "Key Value 2");
If you really want the Extension method to be on DbSet instead of the DbContext, you can do so but you'll need to get the context from the set in order to gain access to the metadata about the entity. Currently there isn't a good way to do this. There are some hacky ways to do this, but they involve using reflection to access private fields of framework classes, so I'd advise against it.
Alternatively...
If you have a way of figure out what the Key properties are without using the DbContext/Metadata, you can make it an extension for DbSet instead. For example, if all of your Key properties are marked with the [Key] attribute, you can use this code:
public static T FindNoTracking<T>(this DbSet<T> source, params object[] keyValues)
where T : class
{
//Pretty much the same...
}
public static PropertyInfo[] GetKeyProperties<T>()
{
return typeof(T).GetProperties()
.Where(pi => pi.GetCustomAttribute<KeyAttribute>() != null).ToArray();
}
This would also work in both Entity Framework and EF Core.
Back in 2015, an official request was made to include the functionality, i.e. combine Find() and AsNoTracking(). The issue was immediately closed after giving this argument:
AsNoTracking doesn't really make sense for Find since one of the key features of find is that it will return the already tracked version of the entity without hitting the database if it is already in memory. If you want to load an entity by key without tracking it then use Single.
Hence, you could replace:
_context.Set<Entity>().AsNoTracking().Find(id); // Invalid
with something like this:
_context.Set<Entity>().AsNoTracking().Single(e => e.Id == id);
Well, I guess if you really want to do this, you can try creating your expression yourself. I assume you have a base entity class that's generic and that's where the generic key property comes from. I named that class KeyedEntityBase<TKey>, TKey is the type of the key (if you don't have such a class, that's fine, the only thing that I used that for is the generic constraint). Then you can create an extension method like this to build the expression yourself:
public static class Extensions
{
public static IQueryable<TEntity> WhereIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TKey>> keyExpression,
TKey otherKeyValue)
where TEntity : KeyedEntityBase<TKey>
{
var memberExpression = (MemberExpression)keyExpression.Body;
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, memberExpression.Member.Name);
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.Where(lambda);
}
}
And then, you can use it like this (for an integer key type):
context.Set<MyEntity>.AsNoTracking().WhereIdEquals(m=>m.Id, 9).ToList();

Add object to Entity Framework dynamically using Reflection

In the following code, the type of domainObject varies (but ends with DO, which I trim then to get the corresponding table name). Having the name of the table and its type, I want to update an existing object - its name is the same as the tableName due to the EF - in the database with the new property values from domainObject. Therefore, I have to find the POCO in the table with the same ID first to overwrite this. This is the code so far:
public void Update(object domainObject)
{
Type type = domainObject.GetType();
string tableName = type.Name.Substring(0, type.Name.Length - 2);
PropertyInfo tableProp = typeof(MyDbContext).GetProperty(tableName);
Type tableType = tableProp.PropertyType;
Type pocoType = tableType.GetGenericArguments()[0];
int id = (int)type.GetProperty("ID").GetValue(domainObject);
using (var context = new MyDbContext())
{
object table = tableProp.GetValue(context);
MethodInfo singleMethod = tableType.GetMethod("Single");
}
}
Usually, knowing the actual table and not just its type, I would now get the POCO via
var poco = context.TableName.Single(item => item.ID == id);
There's 2 problems here:
(1) Single is an extension method.
(2) I don't have an idea how to get the lambda expression in form of an object to pass it to the Invoke of Single.
Is there any way to do this at all with Reflection, or do I have to work around this? (For example, I could iterate through the items in table and check manually [which would load everything from the DB into memory and thus should be avoided], or maybe configure the EF to do some kind of 'override' whenever I just Add and object whose ID is already present if this is possible). Even supposing I could work around this, I'd still like to know a definitive answer to this question, since it's pretty interesting for me!
If you want to use reflection and to find given entity by ID then, if ID is primary key this is fairly simple as this is all you have to do:
object entity = context.Set(domainObject.GetType()).Find(id);
If your property is not primary key then you need to do it as follows:
ParameterExpression p = Expression.Parameter(domainObject.GetType());
Expression property = Expression.Property(p, "ID");
Expression c = Expression.Constant(id);
Expression body = Expression.Equal(property, c);
Expression exp = Expression.Lambda(body, new ParameterExpression []{ p });
MethodInfo singleMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "Single" && m.GetParameters().Count() == 2)
.MakeGenericMethod(domainObject.GetType());
DbSet dbSet = context.Set(domainObject.GetType());
object entity = singleMethod.Invoke(null, new object[]{ dbSet, exp });
First with Expression class you build expression that will be passed to Single method (in your case this will be p => p.ID == id). Then you search proper Single method from Queryable class. The last thing is to invoke this method with proper parameters. This way you may do any linq queries with use of Reflection.
You simply need to make a generic method, with a type parameter that represents the type of your entity and use the corresponding DbSet.
public int Update<TEntity>(TEntity domainObject)
{
int id = domainObject.Id; // Needs interface !!
using (var context = new MyDbContext())
{
var objectInDb
= ctx.DbSet<TEntity>.Single(e => e.Id == id); // Needs interface !!
// Use ValueInjecter (not AutoMapper) to copy the properties
objectInDb.InjectFrom(domainObject); // needs ValueInjecter Nuget Package
context.SaveChanges();
}
return userId;
}
As you see in the code comments, your entities need to implement an interface so that you can access the Id property:
public interface IId
{
public int Id { get; set; }
}
And then you need to include the generic method in a generic class that has the corresponding type constraint:
public RepoClass<TEntity>
where TEntity : IId
{
// Define the generic method here
}
In this way you don't have to resort to Reflection.
If you're using some kind of T4 template,or whatever, to create your POCOs, make them partial classes, so that you can declare the interface in a separate file, like this:
public partial MyDomainClass : IId
{
}
In this wya, the interface won't be lost when you update your Db Context objects.
And finally, download an use ValueInjecter, for example using Nuget Package Manager, or running Install-Package ValueInjecter in the Nuget Package Manager console.
When you include using Omu.ValueInjecter; namespace in your code, you'll get an InjectFrom extension method on all objects, that allows to automatically copy all the properties from a source object (by matching their names). Don't use AutoMapper, or you'll have to solve other problems.
Alternatively, you can check that the object exists in the DB (for security) and use the original object, without copying the properties, i.e.
var updatedObject = ctx.Set<TEntity>().Attach(domainObject);
ctx.Entry(updatedObject).State = EntityState.Modified;
ctx.SaveChanges();
I prefer this solution, better than the previous one.

Dynamically query entities from Entity Framework for association to primary Entity

I know the title wasn't the greatest, but here is what I actually want to accomplish.
I have a details view that represents Entity1 and its associations. I am capturing the property names and values in a key/value pair. I am currently using reflection to set the Entity properties to the corresponding values for non associations. I doubt this is the most efficient way, but I have been unable to find a better way using Expression Trees. So now I need to set the associations of Entity1 to their corresponding entities based on the primary key of those entity associations, call them Entity2-4.
When iterating the Properties of Entity1, I don't know how to construct a dynamic query to Entity2-4 and set Entity1.association to the corresponding entity. Here is the code I have so far:
foreach (string k in e.Values.Keys)
{
if (e.Values[k] != null && !String.IsNullOrEmpty(e.Values[k].ToString()))
{
System.Type objectType = Entity1.GetType();
PropertyInfo[] p = objectType.GetProperties();
foreach (PropertyInfo pi in p)
{
// set Entity1.Property for non associations (works just fine)
if (pi.Name == k)
{
System.Type t = pi.PropertyType;
pi.SetProperty(e.Values[k].ToString(), Entity1);
break;
}
// when i see pi.Name contain Reference, I know I'm working on an association
else if (pi.Name.Contains("Reference"))
{
// k is in the form of Entity.Property
var name = pi.Name.Left("Reference");
var keys = k.Split('.');
var ent = keys[0];
var prop = keys[1];
if (name == ent)
{
// here I need to obtain an instance from the db
// ie generate my dynamic query to the Entity with the name
// contained within the var "ent"
// I tried using reflection and could instantiate the entity
// but it did me no good as I needed the entity from the db
var entityInstance = some dynamic query;
// here I need to set the association of Entity1 to entityInstance from above
// normally I would use reflection, but I'm not sure that would work
// since EntityReference is the actual property returned by reflection
Entity1.SetAssocation(prop, Entity2);
break;
}
}
}
}
}
EDIT
I basically need construct the entity and its association entities so that I can submit them to the data context. Entity 2 through 4 exist in the db, I need to query the db to obtain the instances to that I may associate them to the new Entity1 I am creating and going to submit.
My basic model:
Entity1
Entity1.ID
Entity1.Prop1
Entity1.Prop2
Entity1.Prop3
Entity1.Entity2
Entity1.Entity3
Entity1.Entity4
Entity2
Entity2.ID
Entity2.Name
Entity3
Entity3.ID
Entity3.Name
Entity4
Entity4.ID
Entity4.Name
We've had an in-depth discussion about how to get the metadata out of the DbContext. Here are a few links to get you started. And I'll add some specific comments.
How I can read EF DbContext metadata programmatically?
A short summary (but you should check in there for more):
using (var db = new MyDbContext())
{
var objectContext = ((IObjectContextAdapter)db).ObjectContext;
var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
var dependents = ((EntitySet)(set)).ForeignKeyDependents;
var principals = ((EntitySet)(set)).ForeignKeyPrincipals;
var navigationProperties = ((EntityType)(set.ElementType)).NavigationProperties;
// and e.g. for many-to-many (there is more for other types)
ManyToManyReferences = navigationProperties.Where(np =>
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
}
#Goran Obradovic did a great job to wrap up what I started into a set of reusable queries (my due credits to him:).
I have worked out all other sorts of information that's in there. This is just for the DataSpace.CSpace (which is the most useful to you), but there is also DataSpace.SSpace etc. - which are more for creating SQL queries etc. I'll put most links at the bottom.
Specifics:
In your case following might be helpful:
(note: I'm not entirely sure what you're after, but I'm trying to guess here what's the direction that you're heading)
db.Set<Worker>().Find(1);
Is the generic method for accessing DbSet for a specific entity.
You could also construct it from a Type if you need it to be fully dynamic, e.g....
(I always wanted to do this:)
MethodInfo setMethod = typeof(DbContext).GetMethod("Set", new Type[]{});
MethodInfo genericSetMethod = setMethod.MakeGenericMethod(new Type[] { typeof(YourEntity) });
var set = genericSetMethod.Invoke(db, new object[] {});
Put your entity - or your Type instead of typeof(YourEntity).
You could then proceed and query that for e.g. Find(id) - for that entity - to get concrete values etc.
That's as dynamic as it gets - I'm not sure if that's what you want - but I'm just throwing things out here in case you need it.
That should get you started at least I hope.
links:
(all are posts of mine - some may be more or less relevant but might help)
How I can read EF DbContext metadata programmatically?
How check by unit test that properties mark as computed in ORM model?
Get Model schema to programmatically create database using a provider that doesn't support CreateDatabase
Programmatic data transformation in EF5 Code First migration
So, I wasn't able to perform this dynamically. But here is my working solution. Can anyone advise on how to perform this dynamically?
foreach (string k in e.Values.Keys)
{
if (e.Values[k] != null && !String.IsNullOrEmpty(e.Values[k].ToString()))
{
System.Type objectType = roster.GetType();
PropertyInfo[] p = objectType.GetProperties();
foreach (PropertyInfo pi in p)
{
if (pi.Name == k)
{
System.Type t = pi.PropertyType;
pi.SetProperty(e.Values[k].ToString(), roster);
break;
}
else if (pi.Name.Contains("Reference"))
{
var name = pi.Name.Left("Reference");
var keys = k.Split('.');
var entityName = keys[0];
var prop = keys[1];
if (name == entityName )
{
var val = e.Values[k].ToString();
switch (pi.Name)
{
case "Entity2Reference":
Entity1.Entity2Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, val);
break;
case "Entity3Reference":
Entity1.Entity3Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, val);
break;
case "Entity4Reference":
Entity1.Entity4Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, Int64.Parse(val));
break;
}
}
}
}
}
}

Categories