Wednesday, 30 January 2008

DropDownList Extension Methods

I was doing a fair bit of work with drop down lists in ASP .NET the other day and seemed to be constantly writing code to select items in the list, then I realised that this code would be ideal for some extension methods. So I came up with the following:

public static class DropDownListExtension
{
/// <summary>
///
Populates the specified drop down list with the names of the specified enum type.
/// </summary>
/// <param name="dropDownList">
The drop down list to populate.</param>
/// <param name="enumType">
Type of the enum.</param>
public static void PopulateWithEnum<T>(this DropDownList dropDownList)
{
DropDownListExtension.PopulateWithEnum<T>(dropDownList, true, true);
}

/// <summary>
///
Populates the specified drop down list with the names of the specified enum type.
/// </summary>
/// <param name="dropDownList">
The drop down list to populate.</param>
/// <param name="enumType">
Type of the enum.</param>
/// <param name="clearItems">
if set to <c>true</c> clear any existing items in the list.</param>
/// <param name="insertSpaces">
if set to <c>true</c> insert spaces between capital letters for readability in UI.</param>
public static void PopulateWithEnum<T>(this DropDownList dropDownList, bool clearItems, bool insertSpaces)
{
if (clearItems)
{
dropDownList.Items.Clear();
}

foreach (var name in Enum.GetNames(typeof(T)))
{
ListItem item = new ListItem()
{
Text = insertSpaces ? Format.SplitPascalCase(name) : name,
Value = name
};

dropDownList.Items.Add(item);
}
}

/// <summary>
///
Gets the selected value and parses it as the specified enum type.
/// </summary>
/// <typeparam name="T">
The type of enum to return.</typeparam>
/// <param name="dropDownList">
The drop down list.</param>
/// <returns>
An enum value of the specified type.</returns>
public static T GetSelectedValueAsEnum<T>(this DropDownList dropDownList)
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException(string.Format("{0} is not a valid enum.", typeof(T).Name));
}

return (T)Enum.Parse(typeof(T), dropDownList.SelectedValue);
}

/// <summary>
///
Sets the selected item by finding the specified value in the drop down list.
/// </summary>
/// <typeparam name="T">
The type of the value to set.</typeparam>
/// <param name="dropDownList">
The drop down list.</param>
/// <param name="value">
The value.</param>
/// <returns>
/// <c>
true</c> if the data selection was successful; otherwise <c>false</c>
/// </returns>
public static bool SetSelectedValue<T>(this DropDownList dropDownList, T value)
{
return SetSelectedValue<T>(dropDownList, value, CultureInfo.CurrentUICulture);
}

/// <summary>
///
Sets the selected item by finding the specified value in the drop down list.
/// </summary>
/// <typeparam name="T">
The type of the value to set.</typeparam>
/// <param name="dropDownList">
The drop down list.</param>
/// <param name="value">
The value.</param>
/// <param name="culture">
The culture to format the string to search by.</param>
/// <returns>
/// <c>
true</c> if the data selection was successful; otherwise <c>false</c>
/// </returns>
public static bool SetSelectedValue<T>(this DropDownList dropDownList, T value, CultureInfo culture)
{
bool success = false;
if (value != null)
{
ListItem item = dropDownList.Items.FindByValue(string.Format(culture, "{0}", value));
if (item != null)
{
dropDownList.SelectedIndex = dropDownList.Items.IndexOf(item);
success = true;
}
}

return success;
}

/// <summary>
///
Sets the selected item by finding the specified text value in the drop down list.
/// </summary>
/// <typeparam name="T">
The type of the text value to set.</typeparam>
/// <param name="dropDownList">
The drop down list.</param>
/// <param name="text">
The text to find in the list.</param>
/// <returns><c>
true</c> if the data selection was successful; otherwise <c>false</c></returns>
public static bool SetSelectedText<T>(this DropDownList dropDownList, T text)
{
return SetSelectedText<T>(dropDownList, text, CultureInfo.CurrentUICulture);
}

