Showing posts with label Note to Work. Show all posts
Showing posts with label Note to Work. Show all posts

Wednesday, August 02, 2017

CSS Popup Sample

Here's some code I wrote for something I'm doing for work.
This code will show and hide a popup
<html>
  <head>
    <style>
      #pop {
          height: 200px;
          width: 200px;
          position: fixed;
          bottom: 50%;
          right: 50%;
          border: 2px solid;
          padding: 10px;
          background: white;
          display: none;
        }
        #buttonsOnPop {
          position: absolute; 
          right: 0; 
          bottom: 0;
          margin: 5px;
        }
        #popTitle {
          vertical-align: top;
          text-align: center;
          width: 100%;
          background-color: limegreen;
        }
      </style>
      <script>
        function closePopYes() {
          alert("yes");
        }
        function closePopNo() {
            document.getElementById("pop").style.display='none';
        }
        function showPop() {
          var text = "<p>To be or not to be</p><p>Revente</p>";
          var popContent = document.getElementById("popContent");
          popContent.innerHTML = text;
            document.getElementById("pop").style.display='block';
        }
     </script>
  </head>
  <body>
    <div id="pop">
      <div id="popTitle">Pop Window</div>
        <div id="popContent">
      </div>
      <div id="buttonsOnPop">
        <button id="yes" onclick="closePopYes();">Yes</button>
        <button id="no" onclick="closePopNo();">No</button>
      </div>
    </div>
    <button id="showPop" onclick="showPop()">Show Pop</button>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vitae iaculis enim. 
Vivamus a tellus sit amet tortor mattis auctor at eget purus. Fusce condimentum semper 
varius. Integer ultricies enim et ipsum scelerisque, dignissim ultricies metus molestie. 
Quisque rutrum, sem id vehicula malesuada, orci purus malesuada dolor, id vehicula nulla 
nisi sed dui. Donec at eros vestibulum, euismod mi quis, rhoncus justo. Curabitur quis 
metus sit amet erat efficitur pellentesque. Nunc dui massa, efficitur ac dapibus ut, 
interdum vel metus. Nunc mattis eleifend nisl, id rhoncus erat cursus et. Nullam semper, 
risus sed feugiat elementum, turpis lacus egestas nisi, quis bibendum lorem turpis eu 
purus. Etiam nec lorem tristique, condimentum ligula ac, mattis erat. Proin aliquet 
quis lectus eu hendrerit. Curabitur vitae laoreet nulla. Pellentesque imperdiet odio id 
urna imperdiet bibendum eget non erat.
</p>

  </body>
</html>

Sunday, April 14, 2013

Compare two DataSets for testing

There is a client that has several statements that are rendered as Crystal Reports; we bind the Crystal Reposts to ADO.NET DataSets. The DataSets are loaded from stored procedures in the usual way. Right now we are printing several of these statements and some poor clerk has to compare those values with an earlier correct one.

My idea is to store the DataSets of known good statements and then I can automatically compare them with a new DataSet loaded from the stored procedure. So, over the weekend I wrote these classes to do the comparisons:

The Code

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace CompareDS
{
    /// <summary>
    /// Compare the contents of DataSets or DataTables 
    /// </summary>
    public class CompareDataSet
    {
        /// <summary>
        /// Compare the content of two DataSets
        /// </summary>
        /// <param name="fromDS">The DataSet considered to be correct</param>
        /// <param name="toDS">The DataSet to be tested</param>
        /// <returns>A list of Inconsistencies</returns>
        public static List<CompareDifference> DoCompareDataSet(
            DataSet fromDS, DataSet toDS)
        {
            var rVal = new List<CompareDifference>();

            // Make sure that the table count match
            if (fromDS.Tables.Count != toDS.Tables.Count)
            {
                rVal.Add(new CompareDifference
            {
                Message = string.Format("Non maching # of tables: {0} != {1}", 
                    fromDS.Tables.Count, toDS.Tables.Count)
                });
            }
            // Process the smaller # of tables
            int minColCount = Math.Min(fromDS.Tables.Count, toDS.Tables.Count);
            for (int i = 0; i < minColCount; ++i)
            {
                DataTable fromTable = fromDS.Tables[i];
                DataTable toTable = toDS.Tables[i];
                string tableName = fromTable.TableName;
                rVal.AddRange(DoCompareTable(fromTable, toTable, tableName, i));
            }
            return rVal;
        }
        /// <summary>
        /// Compare the content of two DataTables
        /// </summary>
        /// <param name="fromTable">The DataTable considered to be correct</param>
        /// <param name="toTable">The DataTable to be tested</param>
        /// <param name="tableName">The Name of the table to be compared</param>
        /// <param name="tableNumber">The zero based table number</param>
        /// <returns>A list of Inconsistencies</returns>
        public static List<CompareDifference> DoCompareTable(DataTable fromTable, 
            DataTable toTable, string tableName = "Table", int tableNumber = 0)
        {
            var rVal = new List<CompareDifference>();
            // Number of Rows should match
            if (fromTable.Rows.Count != toTable.Rows.Count)
            {
                rVal.Add(new CompareDifference
                {
                    TableName = tableName,
                    TableNumber = tableNumber,
                    Message = string.Format("Non maching # of rows: {0} != {1}", 
                        fromTable.Rows.Count, toTable.Rows.Count)
                });
            }
            // Create new CompareDataTable for this table
            var compTables = new CompareDataTable     (fromTable, toTable, 
                tableName, tableNumber);
            int minColCount = Math.Min(fromTable.Rows.Count, toTable.Rows.Count);
            for (int i = 0; i < minColCount; ++i)
            {
                rVal.AddRange(compTables.CompareRow(
                    fromTable.Rows[i], toTable.Rows[i], i));
            }
            return rVal;
        }
    }
    /// <summary>
    /// Compares a DateTable row by row.
    /// I use a seperate non-static class so I don't have to re-aquire
    /// the table's Columns (Overkill, I know)
    /// </summary>
    public class CompareDataTable
    {
        private DataTable _fromTable;
        private DataTable _toTable;
        private DataColumnCollection _fromColumns;
        private DataColumnCollection _toColumns;
        private string _tableName;
        public int _tableNumber;

        /// <summary>
        /// Constructor: I use this class to avoid re-aquiring Columns
        /// for each row.
        /// </summary>
        /// <param name="fromTable">The DataTable considered to be correct</param>
        /// <param name="toTable">The DataTable to be tested</param>
        /// <param name="tableName">The Name of the table to be compared</param>
        /// <param name="tableNumber">The zero based table number</param>
        public CompareDataTable(DataTable fromTable, DataTable toTable, 
            string tableName, int tableNumber)
        {
             _fromTable = fromTable;
             _toTable = toTable;
             _tableName = tableName;
             _tableNumber = tableNumber;

             // Remember the Table Columns
             _fromColumns = _fromTable.Columns;
             _toColumns = _toTable.Columns;
        }
        /// <summary>
        /// Compare the content of two DataRows
        /// </summary>
        /// <param name="fromRow">The DataRow considered to be correct</param>
        /// <param name="toRow">The DataRow  to be tested</param>
        /// <param name="rowNumber">The zero based number of the row</param>
        /// <returns>A list of Inconsistencies</returns>
        public List<CompareDifference> CompareRow(DataRow fromRow, 
        DataRow toRow, int rowNumber)
        {
            var rVal = new List<CompareDifference>();
            int minColCount = Math.Min(fromRow.ItemArray.Count(), 
            toRow.ItemArray.Count());

            for (int i = 0; i < minColCount; ++i)
            {
                object fromCell = fromRow.ItemArray[i];
                object toCell = toRow.ItemArray[i];
                string fromColName = _fromColumns[i].ColumnName;
                string toColName = _toColumns[i].ColumnName;
                // Column names must match
                if (fromColName != toColName)
                {
                    rVal.Add(new CompareDifference
                    {
                        TableName = _tableName,
                        TableNumber = _tableNumber,
                        RowNumber = rowNumber,
                        ColumnName = fromColName,
                        ColumnNumber = i,
                        Message = string.Format(
                            "Non matching column names \"{0}\" != \"{1}\"", 
                            fromColName, toColName)
                        });
                }
                // Type must match
                if (fromCell.GetType() != toCell.GetType())
                {
                    rVal.Add(new CompareDifference
                    {
                        TableName = _tableName,
                        TableNumber = _tableNumber,
                        RowNumber = rowNumber,
                        ColumnName = fromColName,
                        ColumnNumber = i,
                        ExpectedValue = fromCell.ToString(),
                        ActualValue = toCell.ToString(),
                        Message = string.Format(
                            "No matching types \"{0}\" != \"{1}\"", 
                            fromCell.GetType(), toCell.GetType())
                    });
                }
                // And values (string compare for simplicity)
                else if (fromCell.ToString() != toCell.ToString())
                {
                    rVal.Add(new CompareDifference
                    {
                        TableName = _tableName,
                        TableNumber = _tableNumber,
                        RowNumber = rowNumber,
                        ColumnName = fromColName,
                        ColumnNumber = i,
                        ExpectedValue = fromCell.ToString(),
                        ActualValue = toCell.ToString(),
                        Message = string.Format(
                            "No matching values \"{0}\" != \"{1}\"", 
                            fromCell, toCell)
                    });
                }
            }
            return rVal;
        }
    }
    /// <summary>
    /// Simple class to store error messages
    /// </summary>
    public class CompareDifference
    {
        /// <summary>
        /// The Name of the Table 
        /// where the inconsistancy exists
        /// </summary>
        public int TableNumber { get; set; }
        /// <summary>
        /// The zero based number of the Table 
        /// where the inconsistancy exists
        /// </summary>
        public string TableName { get; set; }
        /// <summary>
        /// The zero based number of the column
        /// where the inconsistancy exists
        /// </summary>
        public int? ColumnNumber { get; set; }
        /// <summary>
        /// The name of the column
        /// where the inconsistancy exists
        /// </summary>
        public string ColumnName { get; set; }
        /// <summary>
        /// The number of the row 
        /// where the inconsistancy exists
        /// </summary>
        public int? RowNumber { get; set; }
        /// <summary>
        /// The expected value
        /// </summary>
        public string ExpectedValue { get; set; }
        /// <summary>
        ///  The actual value
        /// </summary>
        public string ActualValue { get; set; }
        /// <summary>
        /// An error message
        /// </summary>
        public string Message { get; set; }
    }
}

