Tuesday, June 29, 2010

SharePoint List GetDataTable Extension Methods

In SharePoint there's a SPList.Items.GetDataTable() method returning a DataTable with all list items. However the DataTable doesn't include all hidden fields. This may cause problems if you need to access some of those hidden fields. For example in one of our SharePoint solutions, we have custom caching mechanism relying on SPListItems' last Modified time stored in the hidden Modified field.

To resolve the problem I wrote some extension methods, and all SharePoint list data, no matter hidden or not, will be returned with a simple call of SPList.GetDataTableWithHiddenFields().

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Microsoft.SharePoint;

public static class SPListExtension
{
private const uint SPQueryItemLimit = 2000;

/// <summary>
/// Get a .NET type for SPField
/// </summary>
private static Type GetFieldType(SPField field)
{
Type type = Type.GetType("System.String");

SPFieldType fieldType = field.Type;
switch (fieldType)
{
case SPFieldType.Integer:
case SPFieldType.Counter:
type = Type.GetType("System.Int32");
break;
case SPFieldType.Text:
case SPFieldType.Note:
case SPFieldType.Choice:
case SPFieldType.Lookup:
case SPFieldType.Boolean:
type = Type.GetType("System.String");
break;
//case SPFieldType.Boolean:
// type = Type.GetType("System.Boolean");
// break;
case SPFieldType.DateTime:
type = Type.GetType("System.DateTime");
break;
case SPFieldType.Number:
case SPFieldType.Currency:
type = Type.GetType("System.Double");
break;
default:
type = Type.GetType("System.String");
break;
}
return type;
}

/// <summary>
/// Remove IDs from lookup fields
/// "1;#CODE1" => "CODE1"
/// Skip for LookupMulti fields
/// </summary>
public static string TryGetLookupCode(string value)
{
string reval = value;
if (!string.IsNullOrEmpty(value))
{
string[] valueSplit = value.Split(new string[] { ";#" },
StringSplitOptions.RemoveEmptyEntries);
int number;
if (valueSplit.Length == 2 && int.TryParse(valueSplit[0], out number) && number >= 0)
{
return valueSplit[1];
}
}
return reval;
}

/// <summary>
/// Extension method for generating DataTable based on SPList schema
/// </summary>
public static DataTable GetDataTableSchema(this SPList list)
{
string[] exludedColumns = new string[] { };
return GetDataTableSchema(list, exludedColumns);
}

/// <summary>
/// Dynamically create a DataTable based on SPList schema
/// </summary>
public static DataTable GetDataTableSchema(this SPList list, string[] excludedColumns)
{
bool hasExcludedColumns = (excludedColumns != null && excludedColumns.Length > 0);
DataTable dataTable = new DataTable(list.Title);
foreach (SPField field in list.Fields)
{
Type fieldType = GetFieldType(field);
if (!hasExcludedColumns || !excludedColumns.Contains(field.InternalName,
StringComparer.InvariantCultureIgnoreCase))
{
dataTable.Columns.Add(field.InternalName, fieldType);
}
}
return dataTable;
}

/// <summary>
/// Extension method to get DataTable from SPList with hidden fields
/// </summary>
public static DataTable GetDataTableWithHiddenFields(this SPList list)
{
string[] exludedColumns = new string[] { };
return GetDataTableWithHiddenFields(list, exludedColumns);
}

/// <summary>
/// SPList.Items.GetDataTable doesn't include hidden fields
/// This extension method returns all fields' data
/// </summary>
public static DataTable GetDataTableWithHiddenFields(this SPList list, string[] excludedColumns)
{
if (list.ItemCount == 0)
return null;

bool hasExcludedColumns = excludedColumns != null && excludedColumns.Length > 0;
DataTable dataTable = new DataTable(list.Title);
Dictionary<string, SPFieldType> fieldDict = new Dictionary<string, SPFieldType>();
List<SPListItemCollection> allListItems = new List<SPListItemCollection>();

// Generate DataTable
foreach (SPField field in list.Fields)
{
if (!hasExcludedColumns || !excludedColumns.Contains(field.InternalName,
StringComparer.InvariantCultureIgnoreCase))
{
fieldDict.Add(field.InternalName, field.Type);
Type type = GetFieldType(field);
dataTable.Columns.Add(field.InternalName, type);
}
}

// Query data
SPQuery spQuery = new SPQuery();
spQuery.RowLimit = SPQueryItemLimit;
spQuery.ViewAttributes = "Scope=\"Recursive\"";
do // Get all list items
{
SPListItemCollection itemCollection = list.GetItems(spQuery);
if (itemCollection != null && itemCollection.Count > 0)
allListItems.Add(itemCollection);
spQuery.ListItemCollectionPosition = itemCollection.ListItemCollectionPosition;
} while (spQuery.ListItemCollectionPosition != null);

// Populate DataTable with query results
Type stringType = Type.GetType("System.String");
foreach (SPListItemCollection listItems in allListItems)
{
foreach (SPListItem item in listItems)
{
DataRow dr = dataTable.NewRow();
foreach (DataColumn column in dataTable.Columns)
{
string columnName = column.ColumnName;
if (!Convert.IsDBNull(item[columnName]) && item[columnName] != null)
{
SPFieldType fieldType = fieldDict[columnName];
if (fieldType == SPFieldType.Boolean)
{
dr[columnName] = Convert.ToBoolean(item[columnName]) ? "1" : "0";
}
else if (fieldType == SPFieldType.Lookup || fieldType == SPFieldType.Invalid)
{
dr[columnName] = TryGetLookupCode(item[columnName].ToString());
}
else if (column.DataType == stringType)
{
dr[columnName] = item[columnName].ToString();
}
else
{
dr[columnName] = item[columnName];
}
}
}
dataTable.Rows.Add(dr);
}
}
return dataTable;
}
}

