You are viewing a read-only archive of the Blogs.Harvard network. Learn more.

Posts filed under 'C# and .NET'

Visual Studio Development Tips and Tricks

This document was written as a cheat sheet for developers on my project:

Visual Studio Development Tips and Tricks Word Document

August 20th, 2006

Working with null values in the .NET Framework

With C# 2.0 comes the introduction of the nullable type as a complete and integrated solution for the nullability issue on all forms of value types.

By utilizing nullable types, we can address the nullability requirements presented on both sides of the fence. Data publishers need no longer maintain generic interfaces to allow support for nulls or handle null values and pass back symbolic representations in such instances. We can cast the results of ExecuteScalar to an int? and redefine the property signature to return int?. On the consuming side, the programmer can now use the HasValue property of int? to determine whether the returning value from the database is indeed null.

C# nullable type is essentially a structure that combines a value of the underlying type with a boolean null indicator. Nullable types possess a default constructor which accepts as an argument, an instance of the underlying type of that nullable. An instance of a nullable type has two public read-only properties: HasValue, of type bool, and Value, of the nullable type’s underlying type. HasValue is true for a non-null instance and false for a null instance. When HasValue is true, the Value property returns the contained value. When HasValue is false, an attempt to access the Value property throws an exception.

Nullable types are constructed using the ? type modifier. This token is placed immediately after the value type being defined as nullable. For instance, if we were trying to define a uint in its nullable form, we would apply the ? after making the token uint?. The type specified before the ? modifier in a nullable type is called the underlying type of the nullable type.
Any value type can be an underlying type. And a nullable indicator may also be applied to any struct. You also have the ability to create your own user defined null value types, and you do not have to do any extra work because any struct or enum you create will automatically have a nullable version of itself that can be utilized anywhere you need it. Once you have your user defined nullable type defined, you may use it as you would use any user defined type.
What folks are saying on the net…

There was some concern that precision is lost between SqlDecimal and Decimal (which isn’t an issue for Credentialing Manager). And some confusion regarding remoting/web services support (nullable types are supported, sqltypes are not). And there was also a lot of discussion about conversions, bool? and lifted operators. Its quirky so we’ll have to be aware of that (stuff like the compiler will prevent you from implicitly converting an object of a nullable type to its underlying type and a null added to a value will result in a null).

In regards to the Infragistic controls, they have a Nullable property so we should be all set. The Microsoft developers envisioned that a DBNull would go up all the way into the UI. On the databinding itself there’s a null replacement value in the advance section of the databinding property for any control. For example, if a database field is a null string, you may want to represent that as “select something” or “type here” in a text box. If that value still is there when the push is done it will actually convert it back to null, so you get the two way null support all the way back and forth.

Microsoft is committed to the success of these nullable types and it has been well tested, but we’ll be pioneering their use in our application.

Null vs. DBNull:
Do not confuse the notion of a null reference in an object-oriented programming language with a DBNull object. In C#, a null reference means the absence of a reference to an object. DBNull represents an uninitialized variant or nonexistent database column. Sadly, a null reference does not equate to a DBNull. So, the code below will fail:

int? _addUserNumber;
_addUserNumber = Convert.DBNull;

The statement below will also throw an InvalidCastException error if the data returned from the dataabse is a DBNull:

int? _addUserNumber;
_addUserNumber = (int?)data["add_user_nbr"];

Nullable Types Quick Overview:

The default value of a nullable type is an instance for which the HasValue property is false and the Value property is undefined. The default value is also known as the null value of the nullable type. An implicit conversion exists from the null literal to any nullable type, and this conversion produces the null value of the type.

private int? _addUserNumber;
_addUserNumber = (int?)data["add_user_nbr"];

But be aware that the code above will fail with a Conversion error in the case that the value returned from the database is DBNull.

One advantage is that you can evaluate a variable against a null directly.

if (_addUserNumber == null) {...}
if (_addUserNumber.HasValue) {...}
if (Convert.IsDBNull._addUserNumber) {...}

And a nullable type can be used in the same way that a regular value type can be used. This means you can assign a standard integer to a nullable integer and vice-versa:

int? nFirst = null;
int Second = 2;
nFirst = Second; // Valid
nFirst = 123; // Valid
Second = nFirst; // Also valid

Conversions between the nullable and standard version of a given value type vary based on direction. Converting from standard to nullable is always implicit whereas conversions from nullable back to standard is always explicit.

nFirst = null; // Valid
Second = nFirst; // Exception, Second is nonnullable.

Lifted operators permit the predefined and user defined operators that work on the standard value types to also work on the nullable versions of those types. But be aware that the results may not be what you would expect:

int ValA = 10;
int? ValB = null;
int? ValC = ValA * ValB; //ValC = null

int ValA = 10;
int? ValB = null;
int ValC = ValA * ValB; // ValC not nullable, exception thrown

NullableTypes vs. SqlTypes

Q: When and where to use NullableTypes and where to use System.Data.SqlTypes instead?

A: Use NullableTypes in the Business Layer: in the code that implements the application business logic (business entities, functions, services and business processes) and in public interfaces (nullable parameters, return type and properties). Use NullableTypes also in the Presentation Layer: in the code that implements the user interaction dialog (navigation, logic and presentation).

Here NullableTypes do not just shift the problem of handling nulls with built-in value-types elsewhere, they do completely solve it in an elegant OO fashion that make code more readable and maintainable.

In the Business Layer and in the Presentation Layer, NullableTypes are a valid general purpose solution: they are more reliable and efficient than every custom solution.

Do prefer System.Data.SqlTypes (or the types provided by the .NET Data Provider in use, as the structures in System.Data.OracleClient or types in Oracle.DataAccess.Types) in the Data Layer: in the code that move data from the database (with a DataAdapter or with a DataCommand) to the memory (DataSet, variables, Array, etc.) and vice versa.
This is because System.Data.SqlTypes (and types provided by other .NET Data Providers) are isomorphic with the database types while NullableTypes (as built-in types) are not.

Q: Why shouldn’t I use System.Data.SqlTypes everywhere?

A: You can, but you can also do better with NullableTypes because System.Data.SqlTypes (and types provided by other .NET Data Providers) do:
– not work with .NET Remoting
– not work with ASP.NET Web Services
– depends on SqlServer or other databases (it is not a good design to couple the Presentation Layer and the Business Layer with a specific database)

While NullableTypes:
– do work with .NET Remoting
– do work with ASP.NET Web Services (in beta now)
– are database agnostic
– have the NullConvertClass that can be used to seamlessly integrate NullableTypes with Web Server controls and WinForms controls
– have the DbNullConvert class that converts NullableTypes values to in-memory database values (Command Parameters, DataReader values, DataSet column values) and vice versa

1 comment August 14th, 2006

TreeView Example

On the Page_Load, two drop downs and an Infragistics control object are initialized. Once the user has entered the search criteria in these objects and pressed the Search button, the btnSearch_Click event is fired. This event initializes the treeview object.

When the treeview is clicked, the TreeView1_TreeNodePopulate event is fired and the appropriate node is populated with one of three possible data trees (Practitioner, Category or Privilege).

When a leaf is clicked, the TreeView1_SelectedNodeChanged event is fired. This event, populates the DetailsView with data corresponding to the leaf that was clicked.


using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using Picis.Common.Functions.Attributes;
using Picis.Common.Enumerations.CredentialingManager;
using Picis.Common.Enumerations.Framework.Security;
using Picis.Web.Framework.Components.Core;
using Picis.Web.Framework.Components.Security;
using PWACMP = Picis.Web.Applications.CredentialingManager.Practitioner;
using PWACMD = Picis.Web.Applications.CredentialingManager.Dictionaries;
using Picis.Web.Applications.Common.Images;
using Picis.Web.Applications.UserPreferences;
using Picis.Web.Exceptions.CredentialingManager.Dictionaries;
using Picis.Common.Trace;

using Infragistics.WebUI.UltraWebGrid;

