Поделиться через


Расширение объектно-реляционного сопоставления

Дата последнего изменения: 12 марта 2010 г.

Применимо к: SharePoint Foundation 2010

Предоставляемое поставщиком LINQ to SharePoint объектно-реляционное сопоставление подходит не для всех возможных ситуаций, в которых может потребоваться доступ к базе данных контента Microsoft SharePoint Foundation в бизнес-логике с помощью LINQ. Ниже приведены ситуации, в которых может потребоваться расширить сопоставление.

  • SPMetal может создавать код только для полей типа контента. Не существует встроенного сопоставления со свойствами объектов SPListItem, такими как контейнер свойств Properties или свойство Attachments.

  • SPMetal не может создавать код для полей, использующих тип данных настраиваемого поля.

  • SPMetal не может предсказывать будущее, поэтому эта программа не может сопоставлять поля (столбцы), которые пользователи добавят в списки после развертывания решения.

  • Не всегда можно повторно запустить SPMetal при изменении структуры конечного списка и добавлении в него новых полей. Например, другие группы разработки могут менять код, сформированный SPMetal.

По этим причинам предоставляется интерфейс ICustomMapping, позволяющий расширить объектно-реляционное сопоставление.

Расширение решения LINQ to SharePoint

Ниже перечислены основные задачи для расширения объектно-реляционного сопоставления.

  • Получение свойств SPListItem или новых столбцов, сопоставленных новым свойствам класса типа контента.

  • Обработка конфликтов параллельного доступа с учетом новых столбцов.

Чтобы обеспечить простое выполнение этих задач, LINQ to SharePoint предоставляет интерфейс ICustomMapping. Класс, который реализует данный интерфейс, может сопоставлять новые столбцы с новыми свойствами. Он также может расширить систему разрешения конфликтов параллельного доступа для учета новых столбцов.

Сопоставление столбцов, использующих типы настраиваемых полей

В файле кода расширения начните с повторного объявления класса, представляющего тип контента списка, с новыми столбцами. Этот класс должен быть помечен как partial (Partial в Visual Basic). (Он также должен быть объявлен как partial в исходном файле кода, обычно создаваемом программой SPMetal. Программа SPMetal, которую рекомендуется использовать, автоматически помечает все создаваемые классы типов контента как partial.) Не повторяйте группы атрибутов из исходного объявления, но укажите, что данный класс реализует интерфейс ICustomMapping.

Внутри класса объявите три метода интерфейса. Ниже приведен пример, в котором тип контента называется "Book".

public partial class Book : ICustomMapping
{
    public void MapFrom(object listItem)
    {
    }

    public void MapTo(object listItem)
    {
    }

    public void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
    {
    }

    // New property declarations go here.

}

Дополните метод MapFrom(Object) атрибутом CustomMappingAttribute. С помощью этого атрибута создайте массив объектов String, содержащих внутренние имена столбцов, и назначьте этот массив свойству Columns.

ПримечаниеПримечание

Внутреннее имя столбца не может быть получено в пользовательском интерфейсе SharePoint Foundation. Его необходимо получить с помощью объектной модели посредством свойства InternalName.

В следующем примере показано, как CustomMappingAttribute используется для создания массива внутренних имен для двух новых столбцов, которые были добавлены в список Books.

  • ISBN — это столбец, который использует тип данных настраиваемого поля (называемый ISBNData), предназначенный для хранения ISBN-номера книги.

  • UPC-A — это столбец, использующий настраиваемый тип поля (называемый UPCAData), предназначенный для хранения структурированных данных, которые могут использоваться для создания штрих-кода книги типа UPC-A. Его внутренним именем является "UPCA".

[CustomMapping(Columns = new String[] { "ISBN", "UPCA" })]
public void MapFrom(object listItem)
{
}

Метод MapFrom(Object) используется LINQ to SharePoint для задания значений свойств для базы данных контента. Передаваемый в него параметр является объектом, который представляет элемент списка, полученный из базы данных контента. Этот метод должен иметь строку для каждого из новых столбцов, назначающую значение поля столбца свойству, которое необходимо использовать для представления этого столбца в расширении объектно-реляционное сопоставления.

Метод MapTo(Object) используется для сохранения значения в соответствующем поле базы данных контента. Передаваемый в него параметр является объектом, который представляет элемент списка, полученный из базы данных контента. Этот метод должен иметь строку для каждого из свойств, представляющих новый столбец, которая назначает значение свойства полю столбца в базе данных контента.

