This article explains that query plan caching can be deactivated by setting EnablePlanCaching to false on the ObjectQuery, and also that it is enabled by default in EF6.
Alas, EF6 doesn't have an ObjectQuery, the DbSet's give you DbQuerys.
I don't think I can get an ObjectQuery from the DbQuery, and I certainly don't want to use the old ObjectContext. So is that it or is there a way to disable plan caching in EF6?
Here is an extension method that will let you get an ObjectQuery from the DbSet
using (var ctx = new TestContext())
{
var query = ctx.Products;
query.GetObjectQuery().EnablePlanCaching = false;
var list = query.ToList();
}
namespace Z.EntityFramework.Plus
{
internal static partial class InternalExtensions
{
/// <summary>An IQueryable<TEntity> extension method that get the ObjectQuery from the query.</summary>
/// <typeparam name="T">The type of elements of the query.</typeparam>
/// <param name="query">The query to get the ObjectQuery from.</param>
/// <returns>The ObjectQuery from the query.</returns>
internal static ObjectQuery<T> GetObjectQuery<T>(this IQueryable<T> query)
{
// CHECK for ObjectQuery
var objectQuery = query as ObjectQuery<T>;
if (objectQuery != null)
{
return objectQuery;
}
// CHECK for DbQuery
var dbQuery = query as DbQuery<T>;
if (dbQuery == null)
{
throw new Exception("Oops! A general error has occurred. Please report the issue including the stack trace to our support team: info#zzzprojects.com");
}
var internalQueryProperty = dbQuery.GetType().GetProperty("InternalQuery", BindingFlags.NonPublic | BindingFlags.Instance);
var internalQuery = internalQueryProperty.GetValue(dbQuery, null);
var objectQueryContextProperty = internalQuery.GetType().GetProperty("ObjectQuery", BindingFlags.Public | BindingFlags.Instance);
var objectQueryContext = objectQueryContextProperty.GetValue(internalQuery, null);
objectQuery = objectQueryContext as ObjectQuery<T>;
return objectQuery;
}
}
}
Related
I am one of the many struggling to "upgrade" from ASP.NET to ASP.NET Core.
In the ASP.NET project, I made database calls from my DAL like so:
var result = context.Database.SqlQuery<Object_VM>("EXEC [sp_Object_GetByKey] #Key",
new SqlParameter("#Key", Key))
.FirstOrDefault();
return result;
My viewmodel has additional fields that my object does not, such as aggregates of related tables. It seems unnecessary and counter intuitive to include such fields in a database / table structure. My stored procedure calculates all those things and returns the fields as should be displayed, but not stored.
I see that ASP.NET Core has removed this functionality. I am trying to continue to use stored procedures and load view models (and thus not have the entity in the database). I see options like the following, but as a result I get "2", the number of rows being returned (or another mysterious result?).
using(context)
{
string cmd = "EXEC [sp_Object_getAll]";
var result = context.Database.ExecuteSQLCommand(cmd);
}
But that won't work because context.Database.ExecuteSQLCommand is only for altering the database, not "selecting".
I've also seen the following as a solution, but the code will not compile for me, as "set" is really set<TEntity>, and there isn't a database entity for this viewmodel.
var result = context.Set().FromSql("EXEC [sp_Object_getAll]");
Any assistance much appreciated.
Solution:
(per Tseng's advice)
On the GitHub Entity Framework Issues page, there is a discussion about this problem. One user recommends creating your own class to handle this sort of requests, and another adds an additional method that makes it run smoother. I changed the methods slights to accept slightly different params.
Here is my adaptation (very little difference), for others that are also looking for a solution:
Method in DAL
public JsonResult GetObjectByID(int ID)
{
SqlParameter[] parms = new SqlParameter[] { new SqlParameter("#ID", ID) };
var result = RDFacadeExtensions.GetModelFromQuery<Object_List_VM>(context, "EXEC [sp_Object_GetList] #ID", parms);
return new JsonResult(result.ToList(), setting);
}
Additional Class
public static class RDFacadeExtensions
{
public static RelationalDataReader ExecuteSqlQuery(
this DatabaseFacade databaseFacade,
string sql,
SqlParameter[] parameters)
{
var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();
using (concurrencyDetector.EnterCriticalSection())
{
var rawSqlCommand = databaseFacade
.GetService<IRawSqlCommandBuilder>()
.Build(sql, parameters);
return rawSqlCommand
.RelationalCommand
.ExecuteReader(
databaseFacade.GetService<IRelationalConnection>(),
parameterValues: rawSqlCommand.ParameterValues);
}
}
public static IEnumerable<T> GetModelFromQuery<T>(
DbContext context,
string sql,
SqlParameter[] parameters)
where T : new()
{
DatabaseFacade databaseFacade = new DatabaseFacade(context);
using (DbDataReader dr = databaseFacade.ExecuteSqlQuery(sql, parameters).DbDataReader)
{
List<T> lst = new List<T>();
PropertyInfo[] props = typeof(T).GetProperties();
while (dr.Read())
{
T t = new T();
IEnumerable<string> actualNames = dr.GetColumnSchema().Select(o => o.ColumnName);
for (int i = 0; i < props.Length; ++i)
{
PropertyInfo pi = props[i];
if (!pi.CanWrite) continue;
System.ComponentModel.DataAnnotations.Schema.ColumnAttribute ca = pi.GetCustomAttribute(typeof(System.ComponentModel.DataAnnotations.Schema.ColumnAttribute)) as System.ComponentModel.DataAnnotations.Schema.ColumnAttribute;
string name = ca?.Name ?? pi.Name;
if (pi == null) continue;
if (!actualNames.Contains(name)) { continue; }
object value = dr[name];
Type pt = pi.DeclaringType;
bool nullable = pt.GetTypeInfo().IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>);
if (value == DBNull.Value) { value = null; }
if (value == null && pt.GetTypeInfo().IsValueType && !nullable)
{ value = Activator.CreateInstance(pt); }
pi.SetValue(t, value);
}//for i
lst.Add(t);
}//while
return lst;
}//using dr
}
I have a repository class with the following code to update entities that have changed.
However I get an error
"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
However I cant see that the context has changed since it is a property of my repository.
Inside the repository I have
protected void InnerUpdate(params T[] items)
{
DbSet<T> dbSet = ((DbContext)this.context).Set<T>();
foreach (T item in items)
{
object id1 = item.GetProperty("Id");
T originalEntity = dbSet.Find(id1);
((DbContext)this.context).Entry(originalEntity).CurrentValues.SetValues(item);
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
navProp.SetValue(originalEntity, navProp.GetValue(item));
}
}
}
this.context.SaveChanges(); // Error occurs here
}
public List<PropertyInfo> GetNavigationProperties(T entity )
{
var t = entity.GetType();
ObjectContext objectContex = ((IObjectContextAdapter)((DbContext)this.context)).ObjectContext;
var elementType = objectContex.CreateObjectSet<T>().EntitySet.ElementType;
var properties = new List<PropertyInfo>();
var entityType = entity.GetType();
foreach (var navigationProperty in elementType.NavigationProperties)
{
properties.Add(entityType.GetProperty(navigationProperty.Name));
}
return properties;
}
I wonder if the problem is due to calling CreateObjectSet ? Is there a different way to do this?
I am using EF6.1
I changed the code as follows and got it working
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
var newval = (LoggedEntity) navProp.GetValue(item);
object entity = null;
if (newval != null)
{
var tp = navProp.PropertyType;
var entities = ((DbContext)this.context).Set(tp);
entity = entities.Find(newval.Id);
}
navProp.SetValue(originalEntity, entity);
}
In EF4, I have a small object map and the volume of data is also small. So for queries I want to eager load all associated data. Is there any single method call that can do the job, like "IQueryable.IncludeEverything()", rather than call Include() repeatedly with hardcoded property names?
There's nothing out of the box, but you could use MetadataWorkspace to implement it:
public static IQueryable<T> IncludeEverything<T>(this IQueryable<T> query, ObjectContext context)
where T : class
{
var ospaceEntityType = context.MetadataWorkspace.GetItem<EntityType>(
typeof(T).FullName, DataSpace.OSpace);
var cspaceEntityType = context.MetadataWorkspace.GetEdmSpaceType(ospaceEntityType);
var includedTypes = new HashSet<EdmType>();
includedTypes.Add(cspaceEntityType);
return IncludeEverything(query, cspaceEntityType as EntityType, "", includedTypes);
}
private static IQueryable<T> IncludeEverything<T>(IQueryable<T> query,
EntityType entity,
string path,
HashSet<EdmType> includedTypes)
where T : class
{
foreach (var navigationProperty in entity.NavigationProperties)
{
var propertyEdmType = navigationProperty.TypeUsage.EdmType;
if (includedTypes.Contains(propertyEdmType))
{
continue;
}
includedTypes.Add(propertyEdmType);
var propertyCollectionType = propertyEdmType as CollectionType;
EntityType propertyEntityType;
if (propertyCollectionType != null)
{
propertyEntityType = propertyCollectionType.TypeUsage.EdmType as EntityType;
} else
{
propertyEntityType = propertyEdmType as EntityType;
}
var propertyPath = string.IsNullOrEmpty(path) ? "" : path + ".";
propertyPath += navigationProperty.Name;
query = query.Include(propertyPath);
query = IncludeEverything(query, propertyEntityType, propertyPath, includedTypes);
}
return query;
}
Note that this code is just for illustration. It doesn't have parameter validation, it may include the same enities several times and it won't include all the related entities if there are cycles in your model.
I have a scenario where I am using Entity Framework in a WCF service, and changes happen on a non-tracked instance of a type that is mapped back to the database via code-first (non-trivial updates and deletes throughout the instance's object tree). When I try to attach the non-tracked instance into the context, EF is only recognizing changes to the simple value types on the root object.
Does anyone know of an elegant solution for this scenario? I am looking for a way to do this by using a generic repository, and avoiding having to run through the instance's entire object tree managing the "attach/detach" state of every object. I have considered possibly using ValueInjecter or AutoMapper to run the changes on a fully hydrated and tracked instance of the "old" state in order for the context to pickup the changes. Also, how would Nhibernate handle this situation?
Thanks in advance for your input!
UPDATE (7/31/2012): I have updated the code to handle genericly-typed keys, and some typing issues with EF Proxies. Also added some helper extensions when dealing with IEntity types. This implementation isn't perfect, but it is very functional.
UPDATE (3/13/2012): I have added a feature request for cleaner merging in EF. The request is located here: http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking
UPDATE (3/12/2012): I have posted my solution below. It uses FubuCore, ValueInjecter, and requires entities to be marked with one of two interfaces, either IEntity, or IRecursiveEntity for recursive classes. The solution will handle recursive, self-linked entities.
Also, I am referencing a generic repository (Repository) that allows me to get a reference to the IDbSet that EF exposes. This could be substituded with any other generic or specific repository. Lastly, the IEntity interface uses an int? id, however you could define that however you want (Guid/Guid?). The solution itself isn't quite as elegant as I would like, however it allows for much more elegant data access code when behind a physical WCF service boundary.
public class DomainMergeInjection : ConventionInjection
{
private readonly Repository _repository;
private readonly Dictionary<string, object> _potentialParentObjectDump;
private readonly Cache<Type, Type> _entityTypesAndKeysCache;
public DomainMergeInjection(Repository repository)
{
_repository = repository;
_potentialParentObjectDump = new Dictionary<string, object>();
_entityTypesAndKeysCache = new Cache<Type, Type>();
}
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if(c.SourceProp.Value == null)
return null;
//for value types and string just return the value as is
if(c.SourceProp.Type.IsSimple())
return c.SourceProp.Value;
//TODO: Expand on this to handle IList/IEnumerable (i.e. the non-generic collections and arrays).
//handle arrays
if(c.SourceProp.Type.IsArray)
{
var sourceArray = c.SourceProp.Value as Array;
// ReSharper disable PossibleNullReferenceException
var clonedArray = sourceArray.Clone() as Array;
// ReSharper restore PossibleNullReferenceException
for(int index = 0; index < sourceArray.Length; index++)
{
var sourceValueAtIndex = sourceArray.GetValue(index);
//Skip null and simple values that would have already been moved in the clone.
if(sourceValueAtIndex == null || sourceValueAtIndex.GetType().IsSimple())
continue;
// ReSharper disable PossibleNullReferenceException
clonedArray.SetValue(RetrieveComplexSourceValue(sourceValueAtIndex), index);
// ReSharper restore PossibleNullReferenceException
}
return clonedArray;
}
//handle IEnumerable<> also ICollection<> IList<> List<>
if(c.SourceProp.Type.IsGenericEnumerable())
{
var t = c.SourceProp.Type.GetGenericArguments()[0];
if(t.IsSimple())
return c.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
dynamic list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach(var sourceItem in (IEnumerable)c.SourceProp.Value)
{
addMethod.Invoke(list, new[] { RetrieveComplexSourceValue(sourceItem) });
}
return list;
}
//Get a source value that is in the right state and is tracked if needed.
var itemStateToInject = RetrieveComplexSourceValue(c.SourceProp.Value);
return itemStateToInject;
}
private object RetrieveComplexSourceValue(object source)
{
//If the source is a non-tracked type, or the source is a new value, then return its value.
if(!source.ImplementsIEntity(_entityTypesAndKeysCache) || source.IsEntityIdNull(_entityTypesAndKeysCache))
return source;
object sourceItemFromContext;
//Handle recursive entities, this could probably be cleaned up.
if(source.ImplementsIRecursiveEntity())
{
var itemKey = source.GetEntityIdString(_entityTypesAndKeysCache) + " " + ObjectContext.GetObjectType(source.GetType());
//If we have a context item for this key already, just return it. This solves a recursion problem with self-linking items.
if(_potentialParentObjectDump.ContainsKey(itemKey))
return _potentialParentObjectDump[itemKey];
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Add the class into the object dump in order to avoid any infinite recursion issues with self-linked objects
_potentialParentObjectDump.Add(itemKey, sourceItemFromContext);
}
else
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Recursively use this injection class instance to inject the source state on to the context source state.
var itemStateToInject = sourceItemFromContext.InjectFrom(this, source);
return itemStateToInject;
}
private object GetSourceItemFromContext(object source)
{
if(source == null)
return null;
//Using dynamic here to "AutoCast" to an IEntity<>. We should have one, but it's important to note just in case.
dynamic sourceEntityValue = source;
var sourceEntityType = ObjectContext.GetObjectType(source.GetType());
var sourceKeyType = sourceEntityType.GetEntityKeyType();
var method = typeof(DomainMergeInjection).GetMethod("GetFromContext", BindingFlags.Instance | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(sourceEntityType, sourceKeyType);
var sourceItemFromContext = generic.Invoke(this, new object[] { new object[] { sourceEntityValue.Id } });
return sourceItemFromContext;
}
// ReSharper disable UnusedMember.Local
private TItem GetFromContext<TItem, TKey>(object[] keys) where TItem : class, IEntity<TKey>
// ReSharper restore UnusedMember.Local
{
var foundItem = _repository.GetDbSet<TItem>().Find(keys);
return foundItem;
}
}
public static class EntityTypeExtensions
{
/// <summary>
/// Determines if an object instance implements IEntity.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache">A cache to hold types that do implement IEntity. If the cache does not have the Type and the Type does implement IEntity, it will add the type to the cache along with the </param>
/// <returns></returns>
public static bool ImplementsIEntity(this object entity, Cache<Type, Type> entityCache = null)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityCache != null && entityCache.Has(entityType))
return true;
var implementationOfIEntity = entityType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntity<>));
if(implementationOfIEntity == null)
return false;
if(entityCache != null)
{
var keyType = implementationOfIEntity.GetGenericArguments()[0];
entityCache.Fill(entityType, keyType);
}
return true;
}
/// <summary>
/// Determines if an object instances implements IRecurisveEntity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static bool ImplementsIRecursiveEntity(this object entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
var implementsIRecursiveEntity = entityType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRecursiveEntity<>));
return implementsIRecursiveEntity;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static bool IsEntityIdNull(this object entity, Cache<Type, Type> entityCache = null)
{
bool isEntityIdNull = ExecuteEntityIdMethod<bool>("IsEntityIdNull", entity, entityCache);
return isEntityIdNull;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static string GetEntityIdString(this object entity, Cache<Type, Type> entityCache = null)
{
string entityIdString = ExecuteEntityIdMethod<string>("GetEntityIdString", entity, entityCache);
return entityIdString;
}
private static T ExecuteEntityIdMethod<T>(string methodName, object entityInstance, Cache<Type, Type> entityCache = null)
{
if(!entityInstance.ImplementsIEntity(entityCache))
throw new ArgumentException(string.Format("Parameter entity of type {0} does not implement IEntity<>, and so ist not executable for {1}!", entityInstance.GetType(), methodName));
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entityInstance.GetType());
var keyType = entityCache != null ? entityCache[entityType] : entityType.GetEntityKeyType();
var method = typeof(EntityTypeExtensions).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(keyType);
T returnValue = (T)generic.Invoke(null, new[] { entityInstance });
return returnValue;
}
private static string GetEntityIdString<TKey>(IEntity<TKey> entity)
{
var entityIdString = entity.Id.ToString();
return entityIdString;
}
private static bool IsEntityIdNull<TKey>(IEntity<TKey> entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityType.IsPrimitive)
return false;
//NOTE: We know that this entity's type is NOT primitive, therefore we can cleanly test for null, and return properly.
// ReSharper disable CompareNonConstrainedGenericWithNull
var entityIdIsNull = entity.Id == null;
// ReSharper restore CompareNonConstrainedGenericWithNull
return entityIdIsNull;
}
public static Type GetEntityKeyType(this Type typeImplementingIEntity)
{
var implementationOfIEntity = typeImplementingIEntity.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>));
if(implementationOfIEntity == null)
throw new ArgumentException(string.Format("Type {0} does not implement IEntity<>", typeImplementingIEntity));
var keyType = implementationOfIEntity.GetGenericArguments()[0];
return keyType;
}
}
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
public interface IRecursiveEntity<TKey> : IEntity<TKey>
{
IRecursiveEntity<TKey> Parent { get; }
IEnumerable<IRecursiveEntity<TKey>> Children { get; }
}
you could use the detached object only as a DTO,
and after refill the object from context with values from the DTO
with ValueInjecter this would be:
//manually
conObj.InjectFrom(dto);
conObj.RefTypeProp.InjectFrom(dto.RefTypeProp);
...
//or by writing a custom injection:
conObj.InjectFrom<ApplyChangesInjection>(dto);
here's the Injection that will do that automatically, (I did it by modifying a bit the DeepClone Injection from VI's home page)
the trick here is that the Injection uses itself in the SetValue method
public class ApplyChangesInjection : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if (c.SourceProp.Value == null) return null;
//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
return c.SourceProp.Value;
//handle arrays - not impl
//handle IEnumerable<> also ICollection<> IList<> List<> - not impl
//for simple object types apply the inject using the corresponding source
return c.TargetProp.Value
.InjectFrom<ApplyChangesInjection>(c.SourceProp.Value);
}
}
//Note: I'm not handling collections in this injection, I just wanted you to understand the principle,
you can look at the original http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home
I've got this function
public static AdoEntity.Inspector GetInspectorWithInclude(int id, List<string> properties)
{
using (var context = new Inspection09Entities())
{
var query = context.Inspector;
if (properties != null)
{
foreach (var prop in properties)
{
if (!string.IsNullOrEmpty(prop))
query.Include(prop);
}
}
return query.Where(i => i.ID == id).First();
}
}
which i use to get my "Inspectors" from the DB and an additional feature to specify what to "Include" with the data. So it takes a List<'string'> and includes them with the query. This function doesn't seem to work because the returned object still does not include the requested data. Could someone tell me what is wrong with this method/approach.
Thanks in advance.
Solution
Thank you to Misha N. suggestion, I have hatched this EF helper which extends the ObjectQuery class. Hopefully others may find it useful.
/// <summary>
/// The include extesion that takes a list and returns a object query with the included data.
/// </summary>
/// <param name="objectQuery">
/// The object query.
/// </param>
/// <param name="includes">
/// The list of strings to include.
/// </param>
/// <typeparam name="T">
/// </typeparam>
/// <returns>
/// An object query of T type with the included data.
/// </returns>
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> objectQuery, List<string> includes)
{
ObjectQuery<T> query = objectQuery;
if (includes != null) includes.ForEach(s => { if (!string.IsNullOrEmpty(s)) query = query.Include(s); });
return query;
}
Usage example.
using(var context = new MyEntity())
{
var includes = new List<string>
{
"Address",
"Orders",
"Invoices"
}
return context.CustomerSet.Include(includes).First(c => c.ID == customerID);
}
Nothing is wrong with your approach, just one little thing need to be changed:
public static AdoEntity.Inspector GetInspectorWithInclude(int id, List<string> properties)
{
using (var context = new Inspection09Entities())
{
var query = context.Inspector;
if (properties != null)
{
foreach (var prop in properties)
{
if (!string.IsNullOrEmpty(prop))
query = query.Include(prop);// <--- HERE
}
}
return query.Where(i => i.ID == id).First();
}
}
ObjectQuery.Include() method is returning altered ObjectQuery object, you haven't been doing changes to the inital query.
Hope this helps