If you are looking for my Linq2DataSets presentation from Saturday's code camp, I am in the progress of getting them ready and finding a place to post item.
Tuesday, March 30, 2010
My Boise Code Camp Presentation Notes
Monday, March 15, 2010
Group By doesn’t appear to work in Linq2DataSet
As I’m working on my presentation for Boise Code Camp, I’ve come across some issues trying to demonstrate Group By. I haven’t been able to make a Group By query work. When I run the same query using Linq2Sql, it works just fine.
For example, given the following code (in VB 10):
Dim MyList = From o In myData.Orders _ Join od In myData.Order_Details On o.OrderID Equals od.OrderID _ Join e In myData.Employees On o.EmployeeID Equals e.EmployeeID _ Group By FullOrder = New With _ { _ .OrderId = od.OrderID, _ .EmployeeName = (e.FirstName & " " & e.LastName), _ .ShipCountry = o.ShipCountry, _ .OrderDate = o.OrderDate _ } _ Into Amount = Sum(od.Quantity * od.UnitPrice) _ Where FullOrder.ShipCountry = "USA" _ Order By FullOrder.OrderId _ Select FullOrder.OrderId, _ FullOrder.OrderDate, _ FullOrder.EmployeeName, _ Amount For Each x In MyList Console.WriteLine( _ String.Format( _ "{0}; {1:d}; {2}: {3:c}", _ x.OrderId, _ x.OrderDate, _ x.EmployeeName, _ x.Amount)) Next
When myData is a DataSet: I get:
10262; 7/22/1996; Laura Callahan: $204.00 10262; 7/22/1996; Laura Callahan: $360.00 10262; 7/22/1996; Laura Callahan: $60.80 10269; 7/31/1996; Steven Buchanan: $120.00 10269; 7/31/1996; Steven Buchanan: $556.00 10271; 8/1/1996; Michael Suyama: $48.00 10272; 8/2/1996; Michael Suyama: $388.80 10272; 8/2/1996; Michael Suyama: $400.00 10272; 8/2/1996; Michael Suyama: $667.20
There is more than 1 entry for each OrderId, the query failed to group properly. Order 10262 has 3 lines corresponding to the 3 line items of the order.
If I run the same Linq query with myData as a Linq2Sql DataContext, I get:
10262; 7/22/1996; Laura Callahan: $624.80 10269; 7/31/1996; Steven Buchanan: $676.00 10271; 8/1/1996; Michael Suyama: $48.00 10272; 8/2/1996; Michael Suyama: $1,456.00
Here everything is grouped together and Amount is totaled. Order 10262 has 1 entry as I would expect.
There may be a slightly more complex way to make this query work using the DataSet and I haven’t found it, but right now, it doesn’t look good for Linq2DataSets in this area.
Tuesday, February 23, 2010
Loading Object Graphs with Linq2DataSets
The data base likes to keep data in relational tables and the middle tier likes to present the data in object graphs where the details are stored within the patient object, so everything is close at hand. So how do you transform the data from tables to graphs? I use Linq2DataSets.
The Stored Procedure
Suppose you are asked to load an object graph with the following stored procedure (using Northwind, my favorite database of all times):
-- This procedure returns 2 results sets the first includes all of the Orders and -- the second all of the Order Details (regardless of order). CREATE PROCEDURE dbo.GetAllOrders AS SELECT o.OrderID, o.CustomerID, o.EmployeeID, o.OrderDate, o.RequiredDate, o.ShippedDate, o.ShipVia, o.Freight, o.ShipName, o.ShipAddress, o.ShipCity, o.ShipRegion, o.ShipPostalCode, o.ShipCountry FROM [Orders] o SELECT od.OrderID, od.ProductID, od.UnitPrice, od.Quantity, od.Discount FROM [Order Details] od
In real life, I would probably have parameters to limit the result sets to the data that I really want. For the purpose of demonstration, it is helpful to process a lot of data.
Getting the data into the Object Graphs
Now, suppose you want to load this data into Object Graphs. In my example, I have an Order class that includes a list of its Order Details (a real production SP would probably include parameters to filter the data returned; you don’t want to load all the orders for a real company).
The SP returns the two lists that you will have to stitch together yourself in code:
/// <summary> /// Loads all the orders into a list of Order objects /// </summary> public static ListGetAllOrders(OrderDS ds) { return new List ( from orders in ds.Orders select new Order( orders.OrderID, orders.CustomerID, orders.EmployeeID, orders.OrderDate, orders.RequiredDate, !orders.IsShippedDateNull() ? orders.ShippedDate : (DateTime?)null, orders.ShipVia, orders.Freight, orders.ShipName, orders.ShipAddress, orders.ShipCity, !orders.IsShipRegionNull() ? orders.ShipRegion : string.Empty, !orders.IsShipPostalCodeNull() ? orders.ShipPostalCode : string.Empty, orders.ShipCountry, getOrderDetails(orders.OrderID, ds))); } /// <summary> /// Loads the order details associated with a given order into a list of /// OrderDetal objects /// </summary> public static List getOrderDetails(int orderId, OrderDS ds) { return new List ( from orderDetails in ds.OrderDetails where orderDetails.OrderID == orderId select new OrderDetail( orderDetails.OrderID, orderDetails.ProductID, orderDetails.UnitPrice, orderDetails.Quantity, orderDetails.Discount)); }
From the above code, I would like to point out
- GetAllOrders() calls getOrderDetails() from within the linq query. When you call the Orders constructor, you can call any code you would call in any other constructor.
- Even though I’m in the middle of a Linq query in GetAllOrders(), I can create a second Linq query in getOrderDetails using the same DataSet.
- Since we are using DataSets and other classic ADO.NET objects, we need to call IsFooNullable() to avoid an InvalidCastException.
- When I’m passing null to the Order constructor, I need to cast it to the correct type.
Appendix
Here are the classes that I loaded the in the code above
public class Order { public int OrderID { get; set; } public string CustomerID { get; set; } public int EmployeeID { get; set; } public DateTime OrderDate { get; set; } public DateTime RequiredDate { get; set; } public DateTime? ShippedDate { get; set; } public int ShipVia { get; set; } public decimal Freight { get; set; } public string ShipName { get; set; } public string ShipAddress { get; set; } public string ShipCity { get; set; } public string ShipRegion { get; set; } public string ShipPostalCode { get; set; } public string ShipCountry { get; set; } public ListDetails { get; set; } public Order(int orderID, string customerID, int employeeID, DateTime orderDate, DateTime requiredDate, DateTime? shippedDate, int shipVia, decimal freight, string shipName, string shipAddress, string shipCity, string shipRegion, string shipPostalCode, string shipCountry, List details) { this.OrderID = orderID; this.CustomerID = customerID; this.EmployeeID = employeeID; this.OrderDate = orderDate; this.RequiredDate = requiredDate; this.ShippedDate = shippedDate; this.ShipVia = shipVia; this.Freight = freight; this.ShipName = shipName; this.ShipAddress = shipAddress; this.ShipCity = shipCity; this.ShipRegion = shipRegion; this.ShipPostalCode = shipPostalCode; this.ShipCountry = shipCountry; this.Details = details; } } public class OrderDetail { public int OrderID { get; set; } public int ProductID { get; set; } public decimal UnitPrice { get; set; } public int Quantity { get; set; } public float Discount { get; set; } public OrderDetail(int orderID, int productID, decimal unitPrice, int quantity, float discount) { this.OrderID = orderID; this.ProductID = productID; this.UnitPrice = unitPrice; this.Quantity = quantity; this.Discount = discount; } }
And GetDataSet() the function that loads the DataSet
/// <summary> /// Loads OrderDS using the SP GetAllOrders /// </summary> public static OrderDS GetDataSet() { var ds = new OrderDS(); SqlConnection conn = new SqlConnection(CONNECTION_STRING); using (SqlCommand cmd = new SqlCommand("GetAllOrders", conn)) { cmd.CommandType = System.Data.CommandType.StoredProcedure; SqlDataAdapter DA = new SqlDataAdapter(cmd); DA.TableMappings.Add("Table", "Orders"); DA.TableMappings.Add("Table1", "OrderDetails"); DA.Fill(ds); } return ds; }
Since I’m too lazy to do graphics on this blog, I will leave it to the reader to make the actual DataSet.
Sunday, February 21, 2010
Generate Stored Procedures with data from SQL Server System Tables
Here’s an experiment I wrote over the weekend to create an Insert SP for a table using SQL Server System Tables.
This code generates code for a specific code pattern and won’t necessarily work for any table that is thrown at it. Supporting every possible situation can make the code really complex. If I am faced with a table that this can’t handle, I am face with the choice of expanding the program to handle the new situation or write the code by hand.
These SPs are the same except for:
- The SP name – Based on Table Name
- The Table Name
- The Fields
- Which Field is the Primary Key
- Which Fields are Nullable
From the System Tables I use:
- sysObjects.name: the name of the table
- sysColumns.name: the column name
- sysTypes.name: the type name
- sysColumns.length: the length of the field
- sysColumns.isnullable: is the field nullable?
- sysColumns.status: used to determine if this is the primary key/auto-number column (look for 0x80)
The Code
using System; using System.Data.SqlClient; using System.Text; namespace GenerateSP { class Program { /// <summary> /// The Connection String /// </summary> private const string CONNECTION_STRING = @"Data Source=localhost\SQLEXPRESS;" + "Initial Catalog=Northwind;Integrated Security=True"; /// <summary> /// Number of list items per line in the generated procedure /// </summary> private const int ITEMS_PER_LINE = 5; /// <summary> /// Shows the Code Generation in action /// </summary> static void Main(string[] args) { Console.WriteLine(CreateInsertSp("Employees")); Console.ReadKey(); } /// <summary> /// Creates an INSERT Stored Procedure for a given table as a string /// </summary> public static string CreateInsertSp(string tableName) { string identityField = string.Empty; // There are 3 places in the SP that refer to the field names // so I am using 3 string builders to generate those parts // of the SP; StringBuilder parametersSb = new StringBuilder(); StringBuilder InsertValueListSb = new StringBuilder(); StringBuilder InsertFieldListSb = new StringBuilder(); using (SqlConnection conn = new SqlConnection(CONNECTION_STRING)) { int lineNumber = 0; // Used to SqlDataReader reader = GetTableDefReader(tableName, conn); while (reader.Read()) { string colName = reader.GetString(0); string typeName = reader.GetString(1); int length = reader.GetInt32(2); int isNullable = reader.GetInt32(3); int status = reader.GetInt32(4); AddToParameterSb(parametersSb, colName, typeName, length, isNullable, status); if (status == 0x80) identityField = colName; else { lineNumber++; AddInsertFieldListToSb(InsertFieldListSb, colName, lineNumber); AddInsertValueListToSb(InsertValueListSb, colName, lineNumber); } } } // Put everything together return string.Format("\nCREATE PROCEDURE [dbo].[Insert{0}]\n", tableName) + parametersSb.ToString() + string.Format("\nAS\n\nINSERT INTO [{0}] (", tableName) + InsertFieldListSb.ToString() + string.Format(")\nVALUES (") + InsertValueListSb.ToString() + string.Format(")\n\nSET @{0} = @@IDENTITY\n\nGO\n", identityField); } /// <summary> /// Gets a DataReader containing selected column information for all of the /// columns in the indecated table from SQL Server System Tables /// </summary> private static SqlDataReader GetTableDefReader(string tableName, SqlConnection conn) { SqlDataReader reader; string getDataForTable = "SELECT c.name AS col_name, " + " t.name AS type_name, " + " CAST(c.length AS INT) AS length, " + " c.isnullable, " + " CAST(c.status AS INT) AS status " + "FROM " + " sysObjects o " + " JOIN sysColumns c ON c.id = o.id " + " JOIN systypes t on c.xtype = t.xtype AND t.status = 0 " + "WHERE o.name = @tableName " + "ORDER BY c.colid "; using (SqlCommand cmd = new SqlCommand(getDataForTable, conn)) { cmd.Parameters.AddWithValue("@tableName", tableName); conn.Open(); reader = cmd.ExecuteReader(); } return reader; } /// <summary> /// Adds the current field to the /// </summary> private static void AddToParameterSb(StringBuilder sb, string colName, string typeName, int length, int isNullable, int status) { if (sb.Length > 0) sb.Append(",\n"); sb.AppendFormat("\t@{0} ", colName); switch (typeName.ToLower()) { case "varchar": case "nvarchar": string lenString = length.ToString(); if (length == -1) lenString = "MAX"; sb.AppendFormat("{0}({1})", typeName, lenString); break; default: sb.Append(typeName); break; } if (status == 0x80) sb.Append(" OUT"); else if (isNullable != 0) sb.Append(" = NULL"); } /// <summary> /// Adds the current field to the VALUES list for an INSERT query /// </summary> private static void AddInsertValueListToSb(StringBuilder sb, string colName, int lineNumber) { AddListSeperatorToSb(sb, lineNumber); sb.AppendFormat("@{0}", colName); } /// <summary> /// Adds the current field to the Field list for an INSERT query /// </summary> private static void AddInsertFieldListToSb(StringBuilder sb, string colName, int lineNumber) { AddListSeperatorToSb(sb, lineNumber); sb.AppendFormat("[{0}]", colName); } /// <summary> /// Adds the comma and or new line to the list StringBuilder /// </summary> /// <remarks> /// Shouldn't have a comma before the first item in the list.<br /> /// Should only insert new ine after each ITEMS_PER_LINE items /// </remarks> private static void AddListSeperatorToSb(StringBuilder sb, int lineNumber) { if (sb.Length > 0) sb.Append(", "); if ((lineNumber % ITEMS_PER_LINE) == 0) sb.Append("\n\t"); } } }
I adapted this code for T4 Templates here
Saturday, January 30, 2010
Linq2DataSet replaces the DataView
Remember the ADO.NET DataView? It is the old-fashioned way to filter an existing DataSet from the days of ADO.NET 1.0. They are useful for filtering and sorting DataSets that you already have on hand for other reasons.
Let’s say you want a list of all the Northwind employees who live in the United States sorted by LastName:
// If you use DataSets you can probably write your own EmployeeDS & SetupData(): EmployeeDS db = SetupData(); // Filter by Country = “USA”: Sort by LastName DataView dv = new DataView( db.Employees, "Country = 'USA'", // The Filter "LastName", // Sort Order DataViewRowState.CurrentRows); // the DataView consists of a collection of DataRowView, so you have to cast back // to get to the strongly typed DataRows EmployeeDS.EmployeesRow foreach (DataRowView dvRow in dv) { EmployeeDS.EmployeesRow item = (EmployeeDS.EmployeesRow)dvRow.Row; Console.WriteLine(item.LastName); }
In the code above, the filter and the sort are in strings, so the compiler can’t find errors; if the Employee table doesn’t have a “Country” field, the above code will compile but throw an EvaluateException at runtime.
In the foreach loop you need to cast the DataRowView into the strongly typed DataRow before you can access the properties. If you really don’t want to do the cast, you could use “dvRow["LastName"]” to get the last name, but that would involve another “magic string”.
Now, let’s get the same list in Linq2DataSet:
// If you use DataSets you can probably write your own EmployeeDS & SetupData(): EmployeeDS db = SetupData(); OrderedEnumerableRowCollection<EmployeeDS.EmployeesRow> linqRows = from e in db.Employees where e.Country == "USA" orderby e.LastName select e; // Linq2DataSet gives you a collection of strongly typed DataRows. // You don’t have to cast to get to the LastName element here: foreach (EmployeeDS.EmployeesRow item in linqRows) Console.WriteLine(item.LastName);
In this code, the only thing in a string is “USA”; the compiler will freak out if you don’t have a “Country” field. You don’t have to rely on angry user to find these bugs.
In the foreach loop you don’t have to cast, the output of the Linq query is a collection of strongly typed DataRows (however it isn’t a DataTable, but a DataRow can’t belong to more than one DataTable anyway).
If you really only want LastNames you can get only LastNames:
// If you use DataSets you can probably write your own EmployeeDS & SetupData(): EmployeeDS db = SetupData(); // Get me only LastNames as a collection of the field’s data type (in this case, string): EnumerableRowCollection<string> LastNames = from e in db.Employees where e.Country == "USA" orderby e.LastName select e.LastName; // Loop through the strings foreach (string LastName in LastNames) Console.WriteLine(LastName);
Here Linq2DataSet gives us back a collection of strings because in the DataSet LastName is a string; if you ask for HireDate, you will get a collection of DateTimes.
Suppose you want a both LastName and FirstName you could do this (with Anonymous Types):
// If you use DataSets you can probably write your own EmployeeDS & SetupData(): EmployeeDS db = SetupData(); // Here we need to use “var” because the type doesn’t exist until the // compiler creates it: var Names = from e in db.Employees where e.Country == "USA" orderby e.LastName select new { e.LastName, e.FirstName }; // Here we are using the same type that the compiler created above: foreach (var Name in Names) Console.WriteLine(string.Format("{0}, {1}",Name.LastName, Name.FirstName));
To make this work, C# created an anonymous type to put the results of the Linq query. Anonymous Types are strong types that are created at compile time; they just don’t have names and there are no class or struct declaration needed in the code.
(On my machine, according to the debugger, Names is a System.Data.EnumerableRowCollection<<>f__AnonymousType0<string,string>>)
More about using Anonymous Types in later posts. . .
Thursday, January 14, 2010
Linq2DataSet Introduction
Lately I’ve been working for a client that recently moved an existing application to VS 2008 / .NET 3.5. The app uses some DataSets, so I’ve been playing to what I call Linq2DataSet (or Linq to DataSet).
Getting Started
To use Linq2DataSet will need to use the .NET 3.5 runtime and, in addition to the usual ADO.NET assemblies you will need to add a reference to System.Data.DataSetExtensions.dll.
Quick comparison to Linq2Sql
// Linq To Sql int Linq2SqlGetCount() { BigTableSQLDataContext data = new BigTableSQLDataContext(); return (from x in data.Big_Tables select x).Count(); } // Linq to DataSet int Linq2DataSetGetCount() { BigTableDS data = new BigTableDS(); Big_TableTableAdapter da = new Big_TableTableAdapter(); da.Fill(data.Big_Table); return (from x in data.Big_Table select x).Count(); }
The code isn’t that much different. There is a little more code to setup the DataSet than the SQL DataContext. No big deal.
There is a big gotcha. I ran both functions against a table with 20,000 records. The DataSet is fully loaded before anything can happen. In the code above, the Linq2Sql function runs in < 1 second and the Linq2DataSet version takes about a minute and a half.
In Linq2DataSet, all 20,000 are loaded into the DataSet before the Linq code is executed. In Linq2Sql, the engine executes the Linq and generates the SQL to get the data (probably something like SELECT COUNT(*) FROM Big_Table).
Yes, this looks bad, however if you already have the DataSet hanging around, Linq2DataSet may be a Mega-Cool way to solve your problems. For the record, I would not recommend Linq2DataSet for new “Green Field” projects, however, if you already have the DataSet lying around, Linq can make it easy to get that little bit of extra info you need. Linq2DataSets is also a way to get into Linq quickly then you can learn about Linq2Sql or the Entity Framework.
More to come…
Sunday, December 13, 2009
Nullable Data Types Demo
The Problem
In several of the projects I’ve worked on, we used some token value to represent null for non-nullable types. These values would be extreme values (for int, we would use int.MinValue or int.MaxValue), or null token. Recently I’ve come across some controls that don’t like this scheme and prefer using nullable types (like int?).
The original code was written in C# 1.X, so nullable types didn’t exist; the code works, so I don’t want to convert the non-nullables to nullable and risk breaking the rest of the application.
In production, I’ve solved the problem by creating “shadow” nullable properties. The code in these properties would translate the null tokens to null. For the most part the translation code is inline.
Taking it to 11 with Generics
This weekend, I played with implementing this stuff with a generic helper class. So the code for a nullable property would look something like this:
public DateTime? DobNullable { get { return NullableHelper.GetNullable<DateTime>(this.Dob); } set { this.Dob = NullableHelper.SetNullable<DateTime>(value); } }
To do this I created a class called NullableHelper that contained translation functions: The to set you use this:
public static T SetNullable<T>(T? futureValue) where T : struct { T rVal; if (futureValue != null) rVal = (T)futureValue; else rVal = NullForT<T>(); return rVal; }
This function echo back the value passed in UNLESS it is null; if it is null, it returns the null token. And to get, you use this:
public static T? GetNullable<T>(T presentValue) where T : struct, IComparable { T? rVal = null; // I can't get the generic do == & !=, this works, so I'm // going with it for now. IComparable ic = presentValue as IComparable; if (ic != null) { T CompareValue = NullForT<T>(); if (ic.CompareTo(CompareValue) != 0) rVal = presentValue; // I already set rVal to null above. return rVal; } else { throw new ApplicationException( string.Format("NullableDataTypesDemo.NullableHelper.GetNullable<T>()" + "\r\n'{0}' is can't be cast to IComparable", typeof(T).ToString()) ); } }
In this function I needed to cast T as IComparable to compare the value with the null token.
Both functions use a null token from the following function:
public static T NullForT<T>() where T : struct { T rVal; // Here you will need to have an if for each of the // supported types, this function could be really big in production: if (typeof(T) == typeof(DateTime)) { DateTime x = DateTime.MaxValue; object o = x; rVal = (T)o; } // Set other null tokens here else { throw new ApplicationException( string.Format("NullableDataTypesDemo.NullableHelper.NullForT<T>()" + "\r\n'{0}' is an unsupported Type", typeof(T).ToString()) ); } return rVal; }
This function is probably the Achilles’ heel of this design. You will need an if (typeof(T) block for each type you want to support in this function. Most of the complexity points of this solution are borne by this function.
Conclusion
Over the weekend I came up with a cool way of translating from a non-nullable with null tokens to nullables. Will I implement this in production? Probably not, the code works and I don’t want to risk breaking it. If I need to do similar things in a future project, I may come back to this post.
The Whole Experiment
using System; namespace NullableDataTypesDemo { class Program { static void Main(string[] args) { // Create an instance of the demo entity DemoEntityClass x1 = new DemoEntityClass( 1, 100m, "Jill Stephens", NullableHelper.NullForT<DateTime>(), NullableHelper.NullForT<int>()); // Do some querying if (x1.IdNullable != null) { Console.WriteLine(string.Format("ID = {0}", x1.IdNullable)); } if (x1.NumOfChildren == NullableHelper.NullForT<int>()) { Console.WriteLine("Children Unknown (classic)"); } // Set a DOB x1.DobNullable = new DateTime(1776, 7, 4); Console.WriteLine(string.Format("{0:d}", x1.DobNullable)); // Set # of children and display x1.NumOfChildrenNullable = 2; Console.WriteLine("value of Number of Children: {0}", x1.NumOfChildren); // Unset # of children x1.NumOfChildren = NullableHelper.NullForT<int>(); if (x1.NumOfChildrenNullable == null) { Console.WriteLine("Children Unknown (new)"); } // Won't comile, // NullableHelper.NullForT<DemoEntityClass>(); // Some NullForT that won't compile // (you can fix these by adding code to NullForT()) try { NullableHelper.NullForT<DemoStruct>(); } catch (Exception ex) { Console.WriteLine(ex.Message); } try { NullableHelper.NullForT<double>(); } catch (Exception ex) { Console.WriteLine(ex.Message); } // Force read line, so this won't go away in VS Console.ReadLine(); } } /// <summary> /// My Demo Entity Class /// </summary> class DemoEntityClass { public DemoEntityClass(int id, decimal amount, string name, DateTime dob, int numOfChildren) { this.Id = id; this.Amount = amount; this.Name = name; this.Dob = dob; this.NumOfChildren = numOfChildren; } public int Id { get; set; } public decimal Amount { get; set; } public string Name { get; set; } public DateTime Dob { get; set; } public int NumOfChildren { get; set; } public int? IdNullable { get { return NullableHelper.GetNullable<int>(this.Id); } set { this.Id = NullableHelper.SetNullable<int>(value); } } public decimal? AmountNullable { get { return NullableHelper.GetNullable<decimal>(this.Amount); } set { this.Amount = NullableHelper.SetNullable<decimal>(value); } } // string is nullable, so I am including this for completeness public string NameNullable { get { return this.Name; } set { this.Name = (string)value; } } public DateTime? DobNullable { get { return NullableHelper.GetNullable<DateTime>(this.Dob); } set { this.Dob = NullableHelper.SetNullable<DateTime>(value); } } public int? NumOfChildrenNullable { get { return NullableHelper.GetNullable<int>(this.NumOfChildren); } set { this.NumOfChildren = NullableHelper.SetNullable<int>(value); } } } /// <summary> /// My Demo struct /// </summary> struct DemoStruct { public int x; public int y; public int xy { get { return x * y; } } } static class NullableHelper { /// <summary> /// Gets the Null marker value for value types /// </summary> /// <remarks> /// In some old applications that date back to before nullable types /// maked fields as being null with extreme values (int.MaxValue for /// example). /// </remarks> /// <typeparam name="T">Tye value type being tested</typeparam> /// <returns>The Null marker value</returns> public static T NullForT<T>() where T : struct { T rVal; // Here you will need to have an if for each of the // supported types, this function could be really big in production: if (typeof(T) == typeof(int)) { int x = int.MaxValue; object o = x; rVal = (T)o; } else if (typeof(T) == typeof(decimal)) { decimal x = decimal.MaxValue; object o = x; rVal = (T)o; } else if (typeof(T) == typeof(string)) { string x = string.Empty; object o = x; rVal = (T)o; } else if (typeof(T) == typeof(DateTime)) { DateTime x = DateTime.MaxValue; object o = x; rVal = (T)o; } else { throw new ApplicationException( string.Format("NullableDataTypesDemo.NullableHelper.NullForT<T>()" + "\r\n'{0}' is an unsupported Type", typeof(T).ToString()) ); } return rVal; } /// <summary> /// Used in a set block to set a non-nullable /// field from a nullable value /// </summary> /// <remarks> /// Since the null values are gotten from NullForT, this function can be short /// </remarks> /// <see cref="NullForT"/> /// <typeparam name="T">Tye value type being set</typeparam> /// <param name="futureValue">nullable new value</param> /// <returns>non nullable value with Null marker value to denote null</returns> public static T SetNullable<T>(T? futureValue) where T : struct { T rVal; if (futureValue != null) rVal = (T)futureValue; else rVal = NullForT<T>(); return rVal; } /// <summary> /// Used in a get block to get a nullable field /// from the non-nullable value /// </summary> /// <remarks> /// This function is a little more complex because we need to cast /// presentValue to IComparable to compare it to the null token /// </remarks> /// <see cref="NullForT"/> /// <typeparam name="T">Tye value type being set</typeparam> /// <param name="presentValue"> /// non-nullable value with Null marker value to denote null /// </param> /// <returns>nullable value returned</returns> public static T? GetNullable<T>(T presentValue) where T : struct, IComparable { T? rVal = null; // I can't get the generic do == & !=, this works, so I'm // going with it for now. IComparable ic = presentValue as IComparable; if (ic != null) { T CompareValue = NullForT<T>(); if (ic.CompareTo(CompareValue) != 0) rVal = presentValue; // I already set rVal to null above. } else { throw new ApplicationException( string.Format("NullableDataTypesDemo.NullableHelper.GetNullable<T>()" + "\r\n'{0}' is can't be cast to IComparable", typeof(T).ToString()) ); } return rVal; } } }