/// <summary> /// Tries to get the value of a property and corresponding BindingPropertyInfo or ClientPropertyAnnotation if the property exists /// </summary> /// <param name="source">Source object whose property needs to be read</param> /// <param name="sourceProperty">Name of the source object property</param> /// <param name="model">The client model.</param> /// <param name="bindingPropertyInfo">BindingPropertyInfo corresponding to <paramref name="sourceProperty"/></param> /// <param name="clientProperty">Instance of ClientProperty corresponding to <paramref name="sourceProperty"/></param> /// <param name="propertyValue">Value of the property</param> /// <returns>true if the property exists and the value was read; otherwise false.</returns> internal static bool TryGetPropertyValue(object source, string sourceProperty, ClientEdmModel model, out BindingPropertyInfo bindingPropertyInfo, out ClientPropertyAnnotation clientProperty, out object propertyValue) { Type sourceType = source.GetType(); bindingPropertyInfo = BindingEntityInfo.GetObservableProperties(sourceType, model) .SingleOrDefault(x => x.PropertyInfo.PropertyName == sourceProperty); bool propertyFound = bindingPropertyInfo != null; // bindingPropertyInfo is null for primitive properties. if (!propertyFound) { clientProperty = BindingEntityInfo.GetClientType(sourceType, model) .GetProperty(sourceProperty, UndeclaredPropertyBehavior.Support); propertyFound = clientProperty != null; if (!propertyFound) { propertyValue = null; } else { propertyValue = clientProperty.GetValue(source); } } else { clientProperty = null; propertyValue = bindingPropertyInfo.PropertyInfo.GetValue(source); } return(propertyFound); }
/// <summary> /// Gets or creates a collection property on the specified <paramref name="instance"/>. /// </summary> /// <param name="instance">Instance on which to get/create the collection.</param> /// <param name="property">Collection property on the <paramref name="instance"/>.</param> /// <param name="forLoadProperty">Is this collection being created for LoadProperty scenario.</param> /// <returns> /// The collection corresponding to the specified <paramref name="property"/>; /// never null. /// </returns> private object GetOrCreateCollectionProperty(object instance, ClientPropertyAnnotation property, bool forLoadProperty) { Debug.Assert(instance != null, "instance != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(property.IsResourceSet, "property.IsEntityCollection has to be true - otherwise property isn't a collection"); // NOTE: in V1, we would have instantiated nested objects before setting them. object result; result = property.GetValue(instance); if (result == null) { Type collectionType = property.PropertyType; // For backward compatibility we need to have different strategy of collection creation b/w // LoadProperty scenario versus regular collection creation scenario. if (forLoadProperty) { if (BindingEntityInfo.IsDataServiceCollection(collectionType, this.MaterializerContext.Model)) { Debug.Assert(WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType) != null, "DataServiceCollection<> must be available here."); // new DataServiceCollection<property.EntityCollectionItemType>(null, TrackingMode.None) result = Activator.CreateInstance( WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType), null, TrackingMode.None); } else { // Try List<> first because that's what we did in V1/V2, but use the actual property type if it doesn't support List<> Type listCollectionType = typeof(List <>).MakeGenericType(property.EntityCollectionItemType); if (collectionType.IsAssignableFrom(listCollectionType)) { collectionType = listCollectionType; } result = Activator.CreateInstance(collectionType); } } else { if (DSClient.PlatformHelper.IsInterface(collectionType)) { collectionType = typeof(System.Collections.ObjectModel.Collection <>).MakeGenericType(property.EntityCollectionItemType); } result = this.CreateNewInstance(property.EdmProperty.Type, collectionType); } // Update the property value on the instance. property.SetValue(instance, result, property.PropertyName, false /* add */); } Debug.Assert(result != null, "result != null -- otherwise GetOrCreateCollectionProperty didn't fall back to creation"); return(result); }
/// <summary>Verifies the absence of observer for an DataServiceCollection</summary> /// <typeparam name="T">Type of DataServiceCollection</typeparam> /// <param name="oec">Non-typed collection object</param> /// <param name="sourceProperty">Collection property of the source object which is being assigned to</param> /// <param name="sourceType">Type of the source object</param> internal static void VerifyObserverNotPresent <T>(object oec, string sourceProperty, Type sourceType) #endif { #if DEBUG Debug.Assert(BindingEntityInfo.IsDataServiceCollection(oec.GetType(), model), "Must be an DataServiceCollection."); #endif DataServiceCollection <T> typedCollection = oec as DataServiceCollection <T>; if (typedCollection.Observer != null) { throw new InvalidOperationException(Strings.DataBinding_CollectionPropertySetterValueHasObserver(sourceProperty, sourceType)); } }
/// <summary> /// Determine if the specified type is an entity type. /// </summary> /// <param name="type">An object type specifier.</param> /// <param name="model">The client model.</param> /// <returns>true if the type is an entity type; otherwise false.</returns> internal static bool IsEntityType(Type type, ClientEdmModel model) { Debug.Assert(type != null, "Argument 'type' cannot be null."); metadataCacheLock.EnterReadLock(); try { if (knownNonEntityTypes.Contains(type)) { return(false); } } finally { metadataCacheLock.ExitReadLock(); } bool isEntityType; try { if (BindingEntityInfo.IsDataServiceCollection(type, model)) { return(false); } isEntityType = ClientTypeUtil.TypeOrElementTypeIsEntity(type); } catch (InvalidOperationException) { isEntityType = false; } if (!isEntityType) { metadataCacheLock.EnterWriteLock(); try { if (!knownNonEntityTypes.Contains(type)) { knownNonEntityTypes.Add(type); } } finally { metadataCacheLock.ExitWriteLock(); } } return(isEntityType); }
/// <summary>Initialize and start tracking an DataServiceCollection</summary> /// <param name="context">The context</param> /// <param name="items">Collection to initialize with</param> /// <param name="entitySet">The entity set of the elements in the collection.</param> /// <param name="entityChanged">delegate that needs to be called when an entity changes.</param> /// <param name="collectionChanged">delegate that needs to be called when an entity collection is changed.</param> private void StartTracking( DataServiceContext context, IEnumerable <T> items, String entitySet, Func <EntityChangedParams, bool> entityChanged, Func <EntityCollectionChangedParams, bool> collectionChanged) { Debug.Assert(context != null, "Must have a valid context to initialize."); Debug.Assert(this.observer == null, "Must have no observer which implies Initialize should only be called once."); context.UsingDataServiceCollection = true; // Verify that T corresponds to an entity type. // Validate here before any items are added to the collection because if this fails we want to prevent the collection from being populated. if (!BindingEntityInfo.IsEntityType(typeof(T), context.Model)) { throw new ArgumentException(Strings.DataBinding_DataServiceCollectionArgumentMustHaveEntityType(typeof(T))); } // Set up the observer first because we want the collection to know it's supposed to be tracked while the items are being loaded. // Items being added to the collection are not added to the binding graph until later when StartTracking is called on the observer. this.observer = new BindingObserver(context, entityChanged, collectionChanged); // Add everything from the input collection. if (items != null) { try { this.InternalLoadCollection(items); } catch { // If any failures occur, reset the observer since we couldn't successfully start tracking this.observer = null; throw; } } this.observer.StartTracking(this, entitySet); this.rootCollection = true; }
/// <summary> /// Get the entity set name for the target entity object. /// </summary> /// <param name="target">An entity object.</param> /// <param name="targetEntitySet">The 'currently known' entity set name for the target object.</param> /// <param name="model">The client model.</param> /// <returns>The entity set name for the target object.</returns> /// <remarks> /// Allow user code to provide the entity set name. If user code does not provide the entity set name, then /// this method will get the entity set name from the value of the EntitySetAttribute. /// The 'currently known' entity set name for top level collections can be provided through OEC constructor /// </remarks> internal static string GetEntitySet( object target, string targetEntitySet, ClientEdmModel model) { Debug.Assert(target != null, "Argument 'target' cannot be null."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType(), model), "Argument 'target' must be an entity type."); // Here's the rules in order of priority for resolving entity set name // 1. EntitySet name passed in the constructor or extension methods of DataServiceCollection // 2. EntitySet name specified in the EntitySet attribute by the code gen. {Remember this attribute is // not generated in case of MEST) if (!String.IsNullOrEmpty(targetEntitySet)) { return(targetEntitySet); } else { // If there is not a 'currently known' entity set name to validate against, then there must be // EntitySet attribute on the entity type return(BindingEntityInfo.GetEntitySetAttribute(target.GetType(), model)); } }
/// <summary>Obtain binding info corresponding to a given type</summary> /// <param name="entityType">Type for which to obtain information</param> /// <param name="model">The client model.</param> /// <returns>Info about the <paramref name="entityType"/></returns> private static BindingEntityInfoPerType GetBindingEntityInfoFor(Type entityType, ClientEdmModel model) { BindingEntityInfoPerType bindingEntityInfo; metadataCacheLock.EnterReadLock(); try { if (bindingEntityInfos.TryGetValue(entityType, out bindingEntityInfo)) { return(bindingEntityInfo); } } finally { metadataCacheLock.ExitReadLock(); } bindingEntityInfo = new BindingEntityInfoPerType(); // Try to get the entity set name from the EntitySetAttribute attributes. In order to make the // inheritance work, we need to look at the attributes declared in the base types also. // EntitySetAttribute does not allow multiples, so there can be at most 1 instance on the type. EntitySetAttribute entitySetAttribute = (EntitySetAttribute)entityType.GetCustomAttributes(typeof(EntitySetAttribute), true).SingleOrDefault(); // There must be exactly one (unambiguous) EntitySetAttribute attribute. bindingEntityInfo.EntitySet = entitySetAttribute != null ? entitySetAttribute.EntitySet : null; bindingEntityInfo.ClientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityType)); foreach (ClientPropertyAnnotation p in bindingEntityInfo.ClientType.Properties()) { BindingPropertyInfo bpi = null; Type propertyType = p.PropertyType; if (p.IsStreamLinkProperty) { // DataServiceStreamLink is not mutable externally // It implements INPC to notify controls about our updates // We should ignore its events since we are the only one updating it. continue; } else if (p.IsPrimitiveOrEnumOrComplexCollection) { Debug.Assert(!BindingEntityInfo.IsDataServiceCollection(propertyType, model), "DataServiceCollection cannot be the type that backs collections of primitives or complex types."); bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection }; } else if (p.IsEntityCollection) { if (BindingEntityInfo.IsDataServiceCollection(propertyType, model)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindDataServiceCollection }; } } else if (BindingEntityInfo.IsEntityType(propertyType, model)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindEntity }; } else if (BindingEntityInfo.CanBeComplexType(propertyType)) { // Add complex types and nothing else. Debug.Assert(!p.IsKnownType, "Known types do not implement INotifyPropertyChanged."); bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindComplex }; } if (bpi != null) { bpi.PropertyInfo = p; // For entity types, all of the above types of properties are interesting. // For complex types, only observe collections and complex type properties. if (bindingEntityInfo.ClientType.IsEntityType || bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex || bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection) { bindingEntityInfo.ObservableProperties.Add(bpi); } } } metadataCacheLock.EnterWriteLock(); try { if (!bindingEntityInfos.ContainsKey(entityType)) { bindingEntityInfos[entityType] = bindingEntityInfo; } } finally { metadataCacheLock.ExitWriteLock(); } return(bindingEntityInfo); }