Note: the optional "excludedColumns" parameter defines the fields that you don't want to include in the DataTable.

Monday, June 21, 2010

Intall SharePoint 2010 In Windows 7 Virtual Machine

SharePoint 2010 RTM is out there for about two months. I didn't see any issue during the installation on my desktop with Windows 2008 R2. I also wanted to setup a SP2010 environment in my laptop with Windows 7 64-bit. Direct installation of this mess product on my Windows 7 was not an option for me, so I decided to install SP2010 to a virtual machine.

The problem came up because Windows 7 doesn't support Hyper-V and Windows Virtual PC doesn't support 64-bit system, but SP2010 must run in 64-bit environment. So you can not install SP2010 in a virtual machine run inside Windows 7 with Microsoft products. What a Broken As Designed (B.A.D.)!

The solutions go to non-Microsoft products. My first try, the free VMWare Server version 2.0.1 (http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=751fa0d1-356c-4002-9c60-d539896c66ce), was successful without big issues. Following are steps of my installation:

1. Install VMWare Server 2.0.1 with default settings.
2. Go to VMWare Web Access admin page (http://machineName:8222/ui) and select "Virtual Machine -> Create Virtual Machine" with following settings:
  • Guest OS: "Microsoft Windows Server 2008 (64-bit)" (I want to install 2008 R2 initially but it's not included in the dropdown list).
  • Memory size: 2G, Processors count: 2.
  • "Create a New Virtual Disk" with disk capacity of 40G. "Policies -> Optimize for performance" on the same disk configuration page.
  • "Add a Network Adapter" with default "Bridged" network connection.
  • "Use a Physical Drive"
  • "Don't Add a Floppy Drive"
  • "Add a USB Controller"
3. Insert Windows Server 2008 SP2 64-bit DVD and Power on the newly created VM instance, follow the instruction to complete the Windows Server 2008 SP2 installation.
4. Activate Windows Server 2008 SP2 and install import updates.
5. Install SQL Server 2008 SP1 64-bit.
6. Install SharePoint 2010 RTM using "Server Farm" and "Complete" setting.
6.1 Skip this step if you run the SharePoint with domain account.
I want to use a local account to run SP2010 in my VM, but it's not allowed by default. So I need to install SharePoint configuration database using PowerShell. Click SharePoint 2010 management shell, Type "New-SPConfigurationDatabase", it failed and prompted something like SQL Server version is too low. Google and install SQL Server 2008 Cumulative update(http://support.microsoft.com/kb/963036). Rerun "New-SPConfigurationDatabase" but still failed with message of "User does not exist or is not unique...". Google and found it's related to Administrator account. Create a separate "SVC_SharePoint" account to connect to the database, then run "New-SPConfigurationDatabase" with success.
7. Run SharePoint configuration Wizard
8. Install important updates
9 (optional). Install Visual Studio 2010
10 (optional). Install Office 2010

The total disk usage after all above installation is about 29G, and the memory consumption after reboot is about 1G. The whole VM is installed in an external USB 2.0 hard disk. The speed is satisfactory.