다음을 통해 공유


Entity Classes

Applies to: SharePoint Foundation 2010

This topic introduces the entity classes of the LINQ to SharePoint provider. These classes provide a light weight, object-relational interface between your object-oriented C# or Microsoft Visual Basic code and the relational table structure of the content databases of Microsoft SharePoint Foundation. They also provide the foundation of the object change tracking system.

The object-relational interface enables object-oriented code in LINQ queries and also enables creating, deleting, and updating list items by using object-oriented code that references database entities independently of the regular SharePoint Foundation object model. In addition, after the object-relational framework is in place, you can use it for object-oriented business logic in any situation where the lightweight and deferred loading provided by the framework offer advantages over the regular SharePoint Foundation object model. Using the entity classes instead of the regular object model might also improve code readability when the content reference is in close proximity to LINQ query code that is using the same entity classes, such as in a foreach loop that iterates over the result of a LINQ query.

Use SPMetal

Writing the classes discussed in this article can be quite tedious and error prone, so SharePoint Foundation includes a command-line tool, called SPMetal, which can be used to generate the class and property declarations. It will read the content type definitions and the list schemas for the lists in a target SharePoint Foundation Web site and then generate entity classes, including the attribute decorations, from them. The tool is included with SharePoint Foundation and is usually found in the folder %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\BIN. We recommend you use this tool.

You can customize the way that SPMetal generates the code. Moreover, because all the classes it generates are marked partial (Partial in Visual Basic), you can add members to them in a separate code file. If you need to regenerate the entity classes with SPMetal, then only the generated file is overwritten, not your file with custom members. For information about how to use SPMetal, see How to: Use SPMetal.

Tip

We recommend that you use SPMetal to generate all declarations of the classes discussed in this article. Also, the code examples in this article are not intended to be copied into your own code nor are they exact copies of what SPMetal would produce. Each is simplified to suppress code that is extraneous to the point being made. In particular, all code related to the object change tracking system is suppressed from these examples. For more information about the system and the code that supports it, see Object Change Tracking and Optimistic Concurrency. Also, the attribute decorations on the class and property declarations in this topic are generally simpler than what you would need in practice, and some attributes are left out entirely. For more information about the attributes and their uses, see the reference topics for the following classes (and their members):

Data Context Class

At the top of the hierarchy of entity classes is the DataContext class, which represents the content of a Web site. You can instantiate a DataContext object by passing to its constructor the URL of the Web site whose data you want to model. Then call its GetList<T>(String) method to get a reference to particular list. The following is an example:

DataContext teamData = new DataContext("http://DeptServer/TeamSite")
EntityList<Announcment> teamAnns = teamData.GetList<Announcement>("Announcements");

Assuming that you have declared a class to represent the "Announcement" content type (see below), you could then query teamAnns. Here is an example.

var excitingAnns = from ann in teamAnns
                   where ann.Title.EndsWith("!")
                   select ann;

You can usually get more readable, and more object oriented, calling code if you declare a class that inherits from DataContext and has one property for each of the Web site’s lists. The following is an example of such declarations.

public partial class TeamSite : DataContext 
{
    public TeamSite(string requestUrl) : 
        base(requestUrl) { }

    [List(Name="Announcements")]
    public EntityList<Announcement> Announcements 
    {
        get { return this.GetList<Announcement>("Announcements"); }
    }

}

Note

The ListAttribute identifies the property as representing a specified list in the Web site that is represented by the DataContext object.

After this declaration is in place, the calling code does not need the explicit call of the GetList<T>(String) method and it is more self documenting.

TeamSite teamData = new TeamSite("http://DeptServer/TeamSite");

var excitingAnns = from ann in teamData.Announcements
                   where ann.Title.EndsWith("!")
                   select ann;

Important

The constructor for the TeamSite class does not load the entire Announcements list. The EntityList<TEntity> class supports deferred loading. Objects of this type are not loaded until they are explicitly referenced in later code.

The SPMetal tool always generates a class that derives from DataContext. You can configure which lists it will model. By default, it declares a property for every non-hidden list in the Web site.