/// <summary>
///
Sets the selected item by finding the specified text value in the drop down list.
/// </summary>
/// <typeparam name="T">
The type of the text value to set.</typeparam>
/// <param name="dropDownList">
The drop down list.</param>
/// <param name="text">
The text.</param>
/// <param name="culture">
The culture to format the string to search by.</param>
/// <returns><c>
true</c> if the data selection was successful; otherwise <c>false</c></returns>
public static bool SetSelectedText<T>(this DropDownList dropDownList, T text, CultureInfo culture)
{
bool success = false;
if (text != null)
{
ListItem item = dropDownList.Items.FindByText(string.Format(culture, "{0}", text));
if (item != null)
{
dropDownList.SelectedIndex = dropDownList.Items.IndexOf(item);
success = true;
}
}

return success;
}
}


These methods can be simply used to set the selected text in an ASP .NET drop down list or populate a list with the contents of an enum.



Cheers

Linq DeleteOnNull

During development of our project using Linq I came across a situation where I needed to remove a relationship between two objects, this should be simple enough right?

So I looked at the EntitySet<T> class, low and behold this had a method called Remove<T>(), excellent I thought, I'll just call that and everything will be fine. Nope, so the following code will not work the way you expect it to, you get some dodgy error about not being able to set the value to null.

                    ClubGroupMap linq = entitySet[index];
ClubGroupMapEntity entity = entities.FirstOrDefault<ClubGroupMapEntity>(c => c.Club.Id == linq.ClubID);
if (entity == null)
{
entitySet.Remove(linq);
}



Anyway, it turns out that in order to remove relationship data (stuff in a foreign key table) from the database via Linq, you need to set the parent object reference explicitly to null. For more info on this see: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1681314&SiteID=1, basically the Linq object should have the DeleteOnNull parameter of the Association attribute set to true, this them tells Linq that it is able to delete the object when the reference to its parent is null. So the code should look like this:



                    ClubGroupMap linq = entitySet[index];
ClubGroupMapEntity entity = entities.FirstOrDefault<ClubGroupMapEntity>(c => c.Club.Id == linq.ClubID);
if (entity == null)
{
entitySet.Remove(linq);
linq.Club = null;
linq.ClubGroup = null;
}



If you are using SQLMetal to generate the Linq data context on the fly (like we were) you need to make sure that the database has been configured to correctly use cascade deletes.

Linq Data Context Extension Methods

I've been working on a project using a lot of linq lately and came across a problem where the objects we were creating had unique id's that were integer values. A new object within the system would have a decrementing id value such as -1, -2, -3 for each new instance of the object type.

After working with Linq for a while it became apparent that when you used the InsertOnSubmit method (which used to be called Add in the Beta 2 version hence the confusion) the new object was not added to the data context but was actually added to the datacontext.ChangeSet collection.

We therefore needed a way to easily find objects within the data context whether they were new, modified or unchanged. I created a data context extension helper which allows the user to specify the type of object to be found and a search function (using the Func<T,TResult> delegate) to indicate which objects to search for.

The code for the DataContextExtension class is shown below:

