Extensión de la asignación relacional de objetos

Última modificación: viernes, 12 de marzo de 2010

Hace referencia a: SharePoint Foundation 2010

La asignación relacional de objetos que proporciona el proveedor LINQ to SharePoint no abarca todos los escenarios posibles en los que LINQ podría tener acceso a una base de datos de contenido de Microsoft SharePoint Foundation en la lógica de negocios. Las siguientes son situaciones en las que es posible que deba extender la asignación.

  • SPMetal solo puede generar código para los campos de un tipo de contenido. No hay una asignación integrada a las propiedades de los objetos SPListItem, como el contenedor de propiedades Properties o la propiedad Attachments.

  • SPMetal no puede generar código para los campos que usan un tipo de datos de campo personalizado.

  • SPMetal no puede leer el futuro, por lo que no puede asignar campos (columnas) que los usuarios van a agregar a las listas después de que se haya implementado la solución.

  • Es posible que no siempre pueda volver a ejecutar SPMetal cuando se cambia el diseño de una lista de destino y se agregan campos nuevos. Por ejemplo, otros equipos de desarrollo pueden estar destinados al código generado por SPMetal.

Por estos motivos, se proporciona la interfaz ICustomMapping para que pueda extender la asignación relacional de objetos.

Extensión de una solución de LINQ to SharePoint

A continuación se detallan las tareas básicas para extender la asignación relacional de objetos.

  • Asignación de las propiedades SPListItem o nuevas columnas a las nuevas propiedades de la clase de tipo de contenido.

  • Control de conflictos de concurrencia con respecto a las nuevas columnas.

Para que estas tareas se puedan realizar fácilmente, LINQ to SharePoint proporciona la interfaz ICustomMapping. Una clase que implementa esta interfaz puede asignar nuevas columnas a nuevas propiedades. También puede extender el sistema de resolución de conflictos de concurrencia para que tenga en cuenta a las nuevas columnas.

Asignación de columnas que usan tipos de campos personalizados.

En el archivo de código de extensión, comience por volver a declarar la clase que representa el tipo de contenido de la lista con las nuevas columnas. La clase debe estar marcada como partial (Partial en Visual Basic). (También se tiene que haber declarado como partial en el archivo de código original, normalmente generado por SPMetal. La herramienta SPMetal, cuyo uso se recomienda, declara automáticamente todas las clases de tipo de contenido que genera como partial). No debe repetir las decoraciones del atributo de la declaración original, sino que debe indicar que la clase implementa la interfaz ICustomMapping.

Dentro de la clase, declare tres métodos de la interfaz. En el siguiente ejemplo el tipo de contenido se llama 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.

}

Decore el método MapFrom(Object) con un atributo CustomMappingAttribute. Dentro de este atributo, construya una matriz de Strings que contengan los nombres internos de las columnas y asigne la matriz a la propiedad Columns.

Nota

El nombre interno de una columna no se puede obtener en la UI de SharePoint Foundation, sino que debe obtenerlo a través del modelo de objetos mediante la propiedad InternalName.

En el siguiente ejemplo se muestra cómo se usa el CustomMappingAttribute para crear una matriz de nombres internos para dos columnas nuevas que se agregaron a una lista Libros.

  • ISBN es una columna que usa el tipo de datos de campo personalizado (denominado ISBNData) diseñada para contener el número ISBN de un libro.

  • UPC-A es una columna que usa un tipo de campo personalizado (denominado UPCAData) que contiene los datos estructurados que se pueden usar para generar el tipo UPC-A de códigos de barras para un libro. Su nombre interno es "UPCA".

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

LINQ to SharePoint usa el método MapFrom(Object) para establecer los valores de propiedad de la base de datos de contenido. El parámetro que se pasó es un objeto que representa un elemento de lista que se capturó de la base de datos de contenido. El método debe tener una línea, para cada columna nueva, que asigne el valor de campo de la columna a la propiedad que desea usar para representar la columna en la extensión de la asignación relacional de objetos.

El método MapTo(Object) se usa para guardar un valor en el campo correspondiente de la base de datos de contenido. El parámetro que se pasó es un objeto que representa un elemento de lista que se capturó de la base de datos de contenido. El método debe tener una línea, para cada propiedad que represente una columna nueva, que asigne el valor de la propiedad al campo de la columna en la base de datos de contenido.

Nota importanteImportante

No llame a los métodos MapFrom(Object) ni MapTo(Object) desde su propio código.

En el siguiente ejemplo se muestra cómo implementar estos métodos.

[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;
}

Puede agregar cualquier otra lógica que necesite, como lógica de validación, a cualquiera de los dos métodos.

Asignación de las propiedades SPListItem