Важное примечаниеВажно!

Не вызывайте методы MapFrom(Object) или MapTo(Object) из своего кода.

В следующем примере показано, как реализовать эти методы.

[CustomMapping(Columns = new String[] { "ISBN", "UPCA" })]
public void MapFrom(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    this.ISBN = item["ISBN"];
    this.UPCA = item["UPCA"];
}

public void MapTo(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    item["ISBN"] = this.ISBN;
    item["UPCA"] = this.UPCA;
}

В любой из методов можно добавить любую другую требуемую логику, например логику проверки.

Сопоставление свойств SPListItem

Как правило, объектно-реляционное сопоставление следует расширить, если в коде LINQ to SharePoint требуется получить доступ к свойствам объектов SPListItem, отличным от полей типа контента элемента. В этом сценарии строка "*" передается как единственный член свойства Columns атрибута CustomMappingAttribute. Логика методов MapFrom(Object) и MapTo(Object) просто сопоставляет свойство SPListItem с соответствующим свойством класса типов контента. Запись вызывающего кода будет легче осуществить, если использовать для свойства в классе имя, совпадающее с именем соответствующего свойства в классе SPListItem. Ниже приведен соответствующий пример.

[CustomMapping(Columns = new String[] { "*" })]
public void MapFrom(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    this.File = item.File;
}

public void MapTo(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    item.File = this.File;
}

Строка "*" сообщает LINQ to SharePoint о необходимости получить из базы данных весь объект SPListItem вместе со всеми свойствами и всеми полями. Существует одно свойство, для которого данная операция не требуется. Если единственным свойством объекта SPListItem, с которым необходимо выполнить сопоставление, является Attachments, можно использовать "Attachments" в качестве строки в массиве Columns и LINQ to SharePoint получит не только логическое поле (столбец) "Attachments", но и свойство Attachments.

Сопоставление столбцов, добавленных пользователями после развертывания

Использование "*" в качестве единственного элемента в массиве Columns также позволяет сопоставлять столбцы, которые пользователи добавляют в список после развертывания решения, с членами свойства Dictionary<TKey, TValue> в классе типов контента, где "TKey" — это String, а "TValue" — это Object. Это осуществляется с помощью сопоставления внутреннего имени каждого поля с записью словаря, которая имеет ключ с таким же именем. Ниже приведен соответствующий пример.

[CustomMapping(Columns = new String[] { "*" })]
public void MapFrom(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    foreach (var field in item.Fields)
    {
        this.Properties[field.InternalName] = item[field.InternalName];
    }
}

public void MapTo(object listItem)
{
    SPListItem item = (SPListItem)listItem;
    foreach (var kvp in this.Properties)
    {
        item[kvp.Key] = this.Properties[kvp.Key];
    }
}

Сопоставление контейнера свойств элемента списка

Методы ICustomMapping можно также использовать для сопоставления свойств с определенными записями хэш-таблиц в поле базы данных, которое соответствует свойству SPListItem.Properties. При таком сценарии не объявляйте контейнер свойств в классе типов контента. Вместо этого объявите отдельное свойство для каждого свойства в контейнере свойств элемента списка, которое требуется сопоставить. Реализуйте методы ICustomMapping, как показано в следующем примере.

[CustomMapping(Columns = new String[] { "*" })]
public void MapFrom(object listItem)
{
    this.PreviousManager = ((SPListItem)listItem).Properties["PreviousManager"];
}

public void MapTo(object listItem)
{
    ((SPListItem)listItem).Properties["PreviousManager"] = this.PreviousManager;
}

Управление конфликтами параллельного доступа для новых столбцов

Чтобы свойства наверняка использовались в системе отслеживания изменений объектов, убедитесь, что метод доступа set свойств вызывает методы OnPropertyChanging и OnPropertyChanged класса типа контента, как показано в следующем примере. Эти методы являются частью кода, создаваемого программой SPMetal. Они обрабатывают события PropertyChanging и PropertyChanged, соответственно. Ниже приведен пример для одного из столбцов, описанных выше в данном разделе, с настраиваемым типом поля. Обратите внимание, что настраиваемым типом поля является ISBNData.

private ISBNData iSBN;