Note

You can, of course, add methods, events, fields, and properties that do not represent lists to your DataContext–derived class. If you do add such members, your declarations should be in a separate code file from the file generated by SPMetal. This prevents your code from being overwritten if you need to regenerate the code that SPMetal produces.

Lists, Content Types, and List Items

Any solution that uses LINQ to SharePoint must use entity classes to represent the SharePoint lists, content types, and list items.

The EntityList<TEntity> class represents a SharePoint Foundation list. The type variable, TEntity, is the type of the list items; that is, the content type. A property of the EntityList<TEntity> type is given the same name as the list it represents, such as "Announcements" or "Calendar". The TEntity is usually named with the singular form of the same term, such as "Announcement", but if the list name itself is singular, a different term may be used. For example, "CalendarEvent" is used as the type for the class that represents a Calendar list. For a custom list, the type name is constructed by concatenating the list name with the term "Item". For example, a custom "Books" list will be given the type "BooksItem". The EntityList<TEntity> class is sealed and has no public constructor. See the Data Context Class section for an example of declaring a property to be of type EntityList<TEntity> for a particular TEntity.

The content types must themselves be declared as classes. The declaration is decorated with a ContentTypeAttribute (which can be shortened to "ContentType") as shown in this example.

[ContentType(Name="Announcement" Id="Ox0104")]
public partial class Announcement
{

}

Each field (and metadata property) of a list must be represented with a public property in the content type class definition if you need to explicitly reference the field (or property) in a query or other code. Because such classes can have other properties, the ones that represent fields (and metadata properties) are distinguished by the [ColumnAttribute] decoration (which can be shortened to [Column]), as shown in this example.

[ContentType(Name="Customer")]
public partial class Customer : Item
{
    [Column]
    public Int32 CustomerId { get; set; }

    [Column]
    public String Name { get; set; }
}

Warning

If the list schema is changed after the entity classes are generated, a solution that uses those entity classes may fail.

Inheritance of Content Types

The content type classes can mirror the inheritance relations of the actual content types that they represent. The fundamental content type in SharePoint Foundation is called Item. It provides the basic fields that all list items have, such as Title. (Some of these basic fields, such as ID and Version, are hidden by default on standard SharePoint Foundation list views.) A simplified version of the class declaration for the Item content type is the following.

[ContentType(Name="Item")]
public partial class Item
{
    private Nullable<Int32> _id;
    private Nullable<Int32> _version;
    private String _title;

    [Column]
    public Nullable<Int32> Id { get; set; }

    [Column]
    public Nullable<Int32> Version { get; set; }

    [Column]
    public String Title { get; set; }

  // Other member declarations omitted for readability.
}

All other content type classes derive from Item. Hence, they do not need to explicitly declare any of the properties they inherit.

Relationships between Lists and Deferred Loading

Lists can have permanent relationships with each other that are represented in the object-relational infrastructure by the AssociationAttribute and by the EntityRef<TEntity> and EntitySet<TEntity> classes which, like EntityList<TEntity>, defer the loading of the entity they represent until the first time the entity is actually accessed at runtime. (Optionally, a LookupList<T> object is also involved in representing the relationship.)

Important

In SharePoint Foundation, two lists can have an association only if one has a column that is a lookup to a column in the other.

Entity References

The EntityRef<TEntity> class represents, and provides deferred loading of, the list item on the singleton side of a many-to-one or one-to-one relationship between list items in different lists. For example, suppose each member of a team is assigned a security code that is stored in a separate list from the team members list for security reasons. A TeamMember class represents the type of items on the Team Members list and a SecurityCode class represents the type of items of the Security Codes list. Because there is exactly one security code for each team member, the TeamMember class declares a private field of type EntityRef<TEntity> (where TEntity is SecurityCode) that represents an item in the Security Codes list. The TeamMember class also declares a public property, of type SecurityCode, that wraps the private field. It is this property that is decorated with the AssociationAttribute. When an object of type TeamMember is instantiated, its SecurityCode value is not immediately loaded. It is loaded if, and only if, it is referenced in later code.

