Tag Archives: C#

Sorting a generic List object by passing an IComparer object to the Sort method doesn’t preserve order of items that are equal.

List<ArtifactDetails> recordset = GetArtifactsFromProvider();
recordset.Sort(new Core.Comparer<ArtifactDetails>(sortExpression));

http://msdn.microsoft.com/en-us/library/234b841s(VS.80).aspx

This method uses System.Array.Sort, which uses the QuickSort algorithm. This implementation performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.

http://www.csharp411.com/c-stable-sort/

http://en.wikipedia.org/wiki/Stable_sort#Classification

What are our options if we want a stable sort?

  • Write our own sort algorithm that is a stable sort so that the original order is preserved. There are several n log n sorts that are stable sorts; they take more memory than the QuickSort though.

Or if we don’t want the order to change if the sortExpression was empty?

  • Check for an empty string and use a default sort order like uid or whatever the primary key is.
  • Check sortExpression for an empty string and just not call sort.
  • Write a SortList function that does the checking for an empty sortExpression.
protected static List<T> SortList<T>(List<T> list, string sortExpression)
{
    if (!string.IsNullOrEmpty(sortExpression)) list.Sort(new Core.Comparer<T>(sortExpression));
    return list;
}

Then instead of using the Sort call at the top of this post use this call.

List<ArtifactDetails> recordset = GetArtifactsFromProvider();
recordset = SortList(recordset, sortExpression);

Are there other better options? Maybe, but this gets the job done without a lot of extra code.

I just learned something new and wanted to share. You have an asp.net GridView and in that GridView you have a Point of Contact column that shows the primary point of contact. In view mode you want to see the contacts Name, but in edit mode you want a DropDownList with all contacts for a given system, so that you can assign a new primary point of contact. A point of contact can be removed from the system, but can still be listed as the primary until he is replaced.

So you bind the SelectedValue of the DropDownList to the Id of the primary contact stored for that system. What happens when that contact is removed from the system? The DropDownList throws an error because the SelectedValue that you are binding doesn’t exist in the data source.

There are a couple of ways to fix this problem. First, the stored procedure can be changed to check for this condition and union the contacts with the old primary contact. Second, you can pass the contact Id from the GridView to the ObjectDataSource of the DropDownList that is inside the EditItemTemplate of the GridView. Lets say for the sake of argument that you don’t have access to the stored procedures, so you must do this union in code.

Here is the code I used to solve this problem.

First up is the GridView code from the ascx file. Notice a few things. First, the GridView is using the OnRowCreated event. Second, the SelectedValue of the DropDownList is being bound to the contactId of the GridView’s data source. The ObjectDataSource for the DropDownList has two SelectParameters, one for the contactId and one for systemId. There is also a RequiredFieldValidator to make sure the DropDownList doesn’t have a value of -1 selected. In the database -1 is an invalid index. This is important later while getting the data for the DropDownList.

<asp:GridView ID="GridViewVehicles" runat="server"
              OnRowCreated="GridViewVehicles_RowCreated"
              DataSourceID="ObjectDataSourceMain">
    <Columns>
        <asp:TemplateField HeaderText="Point of Contact">
            <ItemTemplate>
                <asp:Literal ID="LitPoc" runat="server" Text='<%# Eval("ContactPoc") %>'/>
            </ItemTemplate>
            <EditItemTemplate>
                <asp:DropDownList ID="DropDownListPoc" runat="server"
                                  DataSourceID="ObjectDataSourcePoc" DataTextField="Name" DataValueField="Id"
                                  SelectedValue='<%# Bind("ContactIdPoc") %>'/>
                <asp:ObjectDataSource ID="ObjectDataSourcePoc" runat="server"
                                      TypeName="Bll.VehicleControlBll"
                                      SelectMethod="GetRolesAndCurrentContactBySystemAndContact">
                    <SelectParameters>
                        <asp:Parameter Name="contactId" Type="Int32"/>
                        <asp:Parameter Name="systemId" Type="Int32"/>
                    </SelectParameters>
                </asp:ObjectDataSource>
                <asp:RequiredFieldValidator ID="ValidatorPoc" runat="server"
                                            ErrorMessage="Not a valid selection." ControlToValidate="DropDownListPoc"
                                            InitialValue="-1" Display="Dynamic" SetFocusOnError="True"/>
            </EditItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Now on to the C# code. In the RowCreated event, check to make sure this is a DataRow and not a header or footer row. Make sure the data for this row isn’t null, and then set the select parameters to that data item’s values. Now the ObjectDataSource has the correct values for this row.

The DropDownList’s ObjectDataSource is used next, and the following code shows the methods used to populate that. First the contacts are retrieved from the DAL for the given system, and then that List object is searched for the passed in contactId, if it’s not found then another DAL call is made to get that contact’s object. The name gets a “*” attached to signify this contact is no longer a POC for this system. Then a new contact is created with an Id of -1 and a name of “Pick Contact” and inserted at the beginning of the List. So now the List object passed to the ObjectDataSource holds an invalid value incase no nothing had been selected before (Ex. creating a new system), holds all the contacts for a system and in case the already assigned primary contact was removed, that contact is added to the list as well.

protected void GridViewVehicles_RowCreated(object sender, GridViewRowEventArgs e)
{
	if (e.Row.RowType != DataControlRowType.DataRow) return;

	VehicleDetails vehicle = e.Row.DataItem as VehicleDetails;

	if (vehicle == null) return;

	ObjectDataSource odsPoc = e.Row.FindControl("ObjectDataSourcePoc") as ObjectDataSource;
	if (odsPoc != null)
	{
		odsPoc.SelectParameters["contactId"].DefaultValue = vehicle.ContactIdPoc;
		odsPoc.SelectParameters["systemId"].DefaultValue = vehicle.SystemId;
	}
}

public List<ContactDetails> GetRolesAndCurrentContactBySystemAndContact(int contactId, int systemId)
{
	List<ContactDetails> contacts = AppendCurrentContact(GetRolesBySystem(systemId), contactId);
	ContactDetails pickA = new ContactDetails();
	pickA.Id = -1;
	pickA.Name = "Pick Contact";
	contacts.Insert(0, pickA);
	return contacts;
}

private List<ContactDetails> AppendCurrentContact(List<ContactDetails> contacts, int contactId)
{
	ContactDetails contact = contacts.Find(delegate(ContactDetails details)
                                           { return details.Id == contactId; });
	if (contact == null)
	{
		ContactDetails newContact = VehicleControlDal.GetContactById(contactId);
		if (newContact != null)
		{
			newContact.Name = newContact.Name + "*";
			contacts.Add(newContact);
		}
	}

	return contacts;
}