public ISBNData ISBN 
{
    get 
    {
        return iSBN;
    }
    set 
    {
        if ((value != iSBN)) 
        {
            this.OnPropertyChanging("ISBN", iSBN);
            iSBN = value;
            this.OnPropertyChanged("ISBN");
        }
    }
}
ПримечаниеПримечание

Не помещайте ColumnAttribute в объявление свойства.

Реализуйте метод Resolve(RefreshMode, Object, Object) для включения новых столбцов в процесс разрешения конфликтов параллельного доступа. Методы ObjectChangeConflict.Resolve() и MemberChangeConflict.Resolve() проверяют каждый элемент списка на реализацию ICustomMapping его типом контента. При положительном результате каждый из этих методов вызывает метод ICustomMapping.Resolve(RefreshMode, Object, Object). Значение RefreshMode, которое передается в метод ICustomMapping.Resolve(RefreshMode, Object, Object), идентично значению, переданному в вызывающий метод.

Ниже приведен пример реализации ICustomMapping.Resolve(RefreshMode, Object, Object).

Важное примечаниеВажно!

Эта реализация выполняет запись только в те свойства, которые представляют новые столбцы, сопоставляемые методами ICustomMapping. Ваша реализация должна также следовать такой политике. На момент вызова ICustomMapping.Resolve(RefreshMode, Object, Object) старые свойства уже были разрешены с помощью метода ObjectChangeConflict.Resolve() или MemberChangeConflict.Resolve(). Если реализация выполняет запись в старые свойства, существует риск потерять работу, проделанную двумя этими методами.

public void Resolve(RefreshMode mode, object originalListItem, object databaseListItem)
{
    SPListItem originalItem = (SPListItem)originalListItem;
    SPListItem databaseItem = (SPListItem)databaseListItem;

    ISBNData originalISBNValue = (ISBNData)originalItem["ISBN"];
    ISBNData dbISBNValue = (ISBNData)databaseItem["ISBN"];

    UPCAData originalUPCAValue = (UPCAData)originalItem["UPCA"];
    UPCAData dbUPCAValue = (UPCAData)databaseItem["UPCA"];

    if (mode == RefreshMode.OverwriteCurrentValues)
    {
        this.ISBN = dbISBNValue;
        this.UPCA = dbUPCAValue;
    }
    else if (mode == RefreshMode.KeepCurrentValues)
    {
        databaseItem["ISBN"] = this.ISBN;
        databaseItem["UPCA"] = this.UPCA;        
    }
    else if (mode == RefreshMode.KeepChanges)
    {
        if (this.ISBN != originalISBNValue)
        {
            databaseItem["ISBN"] = this.ISBN;
        }
        else if (this.ISBN == originalISBNValue && this.ISBN != dbISBNValue)
        {
            this.ISBN = dbISBNValue;
        }

        if (this.UPCA != originalUPCAValue)
        {
            databaseItem["UPCA"] = this.UPCA;
        }
        else if (this.UPCA == originalUPCAValue && this.UPCA != dbUPCAValue)
        {
            this.UPCA = dbUPCAValue;
        }
    }
} 

В данном примере показана реализация метода Resolve для сопоставления контейнера свойств элемента списка.

public void Resolve(RefreshMode mode, object originalListItem, object databaseListItem)
{
    SPListItem originalItem = (SPListItem)originalListItem;
    SPListItem databaseItem = (SPListItem)databaseListItem;

    string originalPreviousManagerValue = 
                originalItem.Properties["PreviousManager"].ToString();
    string dbPreviousManagerValue = 
                databaseItem.Properties["PreviousManager"].ToString();
    
    if (mode == RefreshMode.OverwriteCurrentValues)
    {
        this.PreviousManager = dbPreviousManagerValue;
    }
    else if (mode == RefreshMode.KeepCurrentValues)
    {
        databaseItem.Properties["PreviousManager"] = this.PreviousManager;
    }
    else if (mode == RefreshMode.KeepChanges)
    {
        if (this.PreviousManager != originalISBNValue)
        {
            databaseItem.Properties["PreviousManager"] = this.PreviousManager;
        }
        else if (this.PreviousManager == originalISBNValue && this.PreviousManager != dbPreviousManagerValue)
        {
            this.PreviousManager = dbPreviousManagerValue;
        }
    }      
}