public partial class PrivilegeInquiry : ControlBase
{
#region Event Handlers

protected void Page_Load(object sender, EventArgs e)
{
string jsArray = “privInqControlIds”;

if (!this.IsPostBack)
{
ddlSearchType.Items.Add(new ListItem(“Practitioner”, “1”));
ddlSearchType.Items.Add(new ListItem(“Privilege”, “2”));
ddlSearchType.Items.Add(new ListItem(“Privilege Category”, “3”));
ddlSearchType.Items[0].Selected = true;

ddlSearchBy.Items.Add(new ListItem(“Mnemonic”, “1”));
ddlSearchBy.Items.Add(new ListItem(“Name”, “2”));
ddlSearchBy.Items.Add(new ListItem(“Description”, “3”));
ddlSearchBy.Items[0].Selected = true;

wcFacility.ClientSideEvents.InitializeCombo = “wcFacility_InitializeCombo”;
wcFacility.ClientSideEvents.EditKeyUp = “wcFacility_EditKeyUp”;
wcFacility.DataTextField = “instn_mnc”;
wcFacility.DataValueField = “instn_nbr”;
wcFacility.DataSource = PWACMD.FacilityEntry.GetAllFacilities(false);
wcFacility.DataBind();
wcFacility.Columns[0].Hidden = true; //hide urn column
wcFacility.Columns[2].Hidden = true; //hide active column
wcFacility.Columns[1].Header.Caption = “Mnemonic”;
wcFacility.Columns[3].Header.Caption = “Description”;
wcFacility.Columns[1].Width = Unit.Percentage(30);
wcFacility.Columns[3].Width = Unit.Percentage(70);

//get user id
string userid = ((PWAMembershipUser)Membership.GetUser()).PicisUserID;

//get default facility
Dictionary prefFac = UserPreference.GetUserPreferences(userid, ApplicationType.CredentialingManager, “default_facility”);

//set facility to default
if(prefFac.Count>0)
{
wcFacility.DataValue = Convert.ToInt32(prefFac[0]);
}

//get the grid control within the combo and set the cell click action to rowselect
foreach (Control c in wcFacility.Controls)
{
if (c.ID == “_Grid”)
{
UltraWebGrid g = (UltraWebGrid)c;
g.DisplayLayout.CellClickActionDefault = CellClickAction.RowSelect;
}
}

wcSearch.DataValueField = “URN”;
wcSearch.ClientSideEvents.EditKeyUp = “wcSearch_EditKeyUp”;
wcSearch.ClientSideEvents.InitializeCombo = “wcSearch_InitializeCombo”;
((UltraWebGrid)this.wcSearch.Controls[0]).DisplayLayout.ClientSideEvents.BeforeRowActivateHandler = “wcSearch_BeforeRowActivated”;
((UltraWebGrid)this.wcSearch.Controls[0]).DisplayLayout.AllowAddNewDefault = AllowAddNew.Yes;
Infragistics.WebUI.Shared.Util.ClientScript.ResolveAndRegisterClientScriptInclude(
“/ig_common/20061/scripts/ig_webgrid_an.js”, “”, “/ig_common/20061/scripts/ig_webgrid_an.js”,
“ig_webgrid_an.js”, this.wcSearch, “ig_webgrid_an”);

btnSearch.Text = “Search”;
lblFacility.Text = “Facility:”;
lblFind.Text = “Find:”;
lblSearchType.Text = “Search For:”;
lblSearchBy.Text = “Search By:”;
lblCategoryMnemonic.Text = “Category Mnemonic:”;
lblCategoryDescription.Text = “Category Description:”;

tblPractitionerDetail.Visible = false;
tblPrivilegeCategoryDetail.Visible = false;
tblPrivilegeDetail.Visible = false;
rptResultsByPractitioner.Visible = false;
rptResultsByOther.Visible = false;

}

Page.ClientScript.RegisterArrayDeclaration(jsArray,
“\”” + ddlSearchBy.ClientID + “\”,” +
“\”” + ddlSearchType.ClientID + “\””);

AjaxPro.Utility.RegisterTypeForAjax(typeof(PrivilegeInquiry), this.Page);
}

protected void btnSearch_Click(object sender, EventArgs e)
{
//Create RootNode
TreeNode rootnode = new TreeNode(“By ” + wcSearch.DisplayValue.ToString(), inquiryType.ToString());
rootnode.PopulateOnDemand = true;
rootnode.SelectAction = TreeNodeSelectAction.Expand;
TreeView1.Nodes.Add(rootnode);
}

protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
if (e.Node.ChildNodes.Count == 0)
{
//Populate variables based on Node Depth
int table = -1;
bool populateOnDemand = false;
TreeNodeSelectAction action = TreeNodeSelectAction.None;
switch (e.Node.Depth)
{
case 0:
switch (e.Node.Value)
{
//Category
case “Category”:
//Populate Privilege Nodes
table = 1;
populateOnDemand = true;
action = TreeNodeSelectAction.Expand;
break;
//Privilege
case “Privilege”:
//Populate Practitioner Nodes
table = 2;
populateOnDemand = false;
action = TreeNodeSelectAction.Select;
break;
//Practitioner
case “Practitioner”:
//Populate Category Nodes
table = 0;
populateOnDemand = true;
action = TreeNodeSelectAction.Expand;
break;

default:
throw new NotImplementedException(“Selection type not supported.”);
}
break;

case 1:
//Category
if (e.Node.ValuePath.Contains(“Category/” + e.Node.Value))
{
//Populate Practitioner Nodes
table = 2;
populateOnDemand = false;
action = TreeNodeSelectAction.Select;
}
//Privilege
if (e.Node.ValuePath.Contains(“Privilege/” + e.Node.Value))
{
throw new NotImplementedException(“Privilege not supported at this Node Depth.”);
}
//Practitioner
if (e.Node.ValuePath.Contains(“Practitioner/” + e.Node.Value))
{
//Populate Privilege Nodes
table = 1;
populateOnDemand = false;
action = TreeNodeSelectAction.Select;
}
break;

default:
throw new NotImplementedException(“Node depth not supported.”);

}

//Get table for treeview branch
DataView dv = GetPrivilegeInquiryDataView(table);

//Populate TreeView ChildNodes
switch (table)
{
//Category
case 0:
foreach (DataRow row in dv.Table.Rows)
{
if ((e.Node.Value == (row[“cat_nbr”]).ToString()) || (e.Node.Depth == 0))
{
TreeNode newNode = new TreeNode(row[“cat_mnc”].ToString() + ” – ” + row[“cat_desc”].ToString(), row[“cat_nbr”].ToString());
newNode.PopulateOnDemand = populateOnDemand;
newNode.SelectAction = action;
e.Node.ChildNodes.Add(newNode);
}
}
break;
//Privilege
case 1:
foreach (DataRow row in dv.Table.Rows)
{
if ((e.Node.Value == (row[“cat_nbr”]).ToString()) || (e.Node.Depth == 0))
{
TreeNode newNode = new TreeNode(row[“priv_mnc”].ToString() + ” – ” + row[“priv_descr1”].ToString(), row[“priv_nbr”].ToString());
newNode.PopulateOnDemand = populateOnDemand;
newNode.SelectAction = action;
e.Node.ChildNodes.Add(newNode);
}
}
break;
//Practitioner
case 2:
foreach (DataRow row in dv.Table.Rows)
{
if ((e.Node.Value == (row[“priv_nbr”]).ToString()) || (e.Node.Depth == 0))
{
TreeNode newNode = new TreeNode(row[“dr_mnc”].ToString() + ” – ” + row[“dr_nm”].ToString(), row[“dr_nbr”].ToString());
newNode.PopulateOnDemand = populateOnDemand;
newNode.SelectAction = action;
e.Node.ChildNodes.Add(newNode);
}
}
break;

default:
throw new NotImplementedException(“Table not supported.”);

}

}
}

protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
{
string select;

//If Practitioner, filter by Privileges
if (TreeView1.SelectedNode.ValuePath.Contains(“Practitioner/”))
{
select = “priv_nbr = ” + TreeView1.SelectedNode.Value.ToString();
}
//If Privilege, filter by Practitioner
else
{
select = “dr_nbr = ” + TreeView1.SelectedNode.Value.ToString();
}

//Get DetailsView data from Table 2
DataView dv = GetPrivilegeInquiryDataView(2);
dv.RowFilter = select;

//Populate DetailsView
DetailsView1.DataSource = dv;
DetailsView1.DataBind();

}

#endregion

#region ControlBase Members

public override void InitUserControl()
{
Page.ClientScript.RegisterHiddenField(“nameText”, “Name”);
Page.ClientScript.RegisterHiddenField(“descriptionText”, “Description”);
}

#endregion

#region Public Methods

///
/// AJAX method to retrieve a DataTable consisting of information about the first
/// five entities to match the given search criteria.
///
///
/// DataTable is formatted as follows:
///
/// mnc as string
/// descr as string
/// urn as int
///
/// Search string.
/// Integer representation of the inquiry type:
/// 1 = Practitioner
/// 2 = Privilege
/// 3 = Privilege Category
///
/// Integer representation of the search type:
/// 1 = search by mnemonic
/// >1 = search by description or name
///
/// Integer id used to identify this search request on the client.
/// This id is returned to the client in the URN column of the last row of the table.
///
/// DataTable of five or less rows plus a row with the request id.
[AjaxPro.AjaxMethod]
public DataTable SearchLookup(string search, int inquiryType, int lookupType, int requestId)
{
DataTable dt;

bool byDesc = (lookupType == 1) ? false : true;

switch (inquiryType)
{
case 1:
dt = PWACMP.Privilege.SearchPractitioners(search, byDesc);
break;
case 2:
dt = PWACMP.Privilege.SearchPrivileges(search, byDesc);
break;
default:
dt = PWACMP.Privilege.SearchPrivilegeCategories(search, byDesc);
break;
}

//add request ID
DataRow dr = dt.NewRow();
dr[“urn”] = requestId;
dt.Rows.Add(dr);

return dt;
}

