Linq to SharePoint. Получение мета-данных списка
Одним из недостатков Linq to SharePoint является то, что метаданные списков (поля, типы содержимого и прочее) нельзя получить. При этом сами данные присутствуют, но только как internal. И в случае, когда необходимо проверить какие-нибудь свойства списка, приходится инициализировать объекты SPWeb и SPList. В этом посте я покажу как получать метаданные, не обращаясь напрямую к этим объектам.
Метаданные в Linq to SharePoint
Получить метаданные в Linq to SharePoint можно через свойство DataList объекта EntityList<TEntity>, который реализует интерфейс IBaseList. Вот диаграмма классов, которые мы будем получать из EntityList:
EntityList<TEntity>
Сам EntityList мы получаем, вызывая метод Microsoft.SharePoint.Linq.DataContext.GetList:
- [List(Name = "Companies")]
- public EntityList<Company> Companies
- {
- get
- {
- return GetList<Company>("Companies");
- }
- }
Это и будет нашей отправной точкой.
Свои классы метаданных
Так как метаданные мы будем получать, используя рефлексию, то нам понадобятся свои классы - аналоги internal-классов и перечислений Linq to SharePoint. Вот диаграмма классов-дубликатов:
При желании можно её дополнить.
Получение свойств объекта через рефлексию
Для удобства я написал простой метод-расширитель, который будет возвращать значение свойства объекта. В дальнейшем это повысит читабельность кода:
- /// <summary>
- /// Получение значения своства
- /// </summary>
- /// <typeparam name="T">Тип свойства</typeparam>
- /// <param name="obj">Объект</param>
- /// <param name="propName">Имя свойства</param>
- public static T GetPropertyValue<T>(this object obj, string propName)
- {
- // Получаем тип
- var type = obj.GetType();
- // Получаем свойство
- var prop = type.GetProperty(propName);
- // Получаем значение
- var val = prop.GetValue(obj, null);
- // Приводим к типу T или, если это невозможно,
- // возвращаем значение по умолчанию для типа T
- return val is T
- ? (T)val
- : default(T);
- }
Получение метаданных
Принцип получения метаданных из Linq to SharePoint следующий: мы берем internal-объект и производим маппинг его свойств на свойства нашего класса-аналога, за исключением свойств типы данных которых являются ссылочными. В отношении класса SPServerDataList это свойства ContentTypes и Fields. Их придется обрабатывать в "ручную". Все сказанное теперь можно представить в виде метода:
- /// <summary>
- /// Получение мета данных списка
- /// </summary>
- /// <param name="entityList">Список</param>
- public static EntityListMetaData GetMetaData<T>(EntityList<T> entityList) where T : ZhukDataItem
- {
- var res = new EntityListMetaData();
- // Получаем имена свойств для маппинга в дальнейшем
- var propNames = typeof(EntityListMetaData).GetProperties().Select(p => p.Name);
- var entityType = typeof(EntityList<T>);
- // Получаем приватное поле field
- var listField = entityType.GetField("list",
- BindingFlags.NonPublic | BindingFlags.Instance);
- // Получаем значение поля field
- var listValue = listField.GetValue(entityList);
- // Получаем тип этого поля (Microsoft.SharePoint.Linq.Provider.SPServerDataList)
- var listType = listValue.GetType();
- // Получаем свойства типа SPServerDataList
- var listProperties = listType.GetProperties();
- foreach (var listProperty in listProperties)
- {
- // Если поле отсутствует в нашем классе, то пропускаем его
- if (!propNames.Contains(listProperty.Name)) continue;
- // Получаем значение поля
- var listPropertyValue = listProperty.GetValue(listValue, null);
- // Свойство ContentTypes обрабатываем в "ручном" режиме
- if (listProperty.Name == "ContentTypes")
- {
- res.ContentTypes = new List<EntityListContentTypeInfo>();
- var ctypes = listPropertyValue as IEnumerable;
- if (ctypes != null)
- {
- // Перебираем типы содержимого
- foreach (var ctype in ctypes)
- {
- var ct = new EntityListContentTypeInfo
- {
- Id = ctype.GetPropertyValue<string>("Id"),
- Name = ctype.GetPropertyValue<string>("Name"),
- Description = ctype.GetPropertyValue<string>("Description"),
- Hidden = ctype.GetPropertyValue<bool>("Hidden")
- };
- res.ContentTypes.Add(ct);
- }
- }
- }
- // Свойство Fields обрабатываем в "ручном" режиме
- else if (listProperty.Name == "Fields")
- {
- res.Fields = new List<EntityListFieldInfo>();
- var fields = listPropertyValue as IEnumerable;
- if (fields != null)
- {
- foreach (var field in fields)
- {
- var ef = new EntityListFieldInfo
- {
- Id = field.GetPropertyValue<Guid>("Id"),
- Title = field.GetPropertyValue<string>("Title"),
- InternalName = field.GetPropertyValue<string>("InternalName"),
- FieldType = field.GetPropertyValue<EntityListFieldType>("FieldType"),
- //AllowMultipleValues = field.GetPropertyValue<bool>("AllowMultipleValues"),
- //Choices = field.GetPropertyValue<IEnumerable>("Choices"),
- //FillInChoice = field.GetPropertyValue<bool>("FillInChoice"),
- Hidden = field.GetPropertyValue<bool>("Hidden"),
- IsCalculated = field.GetPropertyValue<bool>("IsCalculated"),
- ReadOnlyField = field.GetPropertyValue<bool>("ReadOnlyField"),
- Required = field.GetPropertyValue<bool>("Required"),
- Description = field.GetPropertyValue<string>("Description"),
- //LookupDisplayColumn = field.GetPropertyValue<string>("LookupDisplayColumn"),
- //LookupList = field.GetPropertyValue<string>("LookupList"),
- //PrimaryFieldId = field.GetPropertyValue<string>("PrimaryFieldId")
- };
- res.Fields.Add(ef);
- }
- }
- }
- else
- {
- // Свойство текущего класса
- var property = typeof(EntityListMetaData).GetProperty(listProperty.Name);
- // Задаем полученное значение
- property.SetValue(res, listPropertyValue, null);
- }
- }
- // возвращаем результат
- return res;
- }
При инициализации объекта EntityListFieldInfo некоторые строки кода закоментированны, т.к. получение этих свойств возможно только для соответствующих типов полей. Надо добавить логику, связанную с проверкой значения FieldType.
Результат
В результате мы получаем метаданные списка без явной инициализации объектов SPSite, SPWeb и SPList. К тому же Linq to SharePoint кэширует эти данные при первом вызове метода GetList.
Применение
Использовать этот функционал, я считаю, лучше всего на уровне репозитария. В одном из ближайших постов я расскажу о своем опыте построения репозитариев для работы с данными списков/библиотек документов SharePoint, используя технологию Linq to SharePoint.