Sunday, September 25, 2011

Filtering files by Date Range

In this For Work post I am wrote a function that will return a list of files (FileInfo objects actually) for a given search string and date range.

The Problem

My internal customer wants archive files in a given directory for a given date range. Right now I am working on the part where I get the files; the rest of the problem is beyond the scope of this post.

My Solution

I am using FileInfo.GetFiles() to get a list of FileInfo objects for a given filename filter and then use Linq to filter that list for the date range.

So here’s my function:

/// <summary>
/// Returns a List of FileInfo for a given serch pattern and date range.
/// </summary>
/// <param name="searchPath">
/// The path + the search string
/// </param>
/// <param name="startDt">
/// The beginning date for the search (as of midnight)
/// </param>
/// <param name="endDt">
/// The end date of the search (as of midnight;
/// use DateTime(y, m, d 23, 59, 59) to get the whole day)
/// </param>
/// <param name="searchOp">
/// Specifies whether to search the current directory, or the current directory
/// and all subdirectories.        
/// </param>
/// <returns>
/// A List of FileInfo
/// </returns>
public static List<FileInfo> SearchFiles(string searchPath, DateTime? startDt, 
    DateTime? endDt, SearchOption searchOp)
{
    // Break searchPath into parts
    string directory = Path.GetDirectoryName(searchPath);
    string pattern = Path.GetFileName(searchPath);

    // DirectoryInfo exposes GetFiles used below
    var dinfo = new DirectoryInfo(directory);
    // Get all of the files that meet the criteria
    var finfol = new List<FileInfo>(dinfo.GetFiles(pattern, searchOp));

    // Throw out the files that are out of the date range
    // Here is where I would add aditional filters
    return new List<FileInfo>(
        from f in finfol
        where (f.LastWriteTime >= (startDt ?? DateTime.MinValue)) &&
              (f.LastWriteTime <= (endDt ?? DateTime.MaxValue))
        select f);
}

Tuesday, April 12, 2011

Generate Linq To Sql .DBML Fragments

Another user for my post on getting the schema from a Stored Procedure. This time I am generating a Dbml fragment.

In the Column element, I am not providing a DbType. I don’t use Linq to Sql in such a way that it actually uses that value for anything important. I am also assuming that CanBeNull is true.

Yes, this is totally a message for work post. If you need detail as to what I’m doing, check this out.

<#@ assembly name="TsqlDesriptionUtil.dll" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="TsqlDesriptionUtil" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
    public class DbmlFunctionFromTSqlCmd : TextTransformation
    {
        public string FunctionName;
        public string FunctionMethod;
        public List<TableDescription> tables;

        public override string TransformText()
        {
#>    <Function Name="<#= FunctionName
                #>" Method="<#= FunctionMethod #>">
<#+ foreach(TableDescription table in tables)
    { #>        <ElementType Name="<#= FunctionMethod #>Result_<#= table.TableName #>">
<#+ foreach(FieldDescription field in table.Fields)
   { #>             <Column Name="<#= field.FieldName #>" 
                      Type="<#= field.FieldType.FullName  #>" 
                      CanBeNull="true" />
<#+ } #>
         </ElementType>
<#+ } #>
    </Function>
<#+ 
        return this.GenerationEnvironment.ToString();
    }
} #>

Again, for Demonstration purposes I wrote a simple T4 Template that includes the template above and uses the library I wrote in this post to get a description of the data.

<#@ template language="C#v3.5" debug="true" 
#><#@ output extension="txt" 
#><#@ include file="DbmlFunctionFromTSqlCmd.tt" 
#><#@ assembly name="TsqlDesriptionUtil.dll" 
#><#@ import namespace="TsqlDesriptionUtil" 
#><#
    // Since I'm generating XML, I will go to extreme measures to avoid new 
    // line characters like the strange formatting of declarations above ^
 
    // Get the table descriptions for given T-SQL
    string conString = @"Data Source=localhost;" +
      @"Initial Catalog=Northwind;Integrated Security=True";
    string commandString = "EXEC  CustOrderHist @CustomerID='ALFKI'";
    List<TableDescription> tables = 
      TsqlCommandSchema.GetFieldDescription(conString, commandString);
 
    // Generate DBML Fragment:
    var gen = new DbmlFunctionFromTSqlCmd();
    gen.FunctionName = "dbo.GetBigTableDataUseTempTab";
    gen.FunctionMethod = "MyNew";
    gen.tables = tables;
    Write(gen.TransformText()); 
#>

(If I were more of a studman programmer dude, I'd write a Visual Studio Add-in that would insert this fragment directly into my .dbml file.)

Tuesday, March 29, 2011

Generate DataSet .XDS

Last week on my Brochure, I wrote about getting the schema of a Stored Procedure or other T-SQL command. Now I am going to bring this a little farther and generate am .XDS file that can be used to generate a Typed DataSet. This T4 Template include file takes a List and a DataSet name and generates the .XSD.

<#@ assembly name="TsqlDesriptionUtil.dll" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="TsqlDesriptionUtil" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
    public class DataSetFromTSqlCmd: TextTransformation
    {
        public string dsName;
        public List<TableDescription> tables;

        public override string TransformText()
        {
#><?xml version="1.0" encoding="utf-8"?>
<xs:schema id="<#= dsName #>" 
  targetNamespace="http://jrcs3.blogspot.com/<#= dsName #>.xsd"
  xmlns:mstns="http://jrcs3.blogspot.com/<#= dsName #>.xsd" 
  xmlns="http://jrcs3.blogspot.com/<#= dsName #>.xsd" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
  xmlns:msprop="urn:schemas-microsoft-com:xml-msprop" 
  attributeFormDefault="qualified" 
  elementFormDefault="qualified">
  <xs:annotation>
    <xs:appinfo source="urn:schemas-microsoft-com:xml-msdatasource">
      <DataSource DefaultConnectionIndex="0" 
          FunctionsComponentName="QueriesTableAdapter"
          Modifier="AutoLayout, AnsiClass, Class, Public" 
          SchemaSerializationMode="IncludeSchema" 
          xmlns="urn:schemas-microsoft-com:xml-msdatasource">
        <Connections />
        <Tables />
        <Sources />
      </DataSource>
    </xs:appinfo>
  </xs:annotation>
 <xs:element name="<#= dsName #>" 
        msdata:IsDataSet="true" 
        msdata:UseCurrentLocale="true" 
        msprop:Generator_UserDSName="<#= dsName #>" 
        msprop:Generator_DataSetName="<#= dsName #>" 
        msprop:EnableTableAdapterManager="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
<#+ foreach(TableDescription table in tables)
    { #>
        <xs:element 
            name="<#= table.TableName #>"  
            msprop:Generator_UserTableName="<#= table.TableName #>" 
            msprop:Generator_RowDeletedName="<#= table.TableName #>RowDeleted" 
            msprop:Generator_RowChangedName="<#= table.TableName #>RowChanged" 
            msprop:Generator_RowClassName="<#= table.TableName #>Row" 
            msprop:Generator_RowChangingName="<#= table.TableName #>RowChanging" 
            msprop:Generator_RowEvArgName="<#= table.TableName #>RowChangeEvent" 
            msprop:Generator_RowEvHandlerName="<#= table.TableName 
               #>RowChangeEventHandler" 
            msprop:Generator_TableClassName="<#= table.TableName #>DataTable" 
            msprop:Generator_TableVarName="table<#= table.TableName #>" 
            msprop:Generator_RowDeletingName="<#= table.TableName #>RowDeleting" 
            msprop:Generator_TablePropName="<#= table.TableName #>">
          <xs:complexType>
            <xs:sequence>
<#+ foreach(FieldDescription field in table.Fields)
 { #>
                <xs:element 
                    name="<#= field.FieldName #>" 
                    msprop:Generator_UserColumnName="<#= field.FieldName #>"
                    msprop:Generator_ColumnVarNameInTable="column<#= field.FieldName 
                      #>" 
                    msprop:Generator_ColumnPropNameInRow="<#= field.FieldName #>" 
                    msprop:Generator_ColumnPropNameInTable="<#= field.FieldName 
                      #>Column" 
                    type="xs:<#= field.XsdTypeName #>" 
                    minOccurs="0" />
<#+ } #>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
<#+ } #>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>
<#+ 
  return this.GenerationEnvironment.ToString();
 }
} #>

For Demonstration purposes I wrote a simple T4 Template that includes the template above and uses the library I wrote in this post to get a description of the data.

<#@ template language="C#v3.5" debug="true" 
#><#@ output extension="xsd" 
#><#@ include file="DataSetFromTSqlCmd.tt" 
#><#@ assembly name="TsqlDesriptionUtil.dll" 
#><#@ import namespace="TsqlDesriptionUtil" 
#><#
    // Since I'm generating XML, I will go to extreme measures to avoid new line 
    // characters like the strange formatting of declarations above ^

    // Get the table descriptions for given T-SQL
    string conString = @"Data Source=localhost;" +
      @"Initial Catalog=Northwind;Integrated Security=True";
    string commandString = "EXEC  CustOrderHist @CustomerID='ALFKI'";
    List<TableDescription> tables = 
      TsqlCommandSchema.GetFieldDescription(conString, commandString);

    // Generate XSD:
    var gen = new DataSetFromTSqlCmd();
    gen.dsName = "MyNewDS";
    gen.tables = tables;
    Write(gen.TransformText()); 
#>

Since TsqlCommandSchema.GetFieldDescription supports multiple results sets and Stored Procedure that use temporary tables.

At work I will probably integrate this template into an existing code generation tool using my TTCommucator tool.

Tuesday, March 22, 2011

Getting the Schema of a Stored Procedure Results Set

Visual Studio is pretty good at discovering the schema of a stored procedure if the procedure doesn't use temporary tables and returns only one result set. You can drag the stored procedure on to a DataSet or Linq to Sql Design Surface and it is generated for you. In real life, stored procedures don’t fit into that neat box.

Since I am lazy and don’t like to build DataSets by hand, I wrote this little class. Note that I am actually executing the T-SQL command. Any side effect of the stored procedure will take place; this is acceptable in my environment, it may not be in yours.

I am getting less information from this method than I get from static sources like SQL Server’s SMO; for example, I am not getting the length of strings. I am getting enough information to do what I want to do.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace TsqlDesriptionUtil
{
    public class TsqlCommandSchema
    {
        public static List<TableDescription> GetFieldDescription(
            string conString, string commandString)
        {
            var rVal = new List<TableDescription>();
            int tableNumber = 1;
            using (var con = new SqlConnection(conString))
            {
                using (var com = new SqlCommand(commandString, con))
                {
                    con.Open();
                    // WARNING: I am executing the command, beware of side effects!
                    SqlDataReader reader = 
                        com.ExecuteReader(CommandBehavior.CloseConnection);
                    // Table Loop
                    do
                    {
                        var fieldDescriptionList = new List<FieldDescription>();
                        // Field Loop
                        for (int i = 0; i < reader.FieldCount; ++i)
                        {
                            Type fieldType = reader.GetProviderSpecificFieldType(i);
                            fieldDescriptionList.Add(
                                new FieldDescription(reader.GetName(i), 
                                reader.GetDataTypeName(i), reader.GetFieldType(i)));
                        }
                        // T-SQL doesn't give table names, so I will make up my own
                        rVal.Add(new TableDescription(string.Format(
                            "Table{0}", tableNumber++), fieldDescriptionList));
                    } while (reader.NextResult());
                    con.Close();
                }
            }
            return rVal;
        }
    }
    public class TableDescription
    {
        public TableDescription(string tableName, List<FieldDescription> fields)
        {
            this.TableName = tableName;
            this.Fields = fields;
        }
        public string TableName { get; set; }
        public List<FieldDescription> Fields { get; set; }
        public int Count
        {
            get
            {
                if (this.Fields != null)
                    return this.Fields.Count;
                else
                    throw new Exception("Fields not loaded");
            }
        }
    }
    public class FieldDescription
    {
        public FieldDescription(string fieldName, string dataTypeName, Type fieldType)
        {
            this.FieldName = fieldName;
            this.DataTypeName = dataTypeName;
            this.FieldType = fieldType;
        }
        public string FieldName { get; set; }
        public string DataTypeName { get; set; }
        public Type FieldType { get; set; }
        public string XsdTypeName
        {
            get
            {
                Type t = this.FieldType;
                return getXsdTypeNameForType(t);
            }
        }

        private static string getXsdTypeNameForType(Type t)
        {
            string rVal = string.Empty;
            //.NET Framework type XML Schema (XSD) type
            switch (t.FullName)
            {
                case "System.Boolean": rVal = "Boolean"; break;
                case "System.Byte": rVal = "unsignedByte"; break;
                case "System.Byte[]": rVal = "base64Binary"; break;
                case "System.DateTime": rVal = "dateTime"; break;
                case "System.Decimal": rVal = "decimal"; break;
                case "System.Double": rVal = "Double"; break;
                case "System.Int16": rVal = "short"; break;
                case "System.Int32": rVal = "int"; break;
                case "System.Int64": rVal = "long"; break;
                case "System.SByte": rVal = "Byte"; break;
                case "System.String": rVal = "string"; break;
                case "System.String[]": rVal = "ENTITIES"; break;
                case "System.TimeSpan": rVal = "duration"; break;
                case "System.UInt16": rVal = "unsignedShort"; break;
                case "System.UInt32": rVal = "unsignedInt"; break;
                case "System.UInt64": rVal = "unsignedLong"; break;
                case "System.Uri": rVal = "anyURI"; break;
                case "System.Xml.XmlQualifiedName": rVal = "QName"; break;
            }
            return rVal;
        }
    }
}

I compiled the above C# source file into an assembly. Here is a xUnit test that demonstrates its use.

[Fact(DisplayName = "Run SP 001")]
public void RunSp001()
{
    string conString = @"Data Source=localhost;Initial Catalog=My_Db;" + 
        "Integrated Security=True";
    string commandString = "EXEC  GetBigTableDataUseTempTab 101";
    List<TableDescription> rVal = 
        TsqlCommandSchema.GetFieldDescription(conString, commandString);

    foreach (var item in rVal) 
    {
        Console.WriteLine(string.Format("{0}: {1}", item.TableName, item.Count));
        foreach(var field in item.Fields)
            Console.WriteLine(
                string.Format("\t{0}\t{1}\t{2}\t{3}", 
                    field.FieldName, 
                    field.DataTypeName, 
                    field.FieldType, 
                    field.XsdTypeName));
    }
}