Building Entity Classes for LINQ to SharePoint

The SharePoint List Data Models reference implementation makes extensive use of the LINQ to SharePoint provider to perform data operations against SharePoint lists. Before you can use LINQ expressions to query SharePoint lists, you must build a set of entity classes to represent the lists and list items in your SharePoint site. SharePoint 2010 provides a command-line tool, SPMetal, which you can use to generate these classes automatically. For more information on SPMetal, see Using LINQ to SharePoint.

In the DataModels.SharePointList.Model project, the PartsSite.cs file contains the entity classes that we generated using the SPMetal tool. Within this file, the PartsSiteDataContext class sits at the top of the entity hierarchy. This class inherits from the DataContext class and represents the SPWeb instance from which the entity classes were generated—in this case, the root site of the SharePointList site collection. The data context class provides the foundation for every LINQ to SharePoint query, as all LINQ to SharePoint expressions are scoped to a DataContext instance. The class provides public read-only properties for each list on the SharePoint site. For example, the following property provides the LINQ to SharePoint provider with access to the Parts list.

[Microsoft.SharePoint.Linq.ListAttribute(Name="Parts")]
public Microsoft.SharePoint.Linq.EntityList<Part> Parts 
{
  get { return this.GetList<Part>("Parts"); }
}

As you can see, lists in LINQ to SharePoint expressions are represented by the generic EntityList<T> class, which represents an enumerable collection of entities. If you take a look through the PartsSite.cs file, you can see that SPMetal generates an entity class for each content type on the site. For example, the file includes a Part class that contains fields, properties, and event handlers for the Part content type.

[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name="Part", 
  Id="0x01001966A9D6EDFEB845A8DD2DDA365BF5DC")]
public partial class Part : Item 
{
  ...
}

This class provides a strongly typed representation of list items that use the Part content type. When you call the GetList<Part>("Parts") method, you are requesting an enumerable collection of Part content type entity instances from the Parts list instance.

Ff798513.note(en-us,PandP.10).gifNote:
For more information on building entity classes for LINQ to SharePoint, see Using LINQ to SharePoint.

Reverse Lookups in the Entity Model

When you use the SPMetal command-line tool to generate entity classes, it automatically detects relationships based on lookup columns. For example, the Inventory Locations list includes a Part lookup column. As a result, the InventoryLocation class includes a Part property that allows you to navigate to the associated Part entity.

private Microsoft.SharePoint.Linq.EntityRef<Part> _part;

[Microsoft.SharePoint.Linq.AssociationAttribute(Name="PartLookup",  
  Storage="_part", 
  MultivalueType=Microsoft.SharePoint.Linq.AssociationType.Single, 
  List="Parts")]
public Part Part 
{
  get { return this._part.GetEntity(); }
  set { this._part.SetEntity(value); }
} 

The class also includes various event handlers to ensure that the Part reference remains up to date if the associated Part entity is changed. What may be less obvious is that SPMetal also attempts to generate a reverse lookup association for this relationship. In other words, SPMetal will add a property to the Part class that allows you to navigate from Parts to Inventory Locations, despite the fact that the Parts list includes no references to the Inventory Locations list. The following code example shows the InventoryLocation property in the Parts class.

private Microsoft.SharePoint.Linq.EntitySet<InventoryLocation> _inventoryLocation;

[Microsoft.SharePoint.Linq.AssociationAttribute(Name="PartLookup", 
  Storage="_inventoryLocation", ReadOnly=true, 
  MultivalueType=Microsoft.SharePoint.Linq.AssociationType.Backward, 
  List="Inventory Locations")]
public Microsoft.SharePoint.Linq.EntitySet<InventoryLocation> InventoryLocation   
{
  get { return this._inventoryLocation; }
  set { this._inventoryLocation.Assign(value); }
}

As you can see from the code, each Part instance maintains a reference to a collection of InventoryLocation instances; in other words, to every InventoryLocation instance that links to that Part instance through its Part lookup column. Note that the Part instance does not actually store these InventoryLocation instances, and navigating this relationship results in a call to the content database. As before, the class includes various event handlers to ensure that the references remain up to date. However, the current version of SPMetal has an important limitation when it comes to generating reverse lookup associations:

  • If a site lookup column is used by only one list or content type, SPMetal will generate a reverse lookup association for the relationship.
  • However, if a site lookup column is used by more than one list or content type, SPMetal will not generate reverse lookup associations for any of the relationships based on that lookup column.

As you can see from the following diagram, three lists—Part Suppliers, Machine Parts, and Inventory Locations—all include a lookup column for the Parts list.

Lookup Relationships for the Parts List

Ff798513.822f5dd7-e051-4bc0-a215-7466f439c63a(en-us,PandP.10).png

If we had used the same site lookup column in each of these three lists, the Part class would not contain any reverse lookup associations. However, the logic in our repository class requires that we are able to retrieve the inventory locations associated with a specified part, which would be a somewhat unwieldy task without the reverse lookup association for Inventory Locations. There are several possible approaches to resolve this issue, as described in Using LINQ to SharePoint. To work around the limitation, we temporarily created two site columns—PartLookup and PartDUPELookup—that reference the Parts list. These columns are identical in everything but name, as shown by the following code example.

<Field Type="Lookup" 
       DisplayName="Part" 
       Required="TRUE" 
       EnforceUniqueValues="FALSE" 
       List="Lists/Parts" 
       WebId="" 
       ShowField="Title" 
       UnlimitedLengthInDocumentLibrary="FALSE"
       Group="Parts Database Columns" 
       ID="{4962bb01-d4a4-409d-895c-fd412baa8293}" 
       Name="PartLookup" 
       Overwrite="TRUE" />
<Field Type="Lookup" 
       DisplayName="PartDUPE" 
       Required="TRUE" 
       EnforceUniqueValues="FALSE" 
       List="Lists/Parts" 
       WebId="" 
       ShowField="Title" 
       UnlimitedLengthInDocumentLibrary="FALSE"
       Group="Parts Database Columns" 
       ID="{299E6CC0-0DEF-49CB-AB38-D371CC98EFCE}" 
       Name="PartDUPELookup" 
       Overwrite="TRUE" />

After generating the model using SPMetal, we removed the PartDUPELookup column and updated the generated code in PartsSite.cs by finding and replacing all instances of PartDUPELookup with PartLookup. Using this approach kept the information model clean at the cost of a straightforward manual edit. However, this would not be a viable approach if you were automatically generating the entity classes as part of your build process.

Since we do not require reverse lookup associations from the Parts list to the Part Suppliers list or the Machine Parts list, the entity classes for these lists were both generated using the PartLookup site column. As a result, the Parts list does not contain reverse lookup associations for Part Suppliers or Machine Parts. In contrast, since we do require a reverse lookup association from the Parts list to the Inventory Locations list, the Inventory Locations list alone used the PartDUPELookup site column during the SPMetal generation process. As this column is not used by any other lists, SPMetal generates the reverse lookup association for Inventory Locations in the Parts class.

We expect that future product releases may address this limitation. However, for the time being it's important to understand where the limitation applies and how you can address it.

Show: