SchemaConverter
An experiment in using ADO 2.0 DbProviderFactories for SQLServer/Oracle/MySql/SQLite/Firebird.
Now a CodePlex project - see there for latest code.
- SchemaReader - use DbProviderFactories to read database metadata.
- SchemaExtendedReader - provides more SqlServer/Oracle/MySql schema information not available in GetSchema.
- DatabaseReader - calls SchemaReader to build a database model
- SchemaConverter - converts DbProviderFactory datatables into a database model
- CreateSql - build (simple) select/insert/update sql using SchemaReader above
Reads the DataTables created by SchemaReader into plain objects (Table, Column ...). Used by DatabaseReader. There are similar converters for constraints, indexes, procedures and procedure arguments.
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
namespace Library.Data.DataSchema
{
/// <summary>
/// Converts the DataTables returned by DbConnection.GetSchema into objects.
/// Resolves differences between SqlServer and Oracle names
/// </summary>
/// <remarks>
/// SqlServer: http://msdn.microsoft.com/en-us/library/ms254969.aspx
/// Oracle: http://msdn.microsoft.com/en-us/library/cc716723.aspx
/// Also supports OleDb, MySql, SQLite, DataDirect and Devart providers
/// </remarks>
public static class SchemaConverter
{
/// <summary>
/// Converts the "Tables" DataTable into <see cref="DatabaseTable"/> objects.
/// </summary>
/// <remarks>
/// Note the SqlServer DataTable includes views, which we explicitly remove.
/// </remarks>
public static List<DatabaseTable> Tables(DataTable dt)
{
List<DatabaseTable> list = new List<DatabaseTable>();
//sql server
string key = "TABLE_NAME";
string ownerKey = "TABLE_SCHEMA";
string typeKey = "TABLE_TYPE";
//oracle
if (!dt.Columns.Contains(ownerKey)) ownerKey = "OWNER";
if (!dt.Columns.Contains(typeKey)) typeKey = "TYPE";
//Devart.Data.Oracle - TABLE_NAME is NAME
if (!dt.Columns.Contains(key)) key = "NAME";
if (!dt.Columns.Contains(ownerKey)) ownerKey = "SCHEMA";
foreach (DataRow row in dt.Rows)
{
string type = row[typeKey].ToString();
//Sql server has base tables and views. Oracle has system and user
if (!type.Equals("TABLE", StringComparison.OrdinalIgnoreCase) &&
!type.Equals("BASE TABLE", StringComparison.OrdinalIgnoreCase) &&
!type.Equals("User", StringComparison.OrdinalIgnoreCase)) continue;
DatabaseTable t = new DatabaseTable();
t.Name = row[key].ToString();
//exclude Oracle bin tables
if (t.Name.StartsWith("BIN$", StringComparison.OrdinalIgnoreCase)) continue;
t.SchemaOwner = row[ownerKey].ToString();
list.Add(t);
}
return list;
}
/// <summary>
/// Converts the "Views" DataTable into <see cref="DatabaseView"/> objects.
/// </summary>
public static List<DatabaseView> Views(DataTable dt)
{
List<DatabaseView> list = new List<DatabaseView>();
string key = "TABLE_NAME"; //yep, it's Table_Name in SqlServer.
string ownerKey = "TABLE_SCHEMA";
string definition = "TEXT";
//firebird
if (!dt.Columns.Contains(ownerKey)) ownerKey = "VIEW_SCHEMA"; //always null
if (!dt.Columns.Contains(definition)) definition = "DEFINITION";
//oracle
if (!dt.Columns.Contains(key)) key = "VIEW_NAME";
if (!dt.Columns.Contains(ownerKey)) ownerKey = "OWNER";
//Oracle does not expose ViewColumns, only the raw sql.
bool hasSql = dt.Columns.Contains(definition);
//Devart.Data.Oracle
if (!dt.Columns.Contains(key)) key = "NAME";
if (!dt.Columns.Contains(ownerKey)) ownerKey = "SCHEMA";
foreach (DataRow row in dt.Rows)
{
DatabaseView t = new DatabaseView();
t.Name = row[key].ToString();
t.SchemaOwner = row[ownerKey].ToString();
if (hasSql) t.Sql = row[definition].ToString();
list.Add(t);
}
return list;
}
/// <summary>
/// Converts the "Columns" DataTable into <see cref="DatabaseColumn"/> objects
/// </summary>
public static List<DatabaseColumn> Columns(DataTable dt)
{
return Columns(dt, null);
}
/// <summary>
/// Converts the "Columns" DataTable into <see cref="DatabaseColumn"/> objects for a specified table
/// </summary>
public static List<DatabaseColumn> Columns(DataTable dt, string tableName)
{
List<DatabaseColumn> list = new List<DatabaseColumn>();
//sql server
const string key = "column_name";
const string tableKey = "table_name";
string ordinalKey = "ordinal_position";
string datatypeKey = "data_type";
string nullableKey = "is_nullable";
string lengthKey = "character_maximum_length";
string precisionKey = "numeric_precision";
string scaleKey = "numeric_scale";
string dateTimePrecision = "datetime_precision";
string defaultKey = "column_default";
//oracle
if (!dt.Columns.Contains(ordinalKey)) ordinalKey = "id";
if (!dt.Columns.Contains(datatypeKey)) datatypeKey = "datatype";
if (!dt.Columns.Contains(nullableKey)) nullableKey = "nullable";
if (!dt.Columns.Contains(lengthKey)) lengthKey = "length";
if (!dt.Columns.Contains(precisionKey)) precisionKey = "precision";
if (!dt.Columns.Contains(scaleKey)) scaleKey = "scale";
if (!dt.Columns.Contains(dateTimePrecision)) dateTimePrecision = null;
if (!dt.Columns.Contains(defaultKey)) defaultKey = null; //not in Oracle catalog
//sqlite
string autoIncrementKey = "AUTOINCREMENT";
string primaryKeyKey = "PRIMARY_KEY";
string uniqueKey = "UNIQUE";
if (!dt.Columns.Contains(autoIncrementKey)) autoIncrementKey = null;
if (!dt.Columns.Contains(primaryKeyKey)) primaryKeyKey = null;
if (!dt.Columns.Contains(uniqueKey)) uniqueKey = null;
//firebird
if (!dt.Columns.Contains(datatypeKey)) datatypeKey = "column_data_type";
if (!dt.Columns.Contains(lengthKey)) lengthKey = "COLUMN_SIZE";
dt.DefaultView.Sort = ordinalKey;
//this could be more than one table, so filter the view
if (!string.IsNullOrEmpty(tableName))
dt.DefaultView.RowFilter = "[" + tableKey + "] = '" + tableName + "'";
foreach (DataRowView row in dt.DefaultView)
{
DatabaseColumn c = new DatabaseColumn();
c.Name = row[key].ToString();
c.TableName = row[tableKey].ToString();
c.Ordinal = Convert.ToInt32(row[ordinalKey], CultureInfo.CurrentCulture);
c.DBDataType = row[datatypeKey].ToString();
string nullable = row[nullableKey].ToString();
//could be Y, YES, N, NO, true, false.
if (nullable.StartsWith("Y", StringComparison.OrdinalIgnoreCase)) //Y or YES
c.Nullable = true;
else if (nullable.StartsWith("N", StringComparison.OrdinalIgnoreCase)) //N or NO
c.Nullable = false;
//sqlite has a boolean type
else if ((bool)row[nullableKey])
c.Nullable = true;
//the length unless it's an OleDb blob or clob
c.Length = GetNullableInt(row[lengthKey]);
c.Precision = GetNullableInt(row[precisionKey]);
c.Scale = GetNullableInt(row[scaleKey]);
if (dateTimePrecision != null)
{
c.DateTimePrecision = GetNullableInt(row[dateTimePrecision]);
}
if (!string.IsNullOrEmpty(defaultKey))
{
string d = row[defaultKey].ToString();
if (!string.IsNullOrEmpty(d)) c.DefaultValue = d.Trim(new[] { ' ', '\'', '=' });
}
if (!string.IsNullOrEmpty(primaryKeyKey) && (bool)row[primaryKeyKey])
c.IsPrimaryKey = true;
if (!string.IsNullOrEmpty(autoIncrementKey) && (bool)row[autoIncrementKey])
c.IsIdentity = true;
if (!string.IsNullOrEmpty(uniqueKey) && (bool)row[uniqueKey])
c.IsUniqueKey = true;
list.Add(c);
}
return list;
}
public static List<DataType> DataTypes(DataTable dataTable)
{
List<DataType> list = new List<DataType>();
if (dataTable == null || dataTable.Rows.Count == 0) return list;
foreach (DataRow row in dataTable.Rows)
{
DataType d = new DataType();
d.TypeName = row["TypeName"].ToString();
d.NetDataType = row["DataType"].ToString();
d.ProviderDbType = Convert.ToInt32(row["ProviderDbType"], CultureInfo.InvariantCulture);
d.LiteralPrefix = row["LiteralPrefix"].ToString();
d.LiteralSuffix = row["LiteralSuffix"].ToString();
d.CreateFormat = row["CreateFormat"].ToString();
list.Add(d);
}
return list;
}
private static int? GetNullableInt(object o)
{
try
{
return (o != DBNull.Value) ? Convert.ToInt32(o, CultureInfo.CurrentCulture) : (int?)null;
}
catch (OverflowException)
{
//this occurs for blobs and clobs using the OleDb provider
return -1;
}
}
}
}