К сожалению, мой первоначальный план по заполнению свойств IsLoaded самосопровождаемыми объектами в методе HandleObjectMaterialized (вызываемом из события ObjectMaterialized) не сработал должным образом, так как множество для многих коллекций заполняется только после события (см. этот пост). И я хотел перебрать отношения в контексте для каждой сущности, которую он отслеживает, протестировать свойство IsLoaded и установить соответствующее свойство IsLoaded в моей сущности Self-tracking.
Поэтому вместо этого я создаю методы расширения для First () и ToList () с именами FirstWithLoaded () и ToListWithLoaded (), чтобы использовать отражение для этого как:
public static T FirstOrDefaultWithLoaded<T>(this IQueryable<T> source) where T : new()
{
T result = default(T);
if (source != null)
{
//Call the base FirstOrDefault
result = source.FirstOrDefault();
var querySource = source as ObjectQuery<T>;
if (querySource != null)
{
PopulateIsLoaded(result, querySource.Context);
}
}
return result;
}
private static void PopulateIsLoaded(object inputEntity, ObjectContext dataContext)
{
var entry = dataContext.ObjectStateManager.GetObjectStateEntry(inputEntity);
//var relationShipManagerProperty = entryType.GetProperty("RelationshipManager");//.GetValue(entityType, null);
var relationShipManager = GetPropertyValue(entry, "RelationshipManager");// relationShipManagerProperty.GetValue(entry, null);
if (relationShipManager != null)
{
//get the relationships (this is a sealed property)
var relationships = GetPropertyValue(relationShipManager, "Relationships") as IEnumerable<RelatedEnd>;
if (relationships != null)
{
foreach (RelatedEnd relationship in relationships)
{
//check to see whether the relationship is loaded
var isLoaded = GetRelatedEndPropertyValue(relationship, "IsLoaded");
if (isLoaded != null && (bool)isLoaded)
{
//if the relationship is loaded then set the
//<NavigationPropertyName>IsLoaded on entry to true
var navigationProperty = GetRelatedEndPropertyValue(relationship, "NavigationProperty");
var identity = GetPropertyValue(navigationProperty, "Identity");
//get the IsLoaded property on entry
var isLoadedProperty = entry.Entity.GetType().GetProperty(identity + "IsLoaded");
if (isLoadedProperty != null)
{
isLoadedProperty.SetValue(entry.Entity, true, null);
}
}
}
}
}
}
private static object GetPropertyValue(object inputObject, string propertyName)
{
object result = null;
if (inputObject != null)
{
var property = inputObject.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (property != null)
{
result = property.GetValue(inputObject, null);
}
}
return result;
}
private static object GetRelatedEndPropertyValue(RelatedEnd inputObject, string propertyName)
{
object result = null;
if (inputObject != null)
{
PropertyInfo property = null;
property = inputObject.GetType().GetProperty(propertyName, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (property != null)
{
result = property.GetValue(inputObject, null);
}
}
return result;
}
Это решение слегка разочаровывает тем, что мне пришлось получить доступ к запечатанному свойству "NavigationProperty", а затем NavigationProperty.Identity, чтобы получить правильную навигацию (например, Person.Addresses вместо Person.Address). Надеюсь, что в будущем появится что-то более элегантное.
Обратите внимание, чтобы это работало, я обновил свой шаблон Types T4, чтобы создать для меня свойства IsLoaded, например, для Person я создал свойство AddressesIsLoaded для адресов как:
<#
if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
{
#>
//The IsLoaded property for use on the client side when including collections
[DataMember]
<#=Accessibility.ForReadOnlyProperty(navProperty)#> bool <#=code.Escape(navProperty)#>IsLoaded
{
get; set;
}
<#
}
#>