The following is an example of the relevant parts of the TeamMember class declaration. Note the following about this example:

[ContentType(Name = "TeamMember")]
public class TeamMember
{
    private EntityRef<SecurityCode> memberSecurityCode;

    [Association(List = "SecurityCodes", MultiValueType = AssociationType.Single)]
    public SecurityCode MemberSecurityCode
    {
        get { return memberSecurityCode.GetEntity(); }
        set { memberSecurityCode.SetEntity(value); }
    }

    // Declarations of other members suppressed for readability.
}

[ContentType(Name = "SecurityCode")]
public class SecurityCode
{
    [Column]
    public String Value { get; set; }

    // Declarations of other members suppressed for readability.
}

Note

The get and set assessors of the MemberSecurityCode property use the GetEntity() and SetEntity(TEntity) methods instead of the more familiar assessor code ("return securityCode" and "securityCode=value") because the property is actually of a different type from the field that it wraps. These two methods reach into the EntityRef<TEntity> object to touch the SecurityCode object that it wraps.

Now you can access a team member’s security code with familiar object oriented dot notation, and the association can function in queries like a kind of permanent join of the two lists. For example, in the following query, member is an item from the Team Members list, but MemberSecurityCode is a reference to an item in the Security Codes list.

var result = from member in teamMembers
             where member.MemberSecurityCode.Value.Contains("guest")
             select member;

The relationship can be defined in either the class that represents the lookup source content type or the class that represents the target content type. Here is how you would define the relationship between the Team Members and Security Codes lists by using a property of the SecurityCode class.

[ContentType(Name = "SecurityCode")]
public partial class SecurityCode
{
    [Column]
    public String Value { get; set; }

    private EntityRef<TeamMember> teamMember;

    [Association(List = "TeamMembers", MultiValueType = AssociationType.Single)]
    public TeamMember TeamMember
    {
        get { return this.teamMember.GetEntity(); }
        set { this.teamMember.SetEntity(value); }
    }
    // Other member declarations suppressed for readability.
}

Declaring the relationship in the target class in this way enables reverse lookups in your calling code. You can even declare the relationship in both classes, to enable lookups in either direction. For more information about reverse lookups, see Reverse Lookups below.

Entity Sets

The EntitySet<TEntity> class represents, and provides deferred loading of, the "many" side of a many-to-one or many-to-many relationship between list items in different lists. For example, suppose that each member of a team is assigned to multiple projects and each project has multiple team members assigned to it. This is a many-to-many relationship. A TeamMember class represents the type of items on the Team Members list and a Project class represents the type of items of the Projects list. The TeamMember class declares a private field of type EntitySet<TEntity> (where TEntity is Project) that represents one or more items in the Projects list. The field is wrapped with a public EntitySet<Project> property called AssignedProjects. When an object of type TeamMember is instantiated, the object referenced by this AssignedProjects property is not loaded immediately. It is loaded if, and only if, it is referenced in later code.

[ContentType(Name = "TeamMember")]
public class TeamMember
{
    private EntitySet<Project> assignedProjects;

    [Association(List="Projects", MultiValueType=AssociationType.Multi)]
    public EntitySet<Project> AssignedProjects
    {
        get { return this.assignedProjects; }
        set { this.assignedProjects.Assign(value); }
    }

    // Declarations of other members suppressed for readability.
}

[ContentType(Name = "Project")]
public class Project
{
    [Column]
    public String Title { get; set; }

    // Declarations of other members suppressed for readability.
}

Note

In the preceding code example, and others that show the Project class, there is a declaration of a Title property to represent the Title column. This is a simplification for readability. In actual code, the Project class would inherit from an Item class that represents the base content type of SharePoint Foundation. Because the Title property is declared in the Item class’s definition, it would not be here in the Project class definition.

With these declarations in place, you can query for team members that are assigned to a particular project as in this example.

Project fiscalPlan = new Project() { Title="Fiscal year planning." };

TeamSite teamData = new TeamSite("http://DeptServer/TeamSite");