public static class DataContextExtension
{
/// <summary>
///
Finds a linq object using the specified search function.
/// </summary>
/// <typeparam name="TLinq">
The type of linq object to be returned.</typeparam>
/// <param name="context">
The data context in which to search for the entity.</param>
/// <param name="searchFunction">
The search condition to meet in order to find the linq object.</param>
/// <returns>
///
The first or default linq object found by the search; otherwise null.
/// </returns>
/// <remarks>
This method should be used when
/// it is not known whether the linq object you are looking for is new or existing. This will
/// first search the new list, if this returns null then the existing list is searched. </remarks>
public static TLinq Find<TLinq>(this DataContext context, Func<TLinq, bool> searchFunction)
where TLinq : class
{
return Find<TLinq>(context, searchFunction, true) ??
Find<TLinq>(context, searchFunction, false);
}

/// <summary>
///
Finds a linq object using an entity id.
/// </summary>
/// <typeparam name="TLinq">
The type of the linq.</typeparam>
/// <param name="context">
The data context in which to search for the entity.</param>
/// <param name="searchFunction">
The search condition to meet in order to find the linq object.</param>
/// <param name="id">
The id.</param>
/// <returns>
///
The first or default linq object found by the search.
/// </returns>
public static TLinq Find<TLinq>(this DataContext context, Func<TLinq, bool> searchFunction, int id)
where TLinq : class
{
return Find<TLinq>(context, searchFunction, id < 0);
}

/// <summary>
///
Finds an linq object of the specified type by first searching for any objects that have been
/// added to the data context changeset or looking in the linq table for a single row that
/// meets the specified search function.
/// </summary>
/// <typeparam name="TLinq">
The type of linq object to be returned.</typeparam>
/// <param name="context">
The data context in which to search for the entity.</param>
/// <param name="searchFunction">
The search condition to meet in order to find the linq object.</param>
/// <param name="isNew">
if set to <c>true</c> the object to find is new.</param>
/// <returns>
///
The first or default linq object found by the search.
/// </returns>
public static TLinq Find<TLinq>(this DataContext context, Func<TLinq, bool> searchFunction, bool isNew)
where TLinq : class
{
ChangeSet changeSet = context.GetChangeSet();

return isNew ? Get<TLinq>(changeSet.Inserts, searchFunction)
: (Get<TLinq>(changeSet.Updates, searchFunction) ??
Get<TLinq>(changeSet.Deletes, searchFunction)) ??
context.GetTable<TLinq>().FirstOrDefault<TLinq>(searchFunction);
}

/// <summary>
///
Counts the number of linq objects of the given type within the specified context.
/// </summary>
/// <typeparam name="TLinq">
The type of the linq.</typeparam>
/// <param name="context">
The context.</param>
/// <param name="searchFunction">
The search function used to match the objects.</param>
/// <returns></returns>
public static int Count<TLinq>(this DataContext context, Func<TLinq, bool> searchFunction) where TLinq : class
{
int count = 0;

ChangeSet changeSet = context.GetChangeSet();

count += (changeSet.Inserts.Where<object>(o => o is TLinq)).Cast<TLinq>().Count<TLinq>(searchFunction);
count += context.GetTable<TLinq>().Count<TLinq>(searchFunction);
count -= (changeSet.Deletes.Where<object>(o => o is TLinq)).Cast<TLinq>().Count<TLinq>(searchFunction);

return count;
}

#region Private methods
/// <summary>
///
Gets the first or default linq object from the collection of objects using the specified
/// search function.
/// </summary>
/// <param name="objects">
The object collection to search.</param>
/// <param name="searchFunction">
The function to use to search.</param>
/// <returns>
A linq object matching the condition.</returns>
private static TLinq Get<TLinq>(IEnumerable<object> objects, Func<TLinq, bool> searchFunction)
{
return objects.Where<object>(o => o is TLinq).Cast<TLinq>().FirstOrDefault<TLinq>(searchFunction);
}
#endregion
}


Here is an example of how it can be used:



private MyLinqObject BuildMyLinqObject(int id, MyDataContext context)
{
MyLinqObject myLinq = context.Find<MyLinqObject>(m => m.Id == id);
if (myLinq == null)
{
myLinq = new MyLinqObject();
}

/* populate details as necessary */

return myLinq;
}


So you can quite easily make a simple method call and find any instance of the type of object you require that may have been created within the Linq data context.



Hope this helps



Enjoy!

Tuesday, 29 January 2008

Free Iterative Training WCF and C# course

Iterative training are offering a free evening training course http://www.iterativetraining.co.uk/courseoffering.aspx?schid=6 held by Michele Leroux Bustamante (from IDesign) on 13th February 2008, could be good - I am going.

ComboBox Databinding to List<int>

A bit of a weird one to start off the blog but here goes anyway...

I was trying to bind a List<int> to the datasource of a combo box in Windows forms C# and got the following error message "Cannot set the SelectedValue in a ListControl with an empty ValueMember."

Okay, admittedly I hadn't set the ValueMember or the DisplayMember of the combobox but what should i set them to? From what I thought was a simple thing to do suddenly caused me a little trouble..mmmm.

Anyway, a bit more investigation and I found that I was setting the "SelectedValue" property of the combobox but what I needed to be doing was setting the "SelectedItem" property of the combobox.

Wrong way:

public int ParentPosDepartmentId
{
set { this.ParentIdComboBox.SelectedValue = value; }
}


Right way:



public int ParentPosDepartmentId
{
set { this.ParentIdComboBox.SelectedItem = value; }
}


Well there is a nice simple problem to start this blog off with, I hope the issues I tackle will get a little bit more involved than this, but hey its a start :-)



Cheers