[AjaxPro.AjaxMethod]
public DataSet GetDetail(int urn, string detailType, int requestId)
{
DataSet ds = new DataSet();

DataTable rid = new DataTable();
rid.Columns.Add(“detail_type”, typeof(String));
rid.Columns.Add(“request_id”, typeof(Int32));
rid.Rows.Add(detailType, requestId);

ds.Tables.Add(rid);

switch (detailType)
{
case “P”:
DataTable dt = new DataTable();
dt.Columns.Add(“img_urn”, typeof(Int32));
dt.Columns.Add(“img_caption”, typeof(String));

List imgs = SecurityManagerImage.GetImages(
ApplicationType.CredentialingManager, 100, 100, urn.ToString());

if (imgs.Count == 1)
{
dt.Rows.Add(imgs[0].Urn, imgs[0].ShortDescription);
}

ds.Tables.Add(dt);

break;
case “R”:

break;
default:

break;
}

return ds;
}

#endregion

#region Protected Methods

protected DataView GetChildRelation(object dataItem, string relation)
{
DataRowView drv = dataItem as DataRowView;

if (drv != null)
{
return drv.CreateChildView(relation);
}
else
{
return null;
}
}

#endregion

#region Private Methods

private DataView GetPrivilegeInquiryDataView(int table)
{
//Get Search Crieria Values
int urn = Convert.ToInt32(wcSearch.DataValue);
int facUrn = Convert.ToInt32(wcFacility.DataValue);
PrivilegeInquiryType inquiryType;
switch (ddlSearchType.SelectedValue)
{
case “1”:
inquiryType = PrivilegeInquiryType.Practitioner;
break;
case “2”:
inquiryType = PrivilegeInquiryType.Privilege;
break;
case “3”:
inquiryType = PrivilegeInquiryType.Category;
break;
default:
throw new NotImplementedException(“Inquiry type not supported.”);
}

//Get PrivilegeInquiry DataTable based on Search Crieria Values
DataTable dt = new DataTable();
switch (table)
{
case 0:
dt = PWACMP.Privilege.PrivilegeInquiry(urn, facUrn, inquiryType).Tables[0];
break;
case 1:
dt = PWACMP.Privilege.PrivilegeInquiry(urn, facUrn, inquiryType).Tables[1];
break;
case 2:
dt = PWACMP.Privilege.PrivilegeInquiry(urn, facUrn, inquiryType).Tables[2];
break;
default:
throw new NotImplementedException(“Table not supported.”);
}

//Return DataTable as DataView
DataView dv = new DataView(dt);
return dv;

}

#endregion
}

June 30th, 2006


Pages

Tweets

Meta

Recent Posts