var filteredTeamMembers = from member in teamData.TeamMembers
                          where member.AssignedProjects.Contains(fiscalPlan) 
                          select member;

As in the case of EntityRef<TEntity> example, either of the two classes that represent the content types of the two related lists can be used to define the relationship. The following shows how the same Team Members-to-Projects relation would be declared inside the Project class.

[ContentType(Name = "Project")]
public class Project
{
    [Column]
    public String Title { get; set; }

    private EntitySet<TeamMember> assignedTeamMembers;

    [Association(List="Team Members", MultivalueType=AssociationType.Multi)]
    public EntitySet<TeamMember> AssignedTeamMembers
    {
        get { return this.assignedTeamMembers; }
        set { this.assignedTeamMembers.Assign(value); }
    }

    // Declarations of other members suppressed for readability.
}

You can declare the relationship in both classes. For more information, see Reverse Lookups below.

One-to-Many and Many-to-One Relations

In the Entity References section, there is an example of coding a one-to-one relation between lists. In the Entity Sets section, there is an example of coding a many-to-many relation. It is possible to represent one-to-many and many-to-one relations as well. To continue the example of the Entity Sets section, suppose that each team member is not only assigned to multiple projects, but is also the leader of more than one project. But each project has only one leader. The leadership relation from the Team Members list to the Projects list is one-to-many. This relation can be represented with EntitySet<TEntity> members in the TeamMember class and an EntityRef<TEntity> member in the Projects class as is shown in the following code.

[ContentType(Name = "TeamMember")]
public class TeamMember
{
    private EntitySet<Project> projectsLedBy;

    [Association(List="Projects", MultivalueType=AssociationType.Multi)]
    public EntitySet<Project> ProjectsLedBy
    {
        get { return this.projectsLedBy; }
        set { this.projectsLedBy.Assign(value); }
    }

    // Declarations of other members suppressed for readability.
}

[ContentType(Name = "Project")]
public class Project
{
    [Column]
    public String Title { get; set; }

    private EntityRef<TeamMember> lead;

    [Association(List = "TeamMembers", MultiValueType = AssociationType.Single)]
    public TeamMember Lead
    {
        get { return this.lead.GetEntity(); }
        set { this.lead.SetEntity(value); }
    }

    // Declarations of other members suppressed for readability.
}

Lookup Lists with Multiple Values Allowed

The LookupList<T> class represents the current values of a lookup field that allows multiple values. So it is a subset of values found in the target column of the target list in a lookup list relationship. A content type class can have a private field of type LookupList<T> to hold the looked-up values of the column in the current list item object.

It is not necessary to have such a field because you can use a EntitySet<TEntity> property to represents a lookup column. But you may want to have such a field because of the differences in the way a LookupList<T> and an EntitySet<TEntity> are loaded. Consider two content type classes, A and B, that are identical except that A uses a LookupList<T> field to represent a Lookup column and B uses an EntitySet<TEntity>. In both cases, when the query is being enumerated (typically in a foreach loop), specifically when the loop reaches a specific list item of the content type, it makes a query to the database to populate the properties of the object that represents the list item. For an object of type A, the LookupList<T> field is immediately populated with the values of the Lookup column. Later references to that field (or to a property that wraps it) in the foreach loop do not require a second query to the database. However, loading is deferred for objects of type EntitySet<TEntity>, so when a foreach loop reaches a specific object of type B, the EntitySet<TEntity> property is not populated immediately with the items from the target list. Instead, loading is deferred until the first time the property is referenced in the body of the loop. When that reference occurs, a second query to the database is required to populate the property.

An example of a call to an EntitySet<TEntity> property is in the following code from the Entity Sets section. In that example, the AssignedProjects field of the Team Members list is configured as a lookup field to the Title field of the Projects list, and it is configured to allow multiple values.

Project fiscalPlan = new Project() { Title="Fiscal year planning." };

TeamSite teamData = new TeamSite("http://DeptServer/TeamSite");

var filteredTeamMembers = from member in teamData.TeamMembers
                          where member.AssignedProjects.Contains(fiscalPlan) 
                          select member;

