I have database table having one column defined as timestamp without time zone. Now from my c# application, when I try to insert null value in that column using NpgSql BeginBinaryImport it gives error message as mentioned below:
08P01: insufficient data left in message
Below is the code which I am trying to execute:
static void Main(string[] args)
{
BulkInsert();
}
private static void BulkInsert()
{
DataTable table = new DataTable();
table.Columns.Add("firstname", typeof(String));
table.Columns.Add("lastname", typeof(String));
table.Columns.Add("logdatetime", typeof(DateTime));
table.Columns.Add("status", typeof(int));
table.Columns.Add("id", typeof(long));
var dataRow = table.NewRow();
dataRow["firstname"] = "MyFirstName";
dataRow["lastname"] = "MyLastName";
dataRow["logdatetime"] = DBNull.Value;
dataRow["status"] = 1;
dataRow["id"] = 10;
table.Rows.Add(dataRow);
var data = new DataAccess();
using (var npgsqlConn = new NpgsqlConnection([ConnectionString]))
{
npgsqlConn.Open();
var commandFormat = string.Format(CultureInfo.InvariantCulture, "COPY {0} {1} FROM STDIN BINARY", "logging.testtable", "(firstName,LastName,logdatetime,status,id)");
using (var writer = npgsqlConn.BeginBinaryImport(commandFormat))
{
foreach (DataRow item in dataTable.Rows)
{
writer.StartRow();
foreach (var item1 in item.ItemArray)
{
writer.Write(item1);
}
}
}
npgsqlConn.Close();
}
The issue is the DBNull.Value you're trying to write - Npgsql doesn't support writing nulls this way. To write a null you need to use the WriteNull() method instead.
I can make Npgsql accept DBNull.Value, but only for the overload of Write() which also accepts an NpgsqlDbType (because Npgsql has to write the data type, and with DBNull.Value we have no idea what that is).
EDIT: Have done this, see https://github.com/npgsql/npgsql/issues/1122.
I faced same issue while bulk copying data in table. To solve this I have created an extension method so you dont have to null check on all fields
public static void WriteWithNullCheck(this NpgsqlBinaryImporter writer, string value)
{
if (string.IsNullOrEmpty(value))
{
writer.WriteNull();
}
else
{
writer.Write(value);
}
}
this can be made generic by
public static void WriteWithNullCheck<T>(this NpgsqlBinaryImporter writer, T value,NpgsqlDbType type)
{
if (value == null)
{
writer.WriteNull();
}
else
{
writer.Write(value, type);
}
}
Related
I have a DataTable with 3x string columns, a constraint and I am trying to fill it with unique rows. The first row adds nicely but unfortunately when I am trying to add a second one with unique values I am receiving a false constraint error message.
I have distilled my custom class to the minimum and added an extra check for the two values before trying to add them, but the result is contradictory.
Here it goes:
using System.Data;
using System.Collections;
namespace DataTableForeignKey
{
public class Symbols : IEnumerable
{
protected DataTable fTable = new DataTable();
protected DataColumn fCategoryColumn = new DataColumn("Category", typeof(string));
protected DataColumn fNameColumn = new DataColumn("Name", typeof(string));
protected DataColumn fValueColumn = new DataColumn("Value", typeof(string));
public Symbols()
{
fTable.Columns.Add(fCategoryColumn);
fTable.Columns.Add(fNameColumn);
fTable.Columns.Add(fValueColumn);
// Temporarily disabled
//fTable.Constraints.Add(new ForeignKeyConstraint(fNameColumn, fCategoryColumn));
fTable.Constraints.Add(new ForeignKeyConstraint(fValueColumn, fCategoryColumn));
}
public virtual void Add(string aCategory, string aName, string aValue)
{
var lRow = fTable.NewRow();
lRow[fCategoryColumn] = aCategory;
lRow[fNameColumn] = aName;
lRow[fValueColumn] = aValue;
fTable.Rows.Add(lRow);
}
IEnumerator IEnumerable.GetEnumerator()
{
return fTable.Rows.GetEnumerator();
}
}
class Program
{
static void Main(string[] args)
{
var lSymbols = new Symbols();
var lWhitespace = " ";
var lEmptyString = string.Empty;
if (lWhitespace != lEmptyString)
{
lSymbols.Add("Whitespace", "Separator", lWhitespace);
lSymbols.Add("Prefix", "Command", lEmptyString);
// The second Add() throws the following exception:
// System.Data.ConstraintException: 'Column 'Value' is
// constrained to be unique. Value '' is already present.'
}
}
}
}
How can an empty string match with a whitespace?
Many thanks for the answers in advance.
What you are looking for is a UniqueConstraint. So you can change that line of code to:
fTable.Constraints.Add(new UniqueConstraint(new DataColumn[] { fValueColumn, fCategoryColumn}));
Sql Server has the variable TEST_TIME data type as Time(7)
I created the tables in C# and it automatically assigned the Timespan datatype.
Now, i'm trying to upload csv file data to the SQL database and it is giving me an error " Cannot implicitly convert DateTime to Timespan". What would be the best way to fix this?
The user first selects the CSV file:
private void button8_Click(object sender, EventArgs e)
{
try
{
using (OpenFileDialog openfiledialog1 = new OpenFileDialog()
{Filter = "Excel Workbook 97-2003|*.xls|Excel Workbook|*.xlsx|Excel Workbook|*.xlsm|Excel Workbook|*.csv|Excel Workbook|*.txt", ValidateNames = true })
{
--After some IFs--
else if (openfiledialog1.FilterIndex == 4)
{
DataTable oDataTable = null;
int RowCount = 0;
string[] ColumnNames = null;
string[] oStreamDataValues = null;
//using while loop read the stream data till end
while (!oStreamReader.EndOfStream)
{
String oStreamRowData = oStreamReader.ReadLine().Trim();
if (oStreamRowData.Length > 0)
{
oStreamDataValues = oStreamRowData.Split(',');
//Bcoz the first row contains column names, we will populate
//the column name by
//reading the first row and RowCount-0 will be true only once
if (RowCount == 0)
{
RowCount = 1;
ColumnNames = oStreamRowData.Split(',');
oDataTable = new DataTable();
//using foreach looping through all the column names
foreach (string csvcolumn in ColumnNames)
{
DataColumn oDataColumn = new DataColumn(csvcolumn.ToUpper(), typeof(string));
//setting the default value of empty.string to newly created column
oDataColumn.DefaultValue = string.Empty;
//adding the newly created column to the table
oDataTable.Columns.Add(oDataColumn);
}
}
else
{
//creates a new DataRow with the same schema as of the oDataTable
DataRow oDataRow = oDataTable.NewRow();
//using foreach looping through all the column names
for (int i = 0; i < ColumnNames.Length; i++)
{
oDataRow[ColumnNames[i]] = oStreamDataValues[i] == null ? string.Empty : oStreamDataValues[i].ToString();
}
//adding the newly created row with data to the oDataTable
oDataTable.Rows.Add(oDataRow);
}
}
}
result.Tables.Add(oDataTable);
//close the oStreamReader object
oStreamReader.Close();
//release all the resources used by the oStreamReader object
oStreamReader.Dispose();
dataGridView5.DataSource = result.Tables[oDataTable.TableName];
}
Here is the Code:
private void button9_Click(object sender, EventArgs e)
{
try
{
DataClasses1DataContext conn = new DataClasses1DataContext();
else if (textBox3.Text.Contains("GEN_EX"))
{
foreach (DataTable dt in result.Tables)
{
foreach (DataRow dr in dt.Rows)
{
GEN_EX addtable = new GEN_EX()
{
EX_ID = Convert.ToByte(dr[0]),
DOC_ID = Convert.ToByte(dr[1]),
PATIENT_NO = Convert.ToByte(dr[2]),
TEST_DATE = Convert.ToDateTime(dr[3]),
**TEST_TIME = Convert.ToDateTime((dr[4])),**
};
conn.GEN_EXs.InsertOnSubmit(addtable);
}
}
conn.SubmitChanges();
MessageBox.Show("File uploaded successfully");
}
else
{
MessageBox.Show("I guess table is not coded yet");
}
}
EDIT
The TEST_TIME represents HH:MM:SS
The Typed Data Set is defined as:
public virtual int Update(
byte EX_ID,
byte DOC_ID,
byte PATIENT_NO,
System.DateTime TEST_DATE,
System.TimeSpan TEST_TIME)
Based on your input that dr[4] represents time values in hours:minutes:seconds format I recommend following solution.
private TimeSpan GetTimeSpan(string timeString)
{
var timeValues = timeString.Split(new char[] { ':' });
//Assuming that timeValues array will have 3 elements.
var timeSpan = new TimeSpan(Convert.ToInt32(timeValues[0]), Convert.ToInt32(timeValues[1]), Convert.ToInt32(timeValues[2]));
return timeSpan;
}
Use above method as following.
else if (textBox3.Text.Contains("GEN_EX"))
{
foreach (DataTable dt in result.Tables)
{
foreach (DataRow dr in dt.Rows)
{
GEN_EX addtable = new GEN_EX()
{
EX_ID = Convert.ToByte(dr[0]),
DOC_ID = Convert.ToByte(dr[1]),
PATIENT_NO = Convert.ToByte(dr[2]),
TEST_DATE = Convert.ToDateTime(dr[3]),
**TEST_TIME = GetTimeSpan(dr[4].ToString()),**
};
conn.GEN_EXs.InsertOnSubmit(addtable);
}
}
conn.SubmitChanges();
MessageBox.Show("File uploaded successfully");
}
This should give your the value you want. You will face runtime issues if value of dr[4] is not in hours:minutes:seconds format. That I will leave it up to you.
First of all Timespan and DateTime are 2 differents type without implicit conversion available. Since Timespan is a time value between two DateTime, you need to know your referenced time (DateTime) used to start the mesure of your Timespan.
For example it could be from DateTime dtReferential = new DateTime(1900, 01, 01);
In order to give a SQL Timespan value you need to give it a C# Timespan! Change TEST_TIME value to a Timespan. And finally, give it the substracted value of your referenced time.
Using the previous example:
else if (textBox3.Text.Contains("GEN_EX"))
{
foreach (DataTable dt in result.Tables)
{
foreach (DataRow dr in dt.Rows)
{
GEN_EX addtable = new GEN_EX()
{
EX_ID = Convert.ToByte(dr[0]),
DOC_ID = Convert.ToByte(dr[1]),
PATIENT_NO = Convert.ToByte(dr[2]),
TEST_DATE = Convert.ToTimespan(dr[3]),
TEST_TIME = dtReferential.Subtract(Convert.ToDateTime(dr[4]))
};
conn.GEN_EXs.InsertOnSubmit(addtable);
}
}
conn.SubmitChanges();
MessageBox.Show("File uploaded successfully");
}
I've just learned about Generics and I'm wondering whether I can use it to dynamically build datatables from my classes.
Or I might be missing the point here.
Here is my code, what I'm trying to do is create a datatable from my existing class and populate it. However I'm getting stuck in my thought process.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
namespace Generics
{
public class Dog
{
public string Breed { get; set; }
public string Name { get; set; }
public int legs { get; set; }
public bool tail { get; set; }
}
class Program
{
public static DataTable CreateDataTable(Type animaltype)
{
DataTable return_Datatable = new DataTable();
foreach (PropertyInfo info in animaltype.GetProperties())
{
return_Datatable.Columns.Add(new DataColumn(info.Name, info.PropertyType));
}
return return_Datatable;
}
static void Main(string[] args)
{
Dog Killer = new Dog();
Killer.Breed = "Maltese Poodle";
Killer.legs = 3;
Killer.tail = false;
Killer.Name = "Killer";
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);
//How do I continue from here?
}
}
}
Now At the DataTable point it errors.
Also, being new to reflection and Generics, how will I actually populate the data with the Killer class?
Building up on all the previous answers, here is a version that creates a DataTable from any collection:
public static DataTable CreateDataTable<T>(IEnumerable<T> list)
{
Type type = typeof(T);
var properties = type.GetProperties();
DataTable dataTable = new DataTable();
dataTable.TableName = typeof(T).FullName;
foreach (PropertyInfo info in properties)
{
dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
}
foreach (T entity in list)
{
object[] values = new object[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
values[i] = properties[i].GetValue(entity);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
Here is a more compact version of David's answer that is also an extension function. I've posted the code in a C# project on Github.
public static class Extensions
{
public static DataTable ToDataTable<T>(this IEnumerable<T> self)
{
var properties = typeof(T).GetProperties();
var dataTable = new DataTable();
foreach (var info in properties)
dataTable.Columns.Add(info.Name, Nullable.GetUnderlyingType(info.PropertyType)
?? info.PropertyType);
foreach (var entity in self)
dataTable.Rows.Add(properties.Select(p => p.GetValue(entity)).ToArray());
return dataTable;
}
}
I have found that this works very well in conjunction with code to write a DataTable to CSV.
my favorite homemade function. it create and populate all at same time. throw any object.
public static DataTable ObjectToData(object o)
{
DataTable dt = new DataTable("OutputData");
DataRow dr = dt.NewRow();
dt.Rows.Add(dr);
o.GetType().GetProperties().ToList().ForEach(f =>
{
try
{
f.GetValue(o, null);
dt.Columns.Add(f.Name, f.PropertyType);
dt.Rows[0][f.Name] = f.GetValue(o, null);
}
catch { }
});
return dt;
}
The error can be resolved by changing this:
dogTable = CreateDataTable(Dog);
to this:
dogTable = CreateDataTable(typeof(Dog));
But there are some caveats with what you're trying to do. First, a DataTable can't store complex types, so if Dog has an instance of Cat on it, you won't be able to add that as a column. It's up to you what you want to do in that case, but keep it in mind.
Second, I would recommend that the only time you use a DataTable is when you're building code that knows nothing about the data its consuming. There are valid use cases for this (e.g. a user-driven data mining tool). If you already have the data in the Dog instance, just use it.
Another little tidbit, this:
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);
can be condensed to this:
DataTable dogTable = CreateDataTable(Dog);
Here is a little bit modified code, which fixed time zone issue for datatime fields:
public static DataTable ToDataTable<T>(this IList<T> data)
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
if (props[i].PropertyType == typeof(DateTime))
{
DateTime currDT = (DateTime)props[i].GetValue(item);
values[i] = currDT.ToUniversalTime();
}
else
{
values[i] = props[i].GetValue(item);
}
}
table.Rows.Add(values);
}
return table;
}
Here's a VB.Net version that creates a data table from a generic list passed to the function as an object. There is also a helper function (ObjectToDataTable) that creates a data table from an object.
Imports System.Reflection
Public Shared Function ListToDataTable(ByVal _List As Object) As DataTable
Dim dt As New DataTable
If _List.Count = 0 Then
MsgBox("The list cannot be empty. This is a requirement of the ListToDataTable function.")
Return dt
End If
Dim obj As Object = _List(0)
dt = ObjectToDataTable(obj)
Dim dr As DataRow = dt.NewRow
For Each obj In _List
dr = dt.NewRow
For Each p as PropertyInfo In obj.GetType.GetProperties
dr.Item(p.Name) = p.GetValue(obj, p.GetIndexParameters)
Next
dt.Rows.Add(dr)
Next
Return dt
End Function
Public Shared Function ObjectToDataTable(ByVal o As Object) As DataTable
Dim dt As New DataTable
Dim properties As List(Of PropertyInfo) = o.GetType.GetProperties.ToList()
For Each prop As PropertyInfo In properties
dt.Columns.Add(prop.Name, prop.PropertyType)
Next
dt.TableName = o.GetType.Name
Return dt
End Function
Using the answer provided by #neoistheone I've changed the following sections. Works fine now.
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(typeof(Dog));
dogTable.Rows.Add(Killer.Breed, Killer.Name,Killer.legs,Killer.tail);
foreach (DataRow row in dogTable.Rows)
{
Console.WriteLine(row.Field<string>("Name") + " " + row.Field<string>("Breed"));
Console.ReadLine();
}
you can convert the object to xml then load the xml document to a dataset, then extract the first table out of the data set. However i dont see how this be practical as it infers creating streams, datasets & datatables and using converstions to create the xml document.
I guess for proof of concept i can understand why. Here is an example, but somewhat hesitant to use it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml.Serialization;
namespace Generics
{
public class Dog
{
public string Breed { get; set; }
public string Name { get; set; }
public int legs { get; set; }
public bool tail { get; set; }
}
class Program
{
public static DataTable CreateDataTable(Object[] arr)
{
XmlSerializer serializer = new XmlSerializer(arr.GetType());
System.IO.StringWriter sw = new System.IO.StringWriter();
serializer.Serialize(sw, arr);
System.Data.DataSet ds = new System.Data.DataSet();
System.Data.DataTable dt = new System.Data.DataTable();
System.IO.StringReader reader = new System.IO.StringReader(sw.ToString());
ds.ReadXml(reader);
return ds.Tables[0];
}
static void Main(string[] args)
{
Dog Killer = new Dog();
Killer.Breed = "Maltese Poodle";
Killer.legs = 3;
Killer.tail = false;
Killer.Name = "Killer";
Dog [] array_dog = new Dog[5];
Dog [0] = killer;
Dog [1] = killer;
Dog [2] = killer;
Dog [3] = killer;
Dog [4] = killer;
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(array_dog);
// continue here
}
}
}
look the following example here
If you want to set columns order/ Include only some columns/ exclude some columns try this:
private static DataTable ConvertToDataTable<T>(IList<T> data, string[] fieldsToInclude = null,
string[] fieldsToExclude = null)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
{
if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
continue;
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
}
foreach (T item in data)
{
var atLeastOnePropertyExists = false;
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
continue;
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
atLeastOnePropertyExists = true;
}
if(atLeastOnePropertyExists) table.Rows.Add(row);
}
if (fieldsToInclude != null)
SetColumnsOrder(table, fieldsToInclude);
return table;
}
private static void SetColumnsOrder(DataTable table, params String[] columnNames)
{
int columnIndex = 0;
foreach (var columnName in columnNames)
{
table.Columns[columnName].SetOrdinal(columnIndex);
columnIndex++;
}
}
I need my program check if specified column exists in MS Access 2000 database, and if it doesn't - add it. I use .NET Framework 2.0
I tried to use oleDbConnection.GetSchema() method, but couldn't find column names in metadata (i'm really not a pro, huh) and any specification on msdn.
I would appreciate any help.
Thanks for answers.
Here is solution i used in my code:
bool flag = false; string[] restrictions = new string[] { null, null, mytable };
DataTable dtColumns = oleDbConnection1.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Columns, restrictions);
foreach (DataRow row in dtColumns.Rows)
{
if (mycolumnname==(string)row["COLUMN_NAME"]) flag = true;
}
This is code that is part of a o/r-mapper of mine. You cannot use it as is beacuse it depends on other classes, but I hope you get the picture.
Define restrictions like this
string[] restrictions = new string[] { null, null, tableName };
This retrieves the columns from a table
private void RetrieveColumnInfo(OleDbConnection cnn, TableSchema tableSchema,
string[] restrictions, Func<string, string> prepareColumnNameForMapping)
{
using (DataTable dtColumns =
cnn.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, restrictions)) {
string AutoNumberColumn = RetrieveAutoNumberColumn(cnn, tableSchema);
foreach (DataRow row in dtColumns.Rows) {
var col = new TableColumn();
col.ColumnName = (string)row["COLUMN_NAME"];
try {
col.ColumnNameForMapping =
prepareColumnNameForMapping(col.ColumnName);
} catch (Exception ex) {
throw new UnimatrixExecutionException(
"Error in delegate 'prepareColumnNameForMapping'", ex);
}
col.ColumnAllowsDBNull = (bool)row["IS_NULLABLE"];
col.ColumnIsIdentity = col.ColumnName == AutoNumberColumn;
DbColumnFlags flags = (DbColumnFlags)(long)row["COLUMN_FLAGS"];
col.ColumnIsReadOnly =
col.ColumnIsIdentity ||
(flags & (DbColumnFlags.Write | DbColumnFlags.WriteUnknown)) ==
DbColumnFlags.None;
if (row["CHARACTER_MAXIMUM_LENGTH"] != DBNull.Value) {
col.ColumnMaxLength = (int)(long)row["CHARACTER_MAXIMUM_LENGTH"];
}
col.ColumnDbType = GetColumnDbType((int)row["DATA_TYPE"]);
col.ColumnOrdinalPosition = (int)(long)row["ORDINAL_POSITION"];
GetColumnDefaultValue(row, col);
tableSchema.ColumnSchema.Add(col);
}
}
}
I have several strongly typed datasets throughout my application. Writing methods to update the data is getting tedious as each has several tables. I want to create one generic function that I can update all of the tables easily. I don't mind if I have to create one of these for each DataSet but if one function could handle all of them, that would be amazing!
There will be any number of new, updated, or deleted records and each row should be flagged properly. This function should just be handling the actual saving. Here is what I have so far:
private bool SaveData(object oTableAdaptor, object ds)
{
try
{
Type oType = oTableAdaptor.GetType();
MethodInfo[] oMethodInfoArray = oType.GetMethods();
foreach (MethodInfo oMI in oMethodInfoArray)
{
if (oMI.Name == "Update")
{
ParameterInfo[] oParamaterInfoArray = oMI.GetParameters();
foreach (ParameterInfo oPI in oParamaterInfoArray)
{
Type DsType = null;
if (oPI.ParameterType.Name == "NameOfDataSet")
{
DsType = typeof(MyDataSet);
// get a list of the changed tables???
}
if (((DataSet)ds).HasChanges() == true)
{
if (oPI.ParameterType == DsType)
{
object[] values = { ds };
try
{
oMI.Invoke(oTableAdaptor, values);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(oTableAdaptor.GetType().Name + Environment.NewLine + ex.Message);
}
}
}
}
}
}
}
catch (Exception Exp)
{
System.Diagnostics.Debug.WriteLine(Exp.Message);
if (Exp.InnerException != null) System.Diagnostics.Debug.WriteLine(Exp.InnerException.Message);
return false;
}
return true;
I have adapted this from another bit of code another developer has in a different application. The main difference thus far is he is passing in an array (of type object) of dataadaptors and has each of the three DataSets (globally instantiated) set up as individual if blocks inside the foreach (ParameterInfo oPI in oParamaterInfoArray) block (where my 'NameOfDataSet' would be one of the datasets)
Can anybody give me a little push (or a shove?) in the direction of finishing this function up? I know I am right there but it feels like I am over looking something. This code does compile without error.
I've been using this. It would need some optimizations though. This also takes care of updating the tables in correct order depending on the relations in dataset (in case there are no self-references, which can be handled by sorting the rows, but for simplicity I'm not posting it here).
public static void Save(DataSet data, SqlConnection connection)
{
/// Dictionary for associating adapters to tables.
Dictionary<DataTable, SqlDataAdapter> adapters = new Dictionary<DataTable, SqlDataAdapter>();
foreach (DataTable table in data.Tables)
{
/// Find the table adapter using Reflection.
Type adapterType = GetTableAdapterType(table);
SqlDataAdapter adapter = SetupTableAdapter(adapterType, connection, validityEnd);
adapters.Add(table, adapter);
}
/// Save the data.
Save(data, adapters);
}
static Type GetTableAdapterType(DataTable table)
{
/// Find the adapter type for the table using the namespace conventions generated by dataset code generator.
string nameSpace = table.GetType().Namespace;
string adapterTypeName = nameSpace + "." + table.DataSet.DataSetName + "TableAdapters." + table.TableName + "TableAdapter";
Type adapterType = Type.GetType(adapterTypeName);
return adapterType;
}
static SqlDataAdapter SetupTableAdapter(Type adapterType, SqlConnection connection)
{
/// Set connection to TableAdapter and extract SqlDataAdapter (which is private anyway).
object adapterObj = Activator.CreateInstance(adapterType);
SqlDataAdapter sqlAdapter = (SqlDataAdapter)GetPropertyValue(adapterType, adapterObj, "Adapter");
SetPropertyValue(adapterType, adapterObj, "Connection", connection);
return sqlAdapter;
}
static object GetPropertyValue(Type type, object instance, string propertyName)
{
return type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance).GetValue(instance, null);
}
static void SetPropertyValue(Type type, object instance, string propertyName, object propertyValue)
{
type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance).SetValue(instance, propertyValue, null);
}
static void Save(DataSet data, Dictionary<DataTable, SqlDataAdapter> adapters)
{
if (data == null)
throw new ArgumentNullException("data");
if (adapters == null)
throw new ArgumentNullException("adapters");
Dictionary<DataTable, bool> procesedTables = new Dictionary<DataTable, bool>();
List<DataTable> sortedTables = new List<DataTable>();
while (true)
{
DataTable rootTable = GetRootTable(data, procesedTables);
if (rootTable == null)
break;
sortedTables.Add(rootTable);
}
/// Updating Deleted rows in Child -> Parent order.
for (int i = sortedTables.Count - 1; i >= 0; i--)
{
Update(adapters, sortedTables[i], DataViewRowState.Deleted);
}
/// Updating Added / Modified rows in Parent -> Child order.
for (int i = 0; i < sortedTables.Count; i++)
{
Update(adapters, sortedTables[i], DataViewRowState.Added | DataViewRowState.ModifiedCurrent);
}
}
static void Update(Dictionary<DataTable, SqlDataAdapter> adapters, DataTable table, DataViewRowState states)
{
SqlDataAdapter adapter = null;
if (adapters.ContainsKey(table))
adapter = adapters[table];
if (adapter != null)
{
DataRow[] rowsToUpdate = table.Select("", "", states);
if (rowsToUpdate.Length > 0)
adapter.Update(rowsToUpdate);
}
}
static DataTable GetRootTable(DataSet data, Dictionary<DataTable, bool> procesedTables)
{
foreach (DataTable table in data.Tables)
{
if (!procesedTables.ContainsKey(table))
{
if (IsRootTable(table, procesedTables))
{
procesedTables.Add(table, false);
return table;
}
}
}
return null;
}
static bool IsRootTable(DataTable table, Dictionary<DataTable, bool> procesedTables)
{
foreach (DataRelation relation in table.ParentRelations)
{
DataTable parentTable = relation.ParentTable;
if (parentTable != table && !procesedTables.ContainsKey(parentTable))
return false;
}
return true;
}
Can't you just treat them as their base classes, DbDataAdapter, DataSet and DataTable?
You can access the table by name by doing DataSet.Tables["name"]. This returns a DataTable object that you can pass to the DbDataAdapters update method.
Or if your TableAdapter updates all the tables in your DataSet then you can pass the entire DataSet to the update method directly.
With that said I would suggest you rethink the use of typed data sets if you have the chance to do so. In my experience they end up being a hassle to maintain and use and have found the general DataTable, DataSet and DbDataAdapter classes to be much easier to use directly.
Do you really want reflection to be used that much in your DAL? Perhaps an ORM such as LINQ to SQL or NHibernate would be a good alternative?