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
Related
I'm working with EF 4.5 and DbContext. At business rules layer level, I should implement checks to avoid change entity value properties in some entity scenarios. Sample: StartProjecteDate should be readonly if ProjectIsStarted but not in other status.
I follow DRY principle, for this reason, I should be able to inspect readonly properties list from context and also from UI.
My question:
Is there a DataAnnotation validator that can dynamically set properties as readonly?
(and if not, is there a different / better solution to this problem?)
Notice than I'm working with Web Forms (and Telerik) architecture, a clean and elegant pattern will be welcome.
I'm trying to set and get at run time EditableAttribute as Jesse Webb explains, but I'm not able to get dataannotation attributes from property, my code:
<EditableAttribute(False)>
<MaxLength(400, ErrorMessage:="MÃ xim 400 caracters")>
Public Property NomInvertebrat As String
Edited Nov 8 2013 after digging docs, it seems that dataanottions if for class but for instance object itself. Perhaps an iReadonlyableProperties interface may be a way.
I have a class containing extension methods that lets me read data annotations like this:
int maxRefLen = ReflectionAPI.GetProperty<Organisation, String>(x => x.Name)
.GetAttribute<StringLengthAttribute>()
.GetValueOrDefault(x => x.MaximumLength, 256);
So if you use it you should be able to do get the value of the EditableAttribute like this:
bool isEditable = ReflectionAPI.GetProperty<Foo, String>(x => x.NomInvertebrat)
.GetAttribute<EditableAttribute>()
.GetValueOrDefault(x => x.AllowEdit, true);
As for setting the data annotations at run-time, I haven't done it myself but I have read that there is a solution here: Setting data-annotations at runtime
Getting a list of all data annotations of a particular type I think would entail reading the entity framework metadata. Again I haven't tried this.
If you add that together I personally think it feels clunky rather than elegant, but you have asked for a solution using DataAnnotations and something more elegant would probably mean getting into your architecture.
I would be inclined to do this:
public bool StartDateIsReadOnly
{
//use this property client-side to disable the input
get{ return Project.IsStarted;}
}
//Implement IValidatable object to do server side validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext
{
bool startdateIsChanged = // I'll leave you to work out this bit
var results = new List<ValidationResult>();
if(StartDateIsReadOnly && startdateIsChanged)
results.Add(new ValidationResult("Start Date cannot be changed after project is started");
}
Here is the ReflectionAPI class:
Please note that the class includes part of a hack that #JonSkeet posted and described as "evil". I personally think this bit ain't so bad, but you should read the following references:
Override a generic method for value types and reference types.
Evil code - overload resolution workaround
public static class ReflectionAPI
{
public static int GetValueOrDefault<TInput>(this TInput a, Func<TInput, int> func, int defaultValue)
where TInput : Attribute
//Have to restrict to struct or you get the error:
//The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
{
if (a == null)
return defaultValue;
return func(a);
}
public static Nullable<TResult> GetValueOrDefault<TInput, TResult>(this TInput a, Func<TInput, TResult> func, Nullable<TResult> defaultValue)
where TInput : Attribute
where TResult : struct
//Have to restrict to struct or you get the error:
//The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
{
if (a == null)
return defaultValue;
return func(a);
}
//In order to constrain to a class without interfering with the overload that has a generic struct constraint
//we need to add a parameter to the signature that is a reference type restricted to a class
public class ClassConstraintHack<T> where T : class { }
//The hack means we have an unused parameter in the signature
//http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/evil-code-overload-resolution-workaround.aspx
public static TResult GetValueOrDefault<TInput, TResult>(this TInput a, Func<TInput, TResult> func, TResult defaultValue, ClassConstraintHack<TResult> ignored = default(ClassConstraintHack<TResult>))
where TInput : Attribute
where TResult : class
{
if (a == null)
return defaultValue;
return func(a);
}
//I don't go so far as to use the inheritance trick decribed in the evil code overload resolution blog,
//just create some overloads that take nullable types - and I will just keep adding overloads for other nullable type
public static bool? GetValueOrDefault<TInput>(this TInput a, Func<TInput, bool?> func, bool? defaultValue)
where TInput : Attribute
{
if (a == null)
return defaultValue;
return func(a);
}
public static int? GetValueOrDefault<TInput>(this TInput a, Func<TInput, int?> func, int? defaultValue)
where TInput : Attribute
{
if (a == null)
return defaultValue;
return func(a);
}
public static T GetAttribute<T>(this PropertyInfo p) where T : Attribute
{
if (p == null)
return null;
return p.GetCustomAttributes(false).OfType<T>().LastOrDefault();
}
public static PropertyInfo GetProperty<T, R>(Expression<Func<T, R>> expression)
{
if (expression == null)
return null;
MemberExpression memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
return null;
return memberExpression.Member as PropertyInfo;
}
}
.NET allows you to dynamically change structure of Class by implementing System.ComponentModel.ICustomTypeDescriptor. Most serializers support this interface.
// Sample Serialization
foreach(PropertyDescriptor p in TypeDescriptor.GetProperties(obj)){
string name = p.PropertyName;
object value = p.GetValue(obj);
}
Internally TypeDescriptor uses Reflection, but the implementation allows us to override reflection attributes easily.
Here are three steps of implementation,
// Implement System.ComponentModel.ICustomTypeDescriptor Interface on
// your Entity
public class MyEntity: System.ComponentModel.ICustomTypeDescriptor
{
....
// most methods needs only call to default implementation as shown below
System.ComponentModel.AttributeCollection
System.ComponentModel.ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
string System.ComponentModel.ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
string System.ComponentModel.ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
System.ComponentModel.TypeConverter System.ComponentModel.ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
System.ComponentModel.EventDescriptor System.ComponentModel.ICustomTypeDescriptor.GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
System.ComponentModel.PropertyDescriptor System.ComponentModel.ICustomTypeDescriptor.GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
object System.ComponentModel.ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
System.ComponentModel.EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
System.ComponentModel.EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
System.ComponentModel.PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
return TypeDescriptor.GetProperties(this, attributes, true);
}
object System.ComponentModel.ICustomTypeDescriptor.GetPropertyOwner(System.ComponentModel.PropertyDescriptor pd)
{
return this;
}
// The Only method that needs different implementation is below
System.ComponentModel.PropertyDescriptorCollection
System.ComponentModel.ICustomTypeDescriptor.GetProperties()
{
// ... you are supposed to create new instance of
// PropertyDescriptorCollection with PropertyDescriptor
PropertyDescriptorCollection pdc = new PropertyDescriptorCollection();
foreach(PropertyDescriptor p in TypeDescriptor.GetProperties(this,true)){
// if readonly..
AtomPropertyDescriptor ap = new AtomPropertyDescriptor(p, p.Name);
// or
AtomPropertyDescriptor ap = new AtomPropertyDescriptor(p, p.Name,
true,
new XmlIgnoreAttribute(),
new ScriptIgnoreAttribute(),
new ReadOnlyAttribute());
pdc.Add(ap);
}
return pdc;
}
}
// And here is the AtomPropertyDescriptorClass
public class AtomPropertyDescriptor : PropertyDescriptor
{
PropertyDescriptor desc;
bool? readOnly = null;
public AtomPropertyDescriptor(PropertyDescriptor pd, string name,
bool? readOnly, params Attribute[] attrs) :
base(name, attrs)
{
desc = pd;
this.readOnly = readOnly;
}
public override bool CanResetValue(object component)
{
return desc.CanResetValue(component);
}
public override Type ComponentType
{
get
{
return desc.ComponentType;
}
}
public override object GetValue(object component)
{
return desc.GetValue(component);
}
public override bool IsReadOnly
{
get
{
if (readOnly.HasValue)
return readOnly.Value;
return desc.IsReadOnly;
}
}
public override Type PropertyType
{
get { return desc.PropertyType; }
}
public override void ResetValue(object component)
{
desc.ResetValue(component);
}
public override void SetValue(object component, object value)
{
desc.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return desc.ShouldSerializeValue(component);
}
}
I think what you are looking for is a custom Annotation Attribute like this:
<DisableEditAttribute(this.IsProjectStarted)>
Public Property NomInvertebrat As String
public override bool IsValid(bool value)
{
bool result = true;
// Add validation logic here.
if(value)
{
//Compare Current Value Against DB Value.
}
return result;
}
See MSDN: http://msdn.microsoft.com/en-us/library/cc668224(v=vs.98).aspx
I am tinkering with WebAPI to create a generic implementation for entity framework. I am able to implement most of the methods just fine, but am finding PUT to be tricky in non-trivial cases. The implementation most commonly found online works for simple entities:
[HttpPut]
[ActionName("Endpoint")]
public virtual T Put(T entity)
{
var db = GetDbContext();
var entry = db.Entry(entity);
entry.State = EntityState.Modified;
var set = db.Set<T>();
set.Attach(entity);
db.SaveChanges();
return entity;
}
...but does not delete or update child lists:
public class Invoice
{
...
public virtual InvoiceLineItem {get; set;} //Attach method doesn't address these
}
In an MVC Controller, you could simply use "UpdateModel" and it would add/update/delete children as needed, however that method is not available on ApiController. I understand that some code would be necessary to get the original item from the database, and that it would need to use Include to get the child lists, but can't quite figure out the best way to replicate UpdateModel's functionality:
[HttpPut]
[ActionName("Endpoint")]
public virtual T Put(T entity)
{
var db = GetDbContext();
var original = GetOriginalFor(entity);
//TODO: Something similar to UpdateModel(original), such as UpdateModel(original, entity);
db.SaveChanges();
return original;
}
How can I implement UpdateModel OR somehow implement Put in such a way that it will handle child lists?
The routine dont validate entity, but fill the pre-existent entity.
protected virtual void UpdateModel<T>(T original, bool overrideForEmptyList = true)
{
var json = ControllerContext.Request.Content.ReadAsStringAsync().Result;
UpdateModel<T>(json, original, overrideForEmptyList);
}
private void UpdateModel<T>(string json, T original, bool overrideForEmptyList = true)
{
var newValues = JsonConvert.DeserializeObject<Pessoa>(json);
foreach (var property in original.GetType().GetProperties())
{
var isEnumerable = property.PropertyType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (isEnumerable && property.PropertyType != typeof(string))
{
var propertyOriginalValue = property.GetValue(original, null);
if (propertyOriginalValue != null)
{
var propertyNewValue = property.GetValue(newValues, null);
if (propertyNewValue != null && (overrideForEmptyList || ((IEnumerable<object>)propertyNewValue).Any()))
{
property.SetValue(original, null);
}
}
}
}
JsonConvert.PopulateObject(json, original);
}
public void Post()
{
var sample = Pessoa.FindById(12);
UpdateModel(sample);
}
I have this method in my SurveyController class:
public ActionResult AddProperties(int id, int[] propertyids, int page = 1)
{
var survey = _uow.SurveyRepository.Find(id);
if (propertyids == null)
return GetPropertiesTable(survey, page);
var repo = _uow.PropertySurveyRepository;
propertyids.Select(propertyid => new PropertySurvey
{
//Setting the Property rather than the PropertyID
//prevents the error occurring later
//Property = _uow.PropertyRepository.Find(propertyid),
PropertyID = propertyid,
SurveyID = id
})
.ForEach(x => repo.InsertOrUpdate(x));
_uow.Save();
return GetPropertiesTable(survey, page);
}
The GetPropertiesTable redisplays Properties but PropertySurvey.Property is marked virtual and I have created the entity using the new operator, so a proxy to support lazy loading was never created and it is null when I access it. When we have access direct to the DbContext we can use the Create method to explicitly create the proxy. But I have a unit of work and repository pattern here. I guess I could expose the context.Create method via a repository.Create method and then I need to remember to use that instead of the new operator when I add an entity . But wouldn't it be better to encapsulate the problem in my InsertOrUpdate method? Is there some way to detect that the entity being added is not a proxy when it should be and substitute a proxy? This is my InsertOrUpdate method in my base repository class:
protected virtual void InsertOrUpdate(T e, int id)
{
if (id == default(int))
{
// New entity
context.Set<T>().Add(e);
}
else
{
// Existing entity
context.Entry(e).State = EntityState.Modified;
}
}
Based on the answer supplied by qujck. Here is how you can do it without having to employ automapper:
Edited to always check for proxy - not just during insert - as suggested in comments
Edited again to use a different way of checking whether a proxy was passed in to the method. The reason for changing the technique is that I ran into a problem when I introduced an entity that inherited from another. In that case an inherited entity can fail the entity.e.GetType().Equals(instance.GetType() check even if it is a proxy. I got the new technique from this answer
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = Context.Set<T>();
DbEntityEntry<T> entry;
if (e.GetType().BaseType != null
&& e.GetType().Namespace == "System.Data.Entity.DynamicProxies")
{
//The entity being added is already a proxy type that supports lazy
//loading - just get the context entry
entry = Context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Generate a proxy type to support lazy loading and attach it
T instance = dbSet.Create();
instance.ID = e.ID;
entry = Context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
public abstract class ModelBase
{
public int ID { get; set; }
}
I agree with you that this should be handled in one place and the best place to catch all looks to be your repository. You can compare the type of T with an instance created by the context and use something like Automapper to quickly transfer all of the values if the types do not match.
private bool mapCreated = false;
protected virtual void InsertOrUpdate(T e, int id)
{
T instance = context.Set<T>().Create();
if (e.GetType().Equals(instance.GetType()))
instance = e;
else
{
//this bit should really be managed somewhere else
if (!mapCreated)
{
Mapper.CreateMap(e.GetType(), instance.GetType());
mapCreated = true;
}
instance = Mapper.Map(e, instance);
}
if (id == default(int))
context.Set<T>().Add(instance);
else
context.Entry(instance).State = EntityState.Modified;
}
Like several other people, I'm having problems serializing Entity Framework objects, so that I can send the data over AJAX in a JSON format.
I've got the following server-side method, which I'm attempting to call using AJAX through jQuery
[WebMethod]
public static IEnumerable<Message> GetAllMessages(int officerId)
{
SIBSv2Entities db = new SIBSv2Entities();
return (from m in db.MessageRecipients
where m.OfficerId == officerId
select m.Message).AsEnumerable<Message>();
}
Calling this via AJAX results in this error:
A circular reference was detected while serializing an object of type \u0027System.Data.Metadata.Edm.AssociationType
Which is because of the way the Entity Framework creates circular references to keep all the objects related and accessible server side.
I came across the following code from (http://hellowebapps.com/2010-09-26/producing-json-from-entity-framework-4-0-generated-classes/) which claims to get around this problem by capping the maximum depth for references. I've added the code below, because I had to tweak it slightly to get it work (All angled brackets are missing from the code on the website)
using System.Web.Script.Serialization;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System;
public class EFObjectConverter : JavaScriptConverter
{
private int _currentDepth = 1;
private readonly int _maxDepth = 2;
private readonly List<int> _processedObjects = new List<int>();
private readonly Type[] _builtInTypes = new[]{
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(float),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
typeof(string),
typeof(DateTime),
typeof(Guid)
};
public EFObjectConverter( int maxDepth = 2,
EFObjectConverter parent = null)
{
_maxDepth = maxDepth;
if (parent != null)
{
_currentDepth += parent._currentDepth;
}
}
public override object Deserialize( IDictionary<string,object> dictionary, Type type, JavaScriptSerializer serializer)
{
return null;
}
public override IDictionary<string,object> Serialize(object obj, JavaScriptSerializer serializer)
{
_processedObjects.Add(obj.GetHashCode());
Type type = obj.GetType();
var properties = from p in type.GetProperties()
where p.CanWrite &&
p.CanWrite &&
_builtInTypes.Contains(p.PropertyType)
select p;
var result = properties.ToDictionary(
property => property.Name,
property => (Object)(property.GetValue(obj, null)
== null
? ""
: property.GetValue(obj, null).ToString().Trim())
);
if (_maxDepth >= _currentDepth)
{
var complexProperties = from p in type.GetProperties()
where p.CanWrite &&
p.CanRead &&
!_builtInTypes.Contains(p.PropertyType) &&
!_processedObjects.Contains(p.GetValue(obj, null)
== null
? 0
: p.GetValue(obj, null).GetHashCode())
select p;
foreach (var property in complexProperties)
{
var js = new JavaScriptSerializer();
js.RegisterConverters(new List<JavaScriptConverter> { new EFObjectConverter(_maxDepth - _currentDepth, this) });
result.Add(property.Name, js.Serialize(property.GetValue(obj, null)));
}
}
return result;
}
public override IEnumerable<System.Type> SupportedTypes
{
get
{
return GetType().Assembly.GetTypes();
}
}
}
However even when using that code, in the following way:
var js = new System.Web.Script.Serialization.JavaScriptSerializer();
js.RegisterConverters(new List<System.Web.Script.Serialization.JavaScriptConverter> { new EFObjectConverter(2) });
return js.Serialize(messages);
I'm still seeing the A circular reference was detected... exception being thrown!
I solved these issues with the following classes:
public class EFJavaScriptSerializer : JavaScriptSerializer
{
public EFJavaScriptSerializer()
{
RegisterConverters(new List<JavaScriptConverter>{new EFJavaScriptConverter()});
}
}
and
public class EFJavaScriptConverter : JavaScriptConverter
{
private int _currentDepth = 1;
private readonly int _maxDepth = 1;
private readonly List<object> _processedObjects = new List<object>();
private readonly Type[] _builtInTypes = new[]
{
typeof(int?),
typeof(double?),
typeof(bool?),
typeof(bool),
typeof(byte),
typeof(sbyte),
typeof(char),
typeof(decimal),
typeof(double),
typeof(float),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
typeof(string),
typeof(DateTime),
typeof(DateTime?),
typeof(Guid)
};
public EFJavaScriptConverter() : this(1, null) { }
public EFJavaScriptConverter(int maxDepth = 1, EFJavaScriptConverter parent = null)
{
_maxDepth = maxDepth;
if (parent != null)
{
_currentDepth += parent._currentDepth;
}
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
return null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
_processedObjects.Add(obj.GetHashCode());
var type = obj.GetType();
var properties = from p in type.GetProperties()
where p.CanRead && p.GetIndexParameters().Count() == 0 &&
_builtInTypes.Contains(p.PropertyType)
select p;
var result = properties.ToDictionary(
p => p.Name,
p => (Object)TryGetStringValue(p, obj));
if (_maxDepth >= _currentDepth)
{
var complexProperties = from p in type.GetProperties()
where p.CanRead &&
p.GetIndexParameters().Count() == 0 &&
!_builtInTypes.Contains(p.PropertyType) &&
p.Name != "RelationshipManager" &&
!AllreadyAdded(p, obj)
select p;
foreach (var property in complexProperties)
{
var complexValue = TryGetValue(property, obj);
if(complexValue != null)
{
var js = new EFJavaScriptConverter(_maxDepth - _currentDepth, this);
result.Add(property.Name, js.Serialize(complexValue, new EFJavaScriptSerializer()));
}
}
}
return result;
}
private bool AllreadyAdded(PropertyInfo p, object obj)
{
var val = TryGetValue(p, obj);
return _processedObjects.Contains(val == null ? 0 : val.GetHashCode());
}
private static object TryGetValue(PropertyInfo p, object obj)
{
var parameters = p.GetIndexParameters();
if (parameters.Length == 0)
{
return p.GetValue(obj, null);
}
else
{
//cant serialize these
return null;
}
}
private static object TryGetStringValue(PropertyInfo p, object obj)
{
if (p.GetIndexParameters().Length == 0)
{
var val = p.GetValue(obj, null);
return val;
}
else
{
return string.Empty;
}
}
public override IEnumerable<Type> SupportedTypes
{
get
{
var types = new List<Type>();
//ef types
types.AddRange(Assembly.GetAssembly(typeof(DbContext)).GetTypes());
//model types
types.AddRange(Assembly.GetAssembly(typeof(BaseViewModel)).GetTypes());
return types;
}
}
}
You can now safely make a call like new EFJavaScriptSerializer().Serialize(obj)
Update : since version Telerik v1.3+ you can now override the GridActionAttribute.CreateActionResult method and hence you can easily integrate this Serializer into specific controller methods by applying your custom [GridAction] attribute:
[Grid]
public ActionResult _GetOrders(int id)
{
return new GridModel(Service.GetOrders(id));
}
and
public class GridAttribute : GridActionAttribute, IActionFilter
{
/// <summary>
/// Determines the depth that the serializer will traverse
/// </summary>
public int SerializationDepth { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GridActionAttribute"/> class.
/// </summary>
public GridAttribute()
: base()
{
ActionParameterName = "command";
SerializationDepth = 1;
}
protected override ActionResult CreateActionResult(object model)
{
return new EFJsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxSerializationDepth = SerializationDepth
};
}
}
and finally..
public class EFJsonResult : JsonResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public EFJsonResult()
{
MaxJsonLength = 1024000000;
RecursionLimit = 10;
MaxSerializationDepth = 1;
}
public int MaxJsonLength { get; set; }
public int RecursionLimit { get; set; }
public int MaxSerializationDepth { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
var serializer = new JavaScriptSerializer
{
MaxJsonLength = MaxJsonLength,
RecursionLimit = RecursionLimit
};
serializer.RegisterConverters(new List<JavaScriptConverter> { new EFJsonConverter(MaxSerializationDepth) });
response.Write(serializer.Serialize(Data));
}
}
You can also detach the object from the context and it will remove the navigation properties so that it can be serialized. For my data repository classes that are used with Json i use something like this.
public DataModel.Page GetPage(Guid idPage, bool detach = false)
{
var results = from p in DataContext.Pages
where p.idPage == idPage
select p;
if (results.Count() == 0)
return null;
else
{
var result = results.First();
if (detach)
DataContext.Detach(result);
return result;
}
}
By default the returned object will have all of the complex/navigation properties, but by setting detach = true it will remove those properties and return the base object only. For a list of objects the implementation looks like this
public List<DataModel.Page> GetPageList(Guid idSite, bool detach = false)
{
var results = from p in DataContext.Pages
where p.idSite == idSite
select p;
if (results.Count() > 0)
{
if (detach)
{
List<DataModel.Page> retValue = new List<DataModel.Page>();
foreach (var result in results)
{
DataContext.Detach(result);
retValue.Add(result);
}
return retValue;
}
else
return results.ToList();
}
else
return new List<DataModel.Page>();
}
I have just successfully tested this code.
It may be that in your case your Message object is in a different assembly? The overriden Property SupportedTypes is returning everything ONLY in its own Assembly so when serialize is called the JavaScriptSerializer defaults to the standard JavaScriptConverter.
You should be able to verify this debugging.
Your error occured due to some "Reference" classes generated by EF for some entities with 1:1 relations and that the JavaScriptSerializer failed to serialize.
I've used a workaround by adding a new condition :
!p.Name.EndsWith("Reference")
The code to get the complex properties looks like this :
var complexProperties = from p in type.GetProperties()
where p.CanWrite &&
p.CanRead &&
!p.Name.EndsWith("Reference") &&
!_builtInTypes.Contains(p.PropertyType) &&
!_processedObjects.Contains(p.GetValue(obj, null)
== null
? 0
: p.GetValue(obj, null).GetHashCode())
select p;
Hope this help you.
I had a similar problem with pushing my view via Ajax to UI components.
I also found and tried to use that code sample you provided. Some problems I had with that code:
SupportedTypes wasn't grabbing the types I needed, so the converter wasn't being called
If the maximum depth is hit, the serialization would be truncated
It threw out any other converters I had on the existing serializer by creating its own new JavaScriptSerializer
Here are the fixes I implemented for those issues:
Reusing the same serializer
I simply reused the existing serializer that is passed into Serialize to solve this problem. This broke the depth hack though.
Truncating on already-visited, rather than on depth
Instead of truncating on depth, I created a HashSet<object> of already seen instances (with a custom IEqualityComparer that checked reference equality). I simply didn't recurse if I found an instance I'd already seen. This is the same detection mechanism built into the JavaScriptSerializer itself, so worked quite well.
The only problem with this solution is that the serialization output isn't very deterministic. The order of truncation is strongly dependent on the order that reflections finds the properties. You could solve this (with a perf hit) by sorting before recursing.
SupportedTypes needed the right types
My JavaScriptConverter couldn't live in the same assembly as my model. If you plan to reuse this converter code, you'll probably run into the same problem.
To solve this I had to pre-traverse the object tree, keeping a HashSet<Type> of already seen types (to avoid my own infinite recursion), and pass that to the JavaScriptConverter before registering it.
Looking back on my solution, I would now use code generation templates to create a list of the entity types. This would be much more foolproof (it uses simple iteration), and have much better perf since it would produce a list at compile time. I'd still pass this to the converter so it could be reused between models.
My final solution
I threw out that code and tried again :)
I simply wrote code to project onto new types ("ViewModel" types - in your case, it would be service contract types) before doing my serialization. The intention of my code was made more explicit, it allowed me to serialize just the data I wanted, and it didn't have the potential of slipping in queries on accident (e.g. serializing my whole DB).
My types were fairly simple, and I didn't need most of them for my view. I might look into AutoMapper to do some of this projection in the future.
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