But because the EntitySet<TEntity> uses deferred loading, the value of the TeamMember.AssignedProjects property must be queried from the content database each time it is referenced. A LookupList<T> object loads immediately, so code that calls an IList<T> property that wraps a LookupList<T> field performs better than code that calls the EntitySet<TEntity> property.

To continue the example, suppose that the TeamMember class declares a private field of type LookupList<T> (where T is String) that represents the values of the AssignedProjects field, which come from the lookup target column (Title) of the Projects list. By convention, this private field is named by a (camel-cased) concatenation of the lookup column name and a pluralized version of the target column from which the values are drawn. In this case, the field is named assignedProjectsTitles. The field is wrapped with a public property of type IList<String>. It is named by convention with a title-cased version of the field’s name, so in this case the property is called AssignedProjectsTitles.

Note

The names of the IList<T> property and the LookupList<T> field are plural because the lookup field allows multiple values. The LookupList<T> class plays no role in representing lookup fields that allow only a single value.

The TeamMember class now has its relationship with the Projects list declared and it has an AssignedProjectsTitles property to reference the values of the Assigned Projects list item field. What it now needs is something to hook the two together. This is accomplished by decorating the AssignedProjectsTitles property with a ColumnAttribute. Four properties of the attribute are set:

  • Name="Assigned Projects" – Specifies the column whose values are represented by the AssignedProjectsTitles property.

  • FieldType="Lookup" – Specifies that the field identified by the Name property is type Lookup in SharePoint Foundation.

  • IsLookupValue=true – Specifies that the field is a lookup field.

  • LookupDisplayColumn="Title" – Specifies the target column of the target list.

The following code shows the declarations needed to support the lookup relation with both an EntitySet<TEntity> property and an IList<T> property that wraps a LookupList<T> field.

[ContentType(Name = "TeamMember")]
public class TeamMember
{
    [Column]
    public Int32 MemberID { get; set; }

    private EntitySet<Project> assignedProjects;

    private LookupList<String> assignedProjectsTitles;

    [Association(List="Projects", MultiValueType=Multi)]
    public EntitySet<Project> AssignedProjects
    {
        get { return this.assignedProjects; }
        set { this.assignedProjects.Assign(value); }
    }

    [Column Name="Assigned Projects", FieldType="Lookup", IsLookupValue="true", LookupDisplayColumn="Title"]
    public IList<String> AssignedProjectsTitles 
    {
        get { return this.assignedProjectsTitles; }
        set { this.assignedProjectsTitles.Assign(value); }
    }

    // Declarations of other members suppressed for readability.
}

[ContentType(Name = "Project")]
public class Project
{
    [Column]
    public String Title { get; set; }

    // Declarations of other members suppressed for readability.
}

With these declarations in place, a better performing version of the preceding query is possible, as shown in this example.

TeamSite teamData = new TeamSite("http://DeptServer/TeamSite");

var filteredTeamMembers = from member in teamData.TeamMembers
                          where member.AssignedProjectsTitles.Contains("Fiscal year planning.") 
                          select member;

Note

SPMetal generates a LookupList<T> only when a lookup column points to a target list that is not itself being represented by SPMetal-generated code, such as a target list that is hidden or has been excluded from code generation by SPMetal’s configuration file. See Overriding SPMetal Defaults by Using a Parameters XML File for more information.

The Special ‘ID’ LookupList Objects

The value of a Lookup type field, after it has been set, is of the form id;#display_value, where display_value is the value retrieved from the target column of the target list and id is the ID of the list item in the target list whose value was selected. It is suppressed in the UI of SharePoint Foundation. Often, when there is a lookup relation between lists and the lookup column allows multiple values, your solution will need to access the IDs of the items in the target list whose target column values are the values of the lookup column. You can reference these values through the EntitySet<TEntity> property that represents the items in the target list. But, in this case too, code runs faster if those IDs are stored in a LookupList<T> field. For this reason, it is a good idea to have such a field, and a public IList<T> property to wrap it, whenever you have a LookupList<T> field to hold the looked-up values themselves.