Generalmente, debe extender la asignación relacional de objetos si necesita obtener acceso, en el código LINQ to SharePoint, a propiedades de objetos SPListItem que no sean los campos del tipo de contenido del elemento. En este escenario, pasa la cadena "*" como el único miembro de la propiedad Columns del CustomMappingAttribute. La lógica de los métodos MapFrom(Object) y MapTo(Object) simplemente asigna la propiedad SPListItem a una propiedad correspondiente de la clase de tipo de contenido. Seguramente sea más fácil escribir el código de llamada si usa el mismo nombre para la propiedad en la clase como el mismo nombre de la propiedad correspondiente en la clase SPListItem. A continuación se muestra un ejemplo.

[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;
}

La cadena "*" indica a LINQ to SharePoint que debe obtener todo el objeto SPListItem de la base de datos, todas las propiedades y también todos los campos. Hay una propiedad para la cual no es necesario realizar esta tarea. Si la única propiedad de SPListItem que desea asignar es Attachments, puede usar "Attachments" como una cadena en la matriz Columns y LINQ to SharePoint capturará no solo el campo booleano (columna) "Attachments", sino también la propiedad Attachments.

Asignación de columnas que los usuarios agregan después de la implementación

El uso de "*" como único elemento de la matriz Columns permite también asignar columnas que el usuario agrega a la lista después de que se implementa la solución a miembros de una propiedad Dictionary<TKey, TValue> en la clase de tipo de contenido, donde TKey es String y TValue es Object. Esto se logra al asignar el nombre interno de cada campo a una entrada de diccionario con una clave del mismo nombre. A continuación se muestra un ejemplo.

[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];
    }
}

Asignación del contenedor de propiedades del elemento de lista

Los métodos ICustomMapping también se pueden usar para asignar propiedades a entradas de una tabla hash en particular dentro del campo de la base de datos que corresponde a la propiedad SPListItem.Properties. En este escenario, no se declara una propiedad de contenedor de propiedades en la clase de tipo de contenido, sino que se declara una propiedad aparte para cada propiedad del contenedor de propiedades del elemento de lista que desea asignar. Implemente los métodos de ICustomMapping como se muestra en el siguiente ejemplo.

[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;
}

Administración de conflictos de concurrencia para las nuevas columnas

Para asegurarse de que las propiedades participan en el sistema de seguimiento de cambios en el objeto, asegúrese de que el descriptor de acceso set de las propiedades está llamando a los métodos OnPropertyChanging y OnPropertyChanged de la clase de tipo de contenido, como se muestra en el siguiente ejemplo. Estos métodos son parte del código que generó SPMetal. Ellos controlan los eventos PropertyChanging y PropertyChanged, respectivamente. El siguiente es un ejemplo para una de las columnas que se mencionaron anteriormente en este tema, que usa un tipo de campo personalizado. Tenga en cuenta que el tipo de campo es ISBNData.

private ISBNData iSBN;

public ISBNData ISBN 
{
    get 
    {
        return iSBN;
    }
    set 
    {
        if ((value != iSBN)) 
        {
            this.OnPropertyChanging("ISBN", iSBN);
            iSBN = value;
            this.OnPropertyChanged("ISBN");
        }
    }
}

Nota

No ponga un ColumnAttribute en la declaración de propiedad.

Implemente el método Resolve(RefreshMode, Object, Object) para inscribir las nuevas columnas en el proceso de resolución de conflictos de concurrencia. Los métodos ObjectChangeConflict.Resolve() y MemberChangeConflict.Resolve() comprueban cada elemento de lista para ver si su tipo de contenido implementa ICustomMapping. Para los que sí lo hacen, cada uno de estos métodos llama al método ICustomMapping.Resolve(RefreshMode, Object, Object). El valor RefreshMode que se pasa a ICustomMapping.Resolve(RefreshMode, Object, Object) es el mismo que el que se pasó al método de llamada.

El siguiente es un ejemplo de una implementación de ICustomMapping.Resolve(RefreshMode, Object, Object).

Nota importanteImportante

La implementación solo escribe en las propiedades que representan las nuevas columnas que asignan los métodos ICustomMapping. Su implementación debe seguir la misma directiva. Las propiedades antiguas ya las ha resuelto el método ObjectChangeConflict.Resolve() o MemberChangeConflict.Resolve() en el momento en que se llama a ICustomMapping.Resolve(RefreshMode, Object, Object). Si su implementación escribe en alguna de las propiedades antiguas, corre el riesgo de deshacer lo que los dos métodos anteriores hicieron.

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;
        }
    }
} 

En el caso de asignar el contenedor de propiedades del elemento de lista, la implementación de Resolve se muestra en el siguiente ejemplo.

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;
        }
    }      
}