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:

  1. [List(Name = "Companies")]
  2. public EntityList<Company> Companies
  3. {
  4.     get
  5.     {
  6.         return GetList<Company>("Companies");
  7.     }
  8. }

Это и будет нашей отправной точкой.

Свои классы метаданных

Так как метаданные мы будем получать, используя рефлексию, то нам понадобятся свои классы - аналоги internal-классов и перечислений Linq to SharePoint. Вот диаграмма классов-дубликатов:

При желании можно её дополнить.

Получение свойств объекта через рефлексию

Для удобства я написал простой метод-расширитель, который будет возвращать значение свойства объекта. В дальнейшем это повысит читабельность кода:

  1. /// <summary>
  2. /// Получение значения своства
  3. /// </summary>
  4. /// <typeparam name="T">Тип свойства</typeparam>
  5. /// <param name="obj">Объект</param>
  6. /// <param name="propName">Имя свойства</param>
  7. public static T GetPropertyValue<T>(this object obj, string propName)
  8. {
  9.     // Получаем тип
  10.     var type = obj.GetType();
  11.     // Получаем свойство
  12.     var prop = type.GetProperty(propName);
  13.     // Получаем значение
  14.     var val = prop.GetValue(obj, null);
  15.     // Приводим к типу T или, если это невозможно,
  16.     // возвращаем значение по умолчанию для типа T
  17.     return val is T
  18.         ? (T)val
  19.         : default(T);
  20. }

Получение метаданных

Принцип получения метаданных из Linq to SharePoint следующий: мы берем internal-объект и производим маппинг его свойств на свойства нашего класса-аналога, за исключением свойств типы данных которых являются ссылочными. В отношении класса SPServerDataList это свойства ContentTypes и Fields. Их придется обрабатывать в "ручную". Все сказанное теперь можно представить в виде метода:

  1. /// <summary>
  2. /// Получение мета данных списка
  3. /// </summary>
  4. /// <param name="entityList">Список</param>
  5. public static EntityListMetaData GetMetaData<T>(EntityList<T> entityList) where T : ZhukDataItem
  6. {
  7.     var res = new EntityListMetaData();
  8.     // Получаем имена свойств для маппинга в дальнейшем
  9.     var propNames = typeof(EntityListMetaData).GetProperties().Select(p => p.Name);
  10.     var entityType = typeof(EntityList<T>);
  11.     // Получаем приватное поле field
  12.     var listField = entityType.GetField("list"
  13.         BindingFlags.NonPublic | BindingFlags.Instance);
  14.     // Получаем значение поля field
  15.     var listValue = listField.GetValue(entityList);
  16.     // Получаем тип этого поля (Microsoft.SharePoint.Linq.Provider.SPServerDataList)
  17.     var listType = listValue.GetType();
  18.     // Получаем свойства типа SPServerDataList
  19.     var listProperties = listType.GetProperties();
  20.     foreach (var listProperty in listProperties)
  21.     {
  22.         // Если поле отсутствует в нашем классе, то пропускаем его
  23.         if (!propNames.Contains(listProperty.Name)) continue;
  24.         // Получаем значение поля
  25.         var listPropertyValue = listProperty.GetValue(listValue, null);
  26.         // Свойство ContentTypes обрабатываем в "ручном" режиме
  27.         if (listProperty.Name == "ContentTypes")
  28.         {
  29.             res.ContentTypes = new List<EntityListContentTypeInfo>();
  30.             var ctypes = listPropertyValue as IEnumerable;
  31.             if (ctypes != null)
  32.             {
  33.                 // Перебираем типы содержимого
  34.                 foreach (var ctype in ctypes)
  35.                 {
  36.                     var ct = new EntityListContentTypeInfo
  37.                     {
  38.                         Id = ctype.GetPropertyValue<string>("Id"),
  39.                         Name = ctype.GetPropertyValue<string>("Name"),
  40.                         Description = ctype.GetPropertyValue<string>("Description"),
  41.                         Hidden = ctype.GetPropertyValue<bool>("Hidden")
  42.                     };
  43.                     res.ContentTypes.Add(ct);
  44.                 }
  45.             }
  46.         }
  47.         // Свойство Fields обрабатываем в "ручном" режиме
  48.         else if (listProperty.Name == "Fields")
  49.         {
  50.             res.Fields = new List<EntityListFieldInfo>();
  51.             var fields = listPropertyValue as IEnumerable;
  52.             if (fields != null)
  53.             {
  54.                 foreach (var field in fields)
  55.                 {
  56.                     var ef = new EntityListFieldInfo
  57.                     {
  58.                         Id = field.GetPropertyValue<Guid>("Id"),
  59.                         Title = field.GetPropertyValue<string>("Title"),
  60.                         InternalName = field.GetPropertyValue<string>("InternalName"),
  61.                         FieldType = field.GetPropertyValue<EntityListFieldType>("FieldType"),
  62.                         //AllowMultipleValues = field.GetPropertyValue<bool>("AllowMultipleValues"),
  63.                         //Choices = field.GetPropertyValue<IEnumerable>("Choices"),
  64.                         //FillInChoice = field.GetPropertyValue<bool>("FillInChoice"),
  65.                         Hidden = field.GetPropertyValue<bool>("Hidden"),
  66.                         IsCalculated = field.GetPropertyValue<bool>("IsCalculated"),
  67.                         ReadOnlyField = field.GetPropertyValue<bool>("ReadOnlyField"),
  68.                         Required = field.GetPropertyValue<bool>("Required"),
  69.                         Description = field.GetPropertyValue<string>("Description"),
  70.                         //LookupDisplayColumn = field.GetPropertyValue<string>("LookupDisplayColumn"),
  71.                         //LookupList = field.GetPropertyValue<string>("LookupList"),
  72.                         //PrimaryFieldId = field.GetPropertyValue<string>("PrimaryFieldId")
  73.                     };
  74.                     res.Fields.Add(ef);
  75.                 }
  76.             }
  77.         }
  78.         else
  79.         {
  80.             // Свойство текущего класса
  81.             var property = typeof(EntityListMetaData).GetProperty(listProperty.Name);
  82.             // Задаем полученное значение
  83.             property.SetValue(res, listPropertyValue, null);
  84.         }
  85.     }
  86.     // возвращаем результат
  87.     return res;
  88. }

При инициализации объекта EntityListFieldInfo некоторые строки кода закоментированны, т.к. получение этих свойств возможно только для соответствующих типов полей. Надо добавить логику, связанную с проверкой значения FieldType.

Результат

В результате мы получаем метаданные списка без явной инициализации объектов SPSite, SPWeb и SPList. К тому же Linq to SharePoint кэширует эти данные при первом вызове метода GetList.

Интернет-сайт на базе SharePoint 2010 для модераторов (wiki-страница)

Применение

Использовать этот функционал, я считаю, лучше всего на уровне репозитария. В одном из ближайших постов я расскажу о своем опыте построения репозитариев для работы с данными списков/библиотек документов SharePoint, используя технологию Linq to SharePoint.


Поделиться

Коментарии