The naming convention for these special ‘ID’ fields and properties is the same as for the field and property that hold the looked-up values, except that the second part of each name is "Ids" instead of a pluralized version of the target column name. The [Column] attribute on the IList<T> property is also the same.

The following example shows the Team Members and Projects declarations extended to add an ‘ID’ LookupList<T> field and its corresponding property.

[ContentType(Name = "TeamMember")]
public class TeamMember
{
    [Column]
    public Int32 MemberID { get; set; }

    private EntitySet<Project> assignedProjects;

    private LookupList<String> assignedProjectsTitles; 

    private LookupList<String> assignedProjectsIds; 


    [Association(List="Projects", MultiValueType=Multi)]
    public EntitySet<Project> AssignedProjects
    {
        get { return this.assignedProjects; }
        set { this.assignedProjects.Assign(value); }
    }

    [Column Name="Assigned Projects", FieldType="Lookup", IsLookupValue="true", LookupDisplayColumn="Title"]
    public IList<String> AssignedProjectsTitles 
    {
        get { return this.assignedProjectsTitles; }
        set { this.assignedProjectsTitles.Assign(value); }
    }

    [Column Name="Assigned Projects", FieldType="Lookup", IsLookupValue="true"]
    public IList<String> AssignedProjectsIds 
    {
        get { return this.assignedProjectsIds; }
        set { this.assignedProjectsIds.Assign(value); }
    }    

    // Declarations of other members suppressed for readability.
}

[ContentType(Name = "Project")]
public class Project
{
    [Column]
    public String Title { get; set; }

    // Declarations of other members suppressed for readability.
}

Lookup Lists that Allow Only One Value

A single-value lookup field represents a simple one-to-one relation. So the private field that stores the value is only the type of the target field, rather than the LookupList<T> type. Similarly, the public property that wraps the field is of the same type, rather than IList. (Strictly speaking, the type of the field and property is the Microsoft .NET Framework equivalent of the target field’s SharePoint Foundation type, as explained in Type Mapping: From LINQ to SharePoint Provider to .NET.) Moreover, because there is only one value, the names of the field and the property are not plural.

Consider a variation on the example in the preceding section. Suppose each team member is assigned to only one project and the assignment is recorded in a Project column which is a lookup field to the Title column of the Projects list. In addition to using an EntityRef<TEntity> field to represent the relation between the two lists, the TeamMember class would also declare a private String field named projectTitle and a public String class to wrap it named ProjectTitle. Note that while the field and property names are singular, they otherwise follow the convention of naming a lookup value with the concatenation of the lookup field name and the target field name.

[ContentType(Name = "TeamMember")]
public class TeamMember
{
    private EntityRef<Project> project;

    private String projectTitle;

    [Association(List="Projects", MultiValueType=AssociationType.Single)]
    public Project Project
    {
        get { return this.project.GetEntity(); }
        set { this.project.SetEntity(value); }
    }

    [Column Name="Project", FieldType="Lookup", IsLookupValue="true", LookupDisplayColumn="Title"]
    public String ProjectTitle 
    {
        get { return this.projectTitle.GetEntity(); }
        set { this.projectTitle.SetEntity(value); }
   }

    // Declarations of other members suppressed for readability.
}

[ContentType(Name = "Project")]
public class Project
{
    [Column]
    public String Title { get; set; }

    // Declarations of other members suppressed for readability.
}

Note

In the special case where the lookup field is looking up a value in another field within the same table, then of course there is no relation between tables; for this reason, the declarations of the EntityRef<TEntity> field and the property that wraps it would not be present.

Reverse Lookups

You can declare a list relationship in both of the content type classes. Doing so enables you to do reverse lookups. When you do this, however, and the relation is many-to-many, then the EntitySet<TEntity> property in the target list has a slightly different AssociationAttribute. Specifically, the MultivalueType property is set to AssociationType.Backward instead of Multi. Also, the Name property is set to the internal name of the source lookup column; that is, the target of a reverse lookup.

See Also

Tasks

How to: Use SPMetal

Reference

SPMetal

Concepts

Object Change Tracking and Optimistic Concurrency