private static EntityFieldInfo EnsureClassField(Type type, PropertyInfo property, EntityInfo classInfo) { var classField = classInfo.Fields.FirstOrDefault(p => p.Name.Equals(property.Name, StringComparison.InvariantCultureIgnoreCase)); if (classField == null) { classField = new EntityFieldInfo() { Name = property.Name, DataType = property.PropertyType, PropertyInfo = type.GetProperty(property.Name), }; classInfo.Fields.Add(classField); } if (classInfo.SharePointTargets.Any() && string.IsNullOrEmpty(classField.SharePointName)) { // This type can be loaded via SharePoint REST, so ensure the SharePoint field is populated classField.SharePointName = property.Name; } return(classField); }
/// <summary> /// Translates model type into a set of classes that are used to drive CRUD operations /// </summary> /// <param name="type">The reference model type</param> /// <returns>Entity model class describing this model instance</returns> internal EntityInfo GetStaticClassInfo(Type type) { type = GetEntityConcreteType(type); // Check if we can deliver this entity from cache if (entityCache.TryGetValue(type, out EntityInfo entityInfoFromCache)) { return(entityInfoFromCache); } else { // Load and process type attributes var sharePointTypeAttributes = type.GetCustomAttributes <SharePointTypeAttribute>(false); var graphTypeAttributes = type.GetCustomAttributes <GraphTypeAttribute>(false); if (sharePointTypeAttributes.Any() || graphTypeAttributes.Any()) { EntityInfo classInfo = new EntityInfo { UseOverflowField = type.ImplementsInterface(typeof(IExpandoDataModel)) }; if (sharePointTypeAttributes.Any()) { foreach (var sharePointTypeAttribute in sharePointTypeAttributes) { var sharePointTargetToAdd = new EntitySharePointTypeInfo { Type = sharePointTypeAttribute.Type, Target = sharePointTypeAttribute.Target ?? type, Uri = sharePointTypeAttribute.Uri, Get = !string.IsNullOrEmpty(sharePointTypeAttribute.Get) ? sharePointTypeAttribute.Get : sharePointTypeAttribute.Uri, LinqGet = !string.IsNullOrEmpty(sharePointTypeAttribute.LinqGet) ? sharePointTypeAttribute.LinqGet : sharePointTypeAttribute.Uri, OverflowProperty = sharePointTypeAttribute.OverflowProperty, Update = !string.IsNullOrEmpty(sharePointTypeAttribute.Update) ? sharePointTypeAttribute.Update : sharePointTypeAttribute.Uri, Delete = !string.IsNullOrEmpty(sharePointTypeAttribute.Delete) ? sharePointTypeAttribute.Delete : sharePointTypeAttribute.Uri, }; classInfo.SharePointTargets.Add(sharePointTargetToAdd); } } if (graphTypeAttributes.Any()) { foreach (var graphTypeAttribute in graphTypeAttributes) { var graphTargetToAdd = new EntityGraphTypeInfo { Target = graphTypeAttribute.Target ?? type, Id = !string.IsNullOrEmpty(graphTypeAttribute.Id) ? graphTypeAttribute.Id : "id", Get = !string.IsNullOrEmpty(graphTypeAttribute.Get) ? graphTypeAttribute.Get : graphTypeAttribute.Uri, LinqGet = !string.IsNullOrEmpty(graphTypeAttribute.LinqGet) ? graphTypeAttribute.LinqGet : graphTypeAttribute.Uri, OverflowProperty = graphTypeAttribute.OverflowProperty, Update = !string.IsNullOrEmpty(graphTypeAttribute.Update) ? graphTypeAttribute.Update : graphTypeAttribute.Uri, Delete = !string.IsNullOrEmpty(graphTypeAttribute.Delete) ? graphTypeAttribute.Delete : graphTypeAttribute.Uri, Beta = graphTypeAttribute.Beta, }; classInfo.GraphTargets.Add(graphTargetToAdd); } } string keyPropertyName = null; foreach (var property in type.GetProperties()) { EntityFieldInfo classField = null; var propertyAttributes = property.GetCustomAttributes(); bool skipField = false; foreach (var attribute in propertyAttributes) { switch (attribute) { // Field metadata case SharePointPropertyAttribute sharePointPropertyAttribute: { classField = EnsureClassField(type, property, classInfo); classField.SharePointName = !string.IsNullOrEmpty(sharePointPropertyAttribute.FieldName) ? sharePointPropertyAttribute.FieldName : property.Name; classField.ExpandableByDefault = sharePointPropertyAttribute.ExpandByDefault; classField.SharePointUseCustomMapping = sharePointPropertyAttribute.UseCustomMapping; classField.SharePointJsonPath = sharePointPropertyAttribute.JsonPath; break; } case GraphPropertyAttribute graphPropertyAttribute: { classField = EnsureClassField(type, property, classInfo); classField.GraphName = !string.IsNullOrEmpty(graphPropertyAttribute.FieldName) ? graphPropertyAttribute.FieldName : ToCamelCase(property.Name); classField.GraphExpandable = graphPropertyAttribute.Expandable; classField.ExpandableByDefault = graphPropertyAttribute.ExpandByDefault; classField.GraphUseCustomMapping = graphPropertyAttribute.UseCustomMapping; classField.GraphJsonPath = graphPropertyAttribute.JsonPath; classField.GraphGet = graphPropertyAttribute.Get; classField.GraphBeta = graphPropertyAttribute.Beta; break; } case KeyPropertyAttribute keyPropertyAttribute: { keyPropertyName = keyPropertyAttribute.KeyPropertyName; skipField = true; break; } case SystemPropertyAttribute systemPropertyAttribute: { skipField = true; break; } } } if (!skipField) { if (classField == null) { classField = EnsureClassField(type, property, classInfo); // Property was not decorated with attributes if (!classInfo.SharePointTargets.Any()) { // This is a Graph only property classField.GraphName = ToCamelCase(property.Name); } else { // This is SharePoint/Graph property, we're not setting the GraphName here because in "mixed" objects the Graph properties must be explicitely marked with the GraphProperty attribute classField.SharePointName = property.Name; } } // Automatically determine "expand" value for SharePoint properties if (!string.IsNullOrEmpty(classField.SharePointName)) { if (JsonMappingHelper.IsModelCollection(classField.PropertyInfo.PropertyType) || JsonMappingHelper.IsModelType(classField.PropertyInfo.PropertyType)) { classField.SharePointExpandable = true; } } } } // Find the property set as key field and mark it as such if (!string.IsNullOrEmpty(keyPropertyName)) { // Store the actual key property name classInfo.ActualKeyFieldName = keyPropertyName; // Process the SharePoint and Graph ID fields var keyProperty = classInfo.Fields.FirstOrDefault(p => p.Name == keyPropertyName); if (keyProperty != null) { if (classInfo.SharePointTargets.Any()) { keyProperty.IsSharePointKey = true; } keyProperty.IsGraphKey = true; // If a property is defined as graph key then ensure the GraphName is correctly set if (string.IsNullOrEmpty(keyProperty.GraphName) && classInfo.GraphTargets.Any()) { keyProperty.GraphName = ToCamelCase(keyProperty.Name); } } } // Update the field used for overflow, this field was added (as it's part of the ExpandoBaseDataModel base data class), // But since there's no field to set properties on the fieldname property comes from the class mapping attribute if (classInfo.UseOverflowField) { var overflowField = classInfo.Fields.FirstOrDefault(p => p.Name == ExpandoBaseDataModel <IExpandoDataModel> .OverflowFieldName); if (!classInfo.SharePointTargets.Any()) { // This is a Graph only property overflowField.GraphName = classInfo.GraphTargets.First().OverflowProperty; } else { // This is SharePoint/Graph property overflowField.SharePointName = classInfo.SharePointTargets.First().OverflowProperty; overflowField.GraphName = classInfo.GraphTargets.FirstOrDefault()?.OverflowProperty; } } // Add to our cache to speed up future retrievals entityCache.TryAdd(type, classInfo); return(classInfo); } else { throw new ClientException(ErrorType.ModelMetadataIncorrect, PnPCoreResources.Exception_ModelMetadataIncorrect_MissingClassMapping); } } }