LINQ to SharePoint
One of SharePoint 2010’s new data access technologies
August 20, 2011
In the Microsoft Office SharePoint Server world, as in most other systems, it’s all about the data. This means getting data in, getting data out, making changes, and cleaning up (i.e., the basic Create, Read, Update, Delete—CRUD—operations). We all know, however, that it’s not quite that simple, because we need to deal with local versus remote scenarios, security, performance, concurrency, and more—all while trends continue to change in SharePoint, the .NET Framework, and even in the software development industry. This article is the first in a series that will take an in-depth look at working with the new data access approaches in SharePoint Server 2010, as well as changes to the existing approaches.
We will take a close look at the three new data access technologies in SharePoint 2010: LINQ to SharePoint, SharePoint 2010 Data Services, and the SharePoint 2010 Client Object Model. I’ll explain what each technology is, what it’s used for, how it works, and how to make the best use of it while avoiding common gotchas. We’ll start with LINQ To SharePoint in this article—partly because it’s typically the most commonly used of the three technologies, partly because it underpins some of the other aspects we’ll examine later, and partly because we’ll learn some key concepts and practices that also carry over to the other technologies.
LINQ to SharePoint Definition and Rationale
Whenever I’m trying to learn a new concept or technology, I find it helpful to try to understand the core principles of what predated the technology, as well as what the new technology is trying to address. To do this for LINQ to SharePoint, let’s take a stroll way back down memory lane to the distant past—perhaps around the time of the Wild West and the woolly frontiers. Back then, things were decidedly rough-and-ready. If you wanted chicken for dinner, you gosh-darn-it went and caught and plucked and gutted a chicken! If you wanted water, you gosh-darn-it went down to the well and drew some water! Even worse than that, if you wanted some records out of your SharePoint installation, by golly you hand-crafted a full-bodied, long-winded Collaborative Application Markup Language (CAML) query, something like this:
1500
Of course, to execute that CAML query from your program, you needed to write some .NET code to do the call, wrapping it perhaps in an SPQuery object such as the following:
SPQuery query = new SPQuery();StringBuilder sbQuery = new StringBuilder();sbQuery.Append("");sbQuery.Append("");sbQuery.Append("");sbQuery.Append("" + fieldValue1 + "");sbQuery.Append("");sbQuery.Append("");query.Query = sbQuery.ToString();
Notice some key points about this code block. It’s loosely typed in a big string variable (well, a StringBuilder to be exact). This means it’s highly susceptible to human error—just one typo and the whole query will self-destruct. Of course, we can only discover this much later, at runtime, rather than nice and early during compile time. In fact, if you don’t have the correct quality gates in place, your query might even end up all the way into production before the trouble unfolds! Furthermore, because we’re just dealing with string variable names for the fields, we have no way to determine in place what types we’re dealing with—we have to go look them up. Similarly, we don’t have access to the productivity and intuitiveness of IntelliSense (everyone’s favorite Visual Studio feature). Finally, the code is very verbose—there’s a lot of ceremony required just to execute a fairly simple query.
To make matters worse, in SharePoint 2010–land, we now have better relationship capabilities between lists. In general, this is a great feature—but it makes our CAML queries (and the resulting .NET code) considerably longer and more complex when we want to express these joins, such as in the following example:
0x0100 Cape Town
The associated .NET code is similarly far longer and more unwieldy. The bottom line is that, although CAML provides quite a bit of functionality and is continuing to evolve, it isn’t pleasant to work with directly. So, until Microsoft implements an Xbox Kinect plug-in to let users query SharePoint directly, using elaborate gestures and dances, we could at least settle for some kind of middle ground that abstracts the CAML away—which is where Language-Integrated Query (LINQ) comes in. (Incidentally, if you happen to build the aforementioned Kinect plug-in, be sure to email me for where to send royalties for the idea.)
Enter LINQ
If you ’re interested in learning more about LINQ, you can find a detailed description, its history, its inner workings, and more in various articles, books, and pages on the MSDN website. For reasons of brevity, a simple overview will suffice here. In short, and as the name implies, LINQ embeds a logical structure directly into the .NET languages to express high-level query semantics. This lets us write a query such as the following:
from p in dataContext.Projectswhere p.Client.City == "Cape Town"select new { Name = p.Title, ClientName = p.Client.Title, Budget = p.BudgetHours };
This is C# code and is thus compiled directly into our projects. However, if the target platform for your query is a database, the DataContext instance will at runtime convert this query into a platform-specific SQL query similar to the following (note that this is the direct log from the LINQ To SQL for this, as I dummied up two tables in SQLExpress to match the structure of the SharePoint lists):
SELECT [t0].LINQ to SharePoint AS [Name], [t1].LINQ to SharePoint AS [ClientName], [t0].[BudgetHours] AS [Budget]FROM [dbo].[Projects] AS [t0] INNER JOIN [dbo].[Clients] AS [t1] ON [t1].[ID] = [t0].[ClientID]WHERE [t1].[City] = 'Cape Town'
However, it’s important to note that even though the LINQ query closely resembles SQL, it’s only converted to actual SQL through the domain-specific DataContext instance—in this case, a LINQ to SQL DataContext. The exact same query, when compiled against a LINQ to SharePoint DataContext, will instead generate a CAML query much like the following (note that the end point of the CAML will be a SQL Query as the CAML is processed by SharePoint code and shipped to the database for processing):
0x0100 Cape Town
Have a look at this CAML query more closely. Then, take another close look. Next, look at the earlier LINQ query. Can you spot the differences? Very subtle, no? Most likely, you noticed that the LINQ version is, oh, just a touch shorter than the original CAML query. This makes it a lot quicker to write, but remember further that the LINQ query uses actual .NET classes, so we get IntelliSense, too. This makes it even quicker to write up front, as well as quicker to maintain over time. It’s strongly typed too, which means we get the compile-time error checking we were aiming for.
In addition to these obvious advantages, there are other subtle benefits from using LINQ. For instance, there’s a much greater opportunity to bring non-SharePoint developers on board to the project who are likely to have used LINQ in other .NET projects. These new team members are certainly a lot more likely to have used LINQ than CAML, which means they’ll become more productive more quickly than if they needed to hand-craft queries using CAML’s obscure operators and syntax. A further point of consideration relates to long-term maintainability and is based on the LINQ philosophy: The programmers’ intent is communicated far more clearly than in the CAML code. Even a business user (heaven forbid!) could look at the LINQ version and be able get a good idea of what it’s trying to do. So now that we’re sold on using LINQ, let’s see what it means in a SharePoint context.
Before We Begin: A Case Study
Before we start working with the tools, let’s take a moment to describe the set of examples we’ll be testing. The sample isn’t comprehensive—it can’t hope to include every possible field, content type, list template, and so on available in SharePoint—but it does give us a good start with a few common scenarios. There’s a sample application that represents a dummy consulting firm with clients, projects, and TimeCards lists. As Figure 1, Figure 2, and Figure 3 show, these are all custom lists with additional fields added. In addition, we’ll look at a few other lists, all representing contacts lists, as follows:
TestContactsList—This list uses the out-of-box contacts list and, in turn, the out-of-box contact content type.
TestContacts2—This list is set up similarly to the TestContactsList, but it also includes explicit content type management and has the core item content type added.
TestContacts3—This list is based on the out-of-box contacts list template, has explicit content type management on, has the core item content type added, and has a custom site content type called Contact2. Contact2 in turn is based on the out-of-box contact content type, adding a new text site column called TwitterName.
MMContacts—This list uses a custom site content type called MMContact. The content type inherits from the base contact content type, adding a new Managed Metadata field called Region.
Figure 1: Clients list
Figure 2: Projects list
Figure 3: TimeCards list
Getting Started With LINQ to SharePoint
To be able to write actual queries with LINQ to SharePoint, you need to start by having classes you can write queries against. If you’ve worked with LINQ to SQL, for instance, you’ll be familiar with the feature in Visual Studio to select LINQ to SQL Classes from the Add New Item dialog box. This provides you with a blank canvas onto which you can drag items from a SQL data connection. Under the covers, this design surface is calling an API to generate a DataContext with strongly typed entities, and you can do the same from a command-line executable called SQLMetal.exe, which is typically found in the Windows SDK folder (e.g., %ProgramFiles(x86)%Microsoft SDKsWindowsv7.0ABin). This command-line tool generates an output file containing the DataContext class and all the entity classes it requires (one for each database table you pull onto the designer).
LINQ to SharePoint works in a similar way, with a command-line tool called SPMetal.exe (found in %ProgramFiles%Common FilesMicrosoft Sharedweb server extensions14BIN). One difference between LINQ to SQL and LINQ to SharePoint right now, however, is the lack of a design surface and associated Add New Item entry. Although not a serious impediment, this missing feature does mean we need to work with the command-line tool directly, at least until Microsoft provides us with a designer (likely coming in a future release). Community options are available that do integrate with Visual Studio, such as the Community Kit for SharePoint and SPMetalExtender. However, we’ll be using the out-of-box tools for the purposes of this article series, in part because the community tools might not always be available to you and in part because most of these tools are simply calling SPMetal anyway—so it’s worthwhile to know what’s happening under the hood.
To start, open a command prompt and navigate into SharePoint’s program files directory (found at C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14, sometimes called the “14 hive”). In this directory, navigate into the bin subdirectory. If you want to see the available options, you can type SPMetal.exe /? and press Enter to see all the parameters you can specify. We’ll look at these options in more detail later, but we’ll keep things simple to start. Enter the following command to specify the minimum required parameters:
SPMetal.exe /web:http://localhost/code:C:SamplesLTSDataContext.cs
In this example, I am pointing to a web application hosted at http://localhost and c:samples is the folder I want the output file to go in to. You will need to change these to match your environment. Then, create a regular Console project. Add a reference to the Microsoft.SharePoint.dll, as well as to Microsoft.SharePoint.LINQ.dll (both can be found in the ISAPI folder in the 14 hive). Next, add the generated file to the project and open Program.cs to begin writing some LINQ to SharePoint code. In this example, we’ll use two of the lists from our sample consulting firm application.
using (LTSDataContextDataContext dataContext = new LTSDataContextDataContext("http://localhost")) { var projects = from p in dataContext.Projects where p.Client.City == "Cape Town" select new { Name = p.Title, ClientName = p.Client.Title, Budget = p.BudgetHours }; Console.WriteLine(projects.Count()); }
In a nutshell, that’s all there is to it—you can now continue to write more queries and be more productive with your SharePoint development. However, let’s go further and discuss how the DataContext is generated, what the output is, and how we can modify, customize, and extend it.
Examining the Generated DataContext
Figure 4 shows a closer examination of our query. Notice that we have a collection of projects, defined as having various properties, such as Title and BudgetHours, as well as a more complex object entity called Client. The link to Client is a representation of the relationship in our data source. It also has properties, such as City and Title. Finally, notice that all of these objects and properties hang off of the dataContext variable.
Figure 4: Examining the generated DataContext
If you open the generated DataContext, you can see that it contains anywhere from hundreds to more likely even a few thousand lines of code, depending on your SharePoint site. However, it essentially consists of the core DataContext itself and a series of classes representing SharePoint content types and list instances. The DataContext inherits from Microsoft.SharePoint.LINQ.DataContext; this base class does all the heavy lifting of producing CAML statements, executing queries, and dealing with the results—so from our perspective, the generated DataContext is relatively simple. The generated entity classes also contain little behavior and are mostly containers with properties. In fact, we could bypass the code generation altogether and write our DataContext and entity classes by hand, as in Listing 1.
Writing the DataContext by hand lets us completely control exactly what it contains. However, this is decidedly impractical because the number of classes and the complexity of each one grows—and it makes little sense to write by hand a few thousands lines of code that could easily be generated. Let’s instead look at how we can bend the code generator to our will and customize its output, starting with the missing namespace.
Customizing the Generated DataContext
To customize the generated DataContext, there are a few options that can be specified from the command-line call to SPMetal.We could simply edit the generated file, but this would contravene the intimidating warning at the top and is dangerous because it would be overwritten every time we regenerate. Instead we have other safer approaches. These include, for instance, specifying a namespace using the /namespace switch. The language for the output can be specified using the /language switch, but this is unnecessary because the tool will detect it based on the extension of the file you specify as the target for the output. You can also produce a slightly improved name for the context by being aware that it assumes the name of the file as a basis and appends DataContext (unfortunately, it does so without first checking if you’ve asked for your filename to end that way, as our earlier example shows with the duplicated DataContext name). That’s about the extent of the customization available from the command line, but thankfully the tool also gives us the option to specify an entirely separate file containing far more interesting customization. The format for this file is defined in the MSDN documentation; for detailed information about each available element, see “SPMetal Parameters XML Schema.”
The following sample file provides an overview of the file’s contents, structure, and options:
Before we examine the contents of this file, here’s a tip: By default, this file should have IntelliSense enabled for the schema in Visual Studio. If it doesn’t come up correctly, you’ll need to add a reference to the XSD schema. To do so, go to the Properties tool window with the file open in the Visual Studio editor, select the Schemas property, and add a reference to the schema file located at %ProgramFiles%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATEXMLSPMetalParameters.xsd.
Now, let’s examine our parameters file. It references a site containing our Clients and Projects list, a TimeCards list for logging time against each project, and a few other lists to highlight other aspects of what the parameters file can and can’t do.
To start, notice that there’s a high-level web element. This represents the SPWeb instance against which the context will be pointed, and it lets us customize the outputted DataContext itself. We can use the Class attribute to define the name for the generated DataContext class, so we’re no longer dependent on the filename. It also lets us specify the visibility of the DataContext (i.e., whether the class will be public or internal to the assembly). This gives us some level of modification, but at this point the SPMetal tool will still generate a class for every single list in our site—which we’re unlikely to want. We might also be dissatisfied with the structure of some or all of the individual classes for our lists and content types and want to modify them.
To begin rectifying things, we’ll instruct the generator to exclude all the lists by default and include only those that we explicitly specify. We do this by first adding the ExcludeOtherLists element, and then adding a List element for each list we do want to include.
We can also make modifications on the List elements, such as changing the name of the collection property in the DataContext, the name of the entity in each list, and the access modifier. Finally, we can make changes to the content types defined in our site. The content type changes are available at two levels, as the sample file shows; for web-level content types, we use the ContentType element defined in the root web element in the parameters file—but we can also change the details of a content type within the scope of a specific list. In our sample system, the web contains a content type called Contact2 that derives from the out-of-box contact content type, adding a new field called TwitterName. The parameters file changes the definition so that the generated class will instead have a property named Twit to represent this field. However, we’ve further specified that when this content type is used within the TestContacts3 list, we want to explicitly remove this field.
The next step is having the SPMetal tool use our new parameters file, as well as the namespace we choose. We do this with a command such as the following:
SPMetal.exe /web:http://localhost/ /code:"c:DevelopmentLTSDataContext.cs" /parameters:"c:DevelopmentParameters.xml" /namespace:SPLINQDomain
You might need to run this command quite a few times during the development phase of your project. Therefore, you should put the command into a .bat file in the project source code, or perhaps define it as an option on your Visual Studio Tools menu (select External Tools to add a definition). You might even go so far as to include the regeneration of your DataContext as part of the build process for the project, which will highlight schema discrepancies made across the team during compile time—one of the goals we’re specifically trying to address by using LINQ to SharePoint in the first place.
Now, let’s examine the code that was generated for the DataContext class based on our new parameters file and focus on a few interesting elements. The DataContext class itself can be found in Listing 2.
You can see an EntityList property generated for each of the List elements we defined. There are some important differences in the definitions of these EntityLists. The SPProContacts EntityList has the name change we requested; the type of list element is also named in a more reasonable way. In fact, you can see that where we haven’t specified a type name, the name defaults to the name of the list itself concatenated with the underlying content type the tool identifies. For instance, the clients list is based on the custom list template and therefore contains only the item content type with some modifications. The items in the clients EntityList are therefore of type ClientsItem. In contrast, the TestContactsList class was based on the out-of-box contacts list definition, thereby containing only the contact content type, so that its entities are of type TestContactsListContact.
Also of interest is the TestContacts2 list. This list is also based on the out-of-box contacts list, but it has the item content type added to the available content types in the list, so the SPMetal tool defaults to the lowest common denominator in the content type hierarchy for the list. This is another good reason not to combine completely disparate types of content (and thus disparate content types) in the same SharePoint list. The fix to this latter issue is simply to tell SPMetal that you’re only interested in the derived content type, which you do by adding the 'Type="Contact"' (or whichever content type you need to specify) attribute to the list element.
Finally, you can see an attribute decorating each EntityList property. This attribute ties the property back to its underlying list even if you’ve asked for the property name to change; the same behavior follows through to the remaining classes and properties in this file.
Aside from the DataContext class itself, notice that the generated file also contains a class definition to match SharePoint’s item content type. Listing 3 shows this class, appropriately called Item; it contains properties representing some basic elements likely to be of value in your LINQ to SharePoint projects, such as the ID of an item, its version, and others.
Notice also that this class implements the INotifyPropertyChanging and INotifyPropertyChanged interfaces to assist in data binding, as well as performing change tracking for all derived entities via the ITrackOriginalValues and ITrackEntityState interfaces, property change notification and tracking fields, properties, and methods. This item class also has a number of attributes that are of interest. The first maps this generated class to the base SharePoint content type via the ContentType attribute, specifying both the name and ID as it’s stored in SharePoint. The class also explicitly defines all the other types in this code file that derive from it, and we can navigate this hierarchy to see how other types have been generated.
Finally, let’s travel down the inheritance hierarchy to see how our customized SPProContact class looks. First, we need to look at the Contact2 class. We can see that it derives from Contact but that it has the new Twit property, as follows:
private string _twit; ... [Microsoft.SharePoint.LINQ.ColumnAttribute(Name="TwitterName", Storage="_twit", FieldType="Text")] public virtual string Twit { get { return this._twit; } set { if ((value != this._twit)) { this.OnPropertyChanging("Twit", this._twit); this._twit = value; this.OnPropertyChanged("Twit"); } } }
This property wraps the TwitterName column as it’s defined in SharePoint in our Content Types gallery on the Contact2 content type. Within our final derived class, SPProContact, we see that it too has a Twit property, even though we requested that this be removed in our parameters file. Closer examination reveals that, although the property does exist, it does so to ensure that it doesn’t get used accidently, as the following code shows:
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [Microsoft.SharePoint.LINQ.RemovedColumnAttribute()] public override string Twit { get { throw new System.InvalidOperationException("Field TwitterName was removed from content type Contact2."); } set { throw new System.InvalidOperationException("Field TwitterName was removed from content type Contact2."); } }
Till Next Time
This article is the first stage in a journey to examine data access in SharePoint 2010 in more detail. We’ve kicked off our travels by looking at LINQ to SharePoint—in particular, understanding the value that LINQ brings to the SharePoint developer and how to get started creating and customizing the DataContext class. In the next article, we’ll complete the final part of our examination of the DataContext class by looking at other ways to extend and complement it. We’ll also start looking at the structure and impact of different kinds of queries to see their effects on logging and performance, as well as how to optimize them. If you have any questions about anything we’ve covered so far, or suggestions for future discussion, please email me at [email protected].
[Author’s Note: Special thanks to Juan Larios for his technical review.]
Listing 1:
using Microsoft.SharePoint.LINQ;
using System.Collections.Generic;
namespace SPLINQDomain.Domain.HandCrafted
{
public class HandCraftedDataContext : DataContext
{
public HandCraftedDataContext(string webUrl) : base(webUrl) { }
[ListAttribute(Name = "Clients")]
public EntityList Clients
{
get
{
return base.GetList("Clients");
}
}
}
[ContentTypeAttribute(Name = "Item", Id = "0x01")]
public partial class Client
{
private System.Nullable _id;
private string _title;
private System.Nullable _version;
private string _path;
private string _contact;
private string _businessPhone;
public Client() { }
[ColumnAttribute(Name = "ID", Storage = "_id", ReadOnly = true, FieldType = "Counter")]
public System.Nullable Id
{
get
{
return this._id;
}
set
{
if ((value != this._id))
{
this._id = value;
}
}
}
[ColumnAttribute(Name = "Title", Storage = "_title", Required = true, FieldType = "Text")]
public virtual string Title
{
get
{
return this._title;
}
set
{
this._title = value;
}
}
[ColumnAttribute(Name = "owshiddenversion", Storage = "_version", ReadOnly = true, FieldType = "Integer")]
public System.Nullable Version
{
get
{
return this._version;
}
set
{
if ((value != this._version))
{
this._version = value;
}
}
}
[ColumnAttribute(Name = "FileDirRef", Storage = "_path", ReadOnly = true, FieldType = "Lookup", IsLookupValue = true)]
public string Path
{
get
{
return this._path;
}
set
{
if ((value != this._path))
{
this._path = value;
}
}
}
[ColumnAttribute(Name = "Contact", Storage = "_contact", FieldType = "Text")]
public string Contact
{
get
{
return this._contact;
}
set
{
this._contact = value;
}
}
[ColumnAttribute(Name = "WorkPhone", Storage = "_businessPhone", FieldType = "Text")]
public string BusinessPhone
{
get
{
return this._businessPhone;
}
set
{
if ((value != this._businessPhone))
{
this._businessPhone = value;
}
}
}
public override string ToString()
{
return this.Title + " (Contact: " + this.Contact + " - " + this.BusinessPhone + ")";
}
}
}
Listing 2:
public partial class LTSDataContext : Microsoft.SharePoint.LINQ.DataContext {
#region Extensibility Method Definitions
partial void OnCreated();
#endregion
public LTSDataContext(string requestUrl) :
base(requestUrl) {
this.OnCreated();
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="Clients")]
public Microsoft.SharePoint.LINQ.EntityList Clients {
get {
return this.GetList("Clients");
}
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="MMContacts")]
public Microsoft.SharePoint.LINQ.EntityList MMContacts {
get {
return this.GetList("MMContacts");
}
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="Projects")]
public Microsoft.SharePoint.LINQ.EntityList Projects {
get {
return this.GetList("Projects");
}
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="TestContacts2")]
public Microsoft.SharePoint.LINQ.EntityList TestContacts2 {
get {
return this.GetList("TestContacts2");
}
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="TestContacts3")]
public Microsoft.SharePoint.LINQ.EntityList SPProContacts
{
get {
return this.GetList("TestContacts3");
}
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="TestContactsList")]
public Microsoft.SharePoint.LINQ.EntityList TestContactsList {
get {
return this.GetList("TestContactsList");
}
}
[Microsoft.SharePoint.LINQ.ListAttribute(Name="TimeCards")]
public Microsoft.SharePoint.LINQ.EntityList TCards {
get {
return this.GetList("TimeCards");
}
}
}
Listing 3:
///
/// Create a new list item.
///
[Microsoft.SharePoint.LINQ.ContentTypeAttribute(Name="Item", Id="0x01")]
[Microsoft.SharePoint.LINQ.DerivedEntityClassAttribute(Type=typeof(ClientsItem))]
[Microsoft.SharePoint.LINQ.DerivedEntityClassAttribute(Type=typeof(Contact))]
[Microsoft.SharePoint.LINQ.DerivedEntityClassAttribute(Type=typeof(ProjectsItem))]
[Microsoft.SharePoint.LINQ.DerivedEntityClassAttribute(Type=typeof(TimeCardsItem))]
public partial class Item : Microsoft.SharePoint.LINQ.ITrackEntityState, Microsoft.SharePoint.LINQ.ITrackOriginalValues, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging {
private System.Nullable _id;
private System.Nullable _version;
private string _path;
private Microsoft.SharePoint.LINQ.EntityState _entityState;
private System.Collections.Generic.IDictionary _originalValues;
private string _title;
#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate();
partial void OnCreated();
#endregion
Microsoft.SharePoint.LINQ.EntityState Microsoft.SharePoint.LINQ.ITrackEntityState.EntityState {
get {
return this._entityState;
}
set {
if ((value != this._entityState)) {
this._entityState = value;
}
}
}
System.Collections.Generic.IDictionary Microsoft.SharePoint.LINQ.ITrackOriginalValues.OriginalValues {
get {
if ((null == this._originalValues)) {
this._originalValues = new System.Collections.Generic.Dictionary();
}
return this._originalValues;
}
}
public Item() {
this.OnCreated();
}
[Microsoft.SharePoint.LINQ.ColumnAttribute(Name="ID", Storage="_id", ReadOnly=true, FieldType="Counter")]
public System.Nullable Id {
get {
return this._id;
}
set {
if ((value != this._id)) {
this.OnPropertyChanging("Id", this._id);
this._id = value;
this.OnPropertyChanged("Id");
}
}
}
[Microsoft.SharePoint.LINQ.ColumnAttribute(Name="owshiddenversion", Storage="_version", ReadOnly=true, FieldType="Integer")]
public System.Nullable Version {
…
}
[Microsoft.SharePoint.LINQ.ColumnAttribute(Name="FileDirRef", Storage="_path", ReadOnly=true, FieldType="Lookup", IsLookupValue=true)]
public string Path {
…
}
[Microsoft.SharePoint.LINQ.ColumnAttribute(Name="Title", Storage="_title", Required=true, FieldType="Text")]
public virtual string Title {
…
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public event System.ComponentModel.PropertyChangingEventHandler PropertyChanging;
protected virtual void OnPropertyChanged(string propertyName) {
if ((null != this.PropertyChanged)) {
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
protected virtual void OnPropertyChanging(string propertyName, object value) {
if ((null == this._originalValues)) {
this._originalValues = new System.Collections.Generic.Dictionary();
}
if ((false == this._originalValues.ContainsKey(propertyName))) {
this._originalValues.Add(propertyName, value);
}
if ((null != this.PropertyChanging)) {
this.PropertyChanging(this, new System.ComponentModel.PropertyChangingEventArgs(propertyName));
}
}
}
About the Author
You May Also Like