I have an object class which I cobvert into a List. I would like to convert the class into a list, so I can do a SQLBulkCopy, as my current method takes long to write data intop SQL database table.
class ExtractedInfo
{
public string Date { get; set; }
public string Client { get; set; }
public string Path { get; set; }
}
List<ExtractedInfo> extractedList = new List<ExtractedInfo>();
try
{
Console.WriteLine("Writing to DB");
using (SqlConnection conn = new SqlConnection(connectionStringPMT))
{
conn.Open();
SqlCommand cmd =
new SqlCommand(
"IF NOT EXISTS (SELECT 1 FROM [FileTrckT] WHERE Path = #Path) " +
"INSERT INTO [FileTrckT] (Date, Client, Path, DateAddedToDb, FileName) " + // dont forget to add "DateAddedToDb" on live code
"VALUES (#Date, #Client, #Path, #DateAddedToDb, #FileName)");// dont forget to add " #DateAddedToDb" on live code
cmd.CommandType = CommandType.Text;
cmd.Connection = conn;
cmd.Parameters.Add("#Date", DbType.DateTime);
cmd.Parameters.Add("#Client", DbType.String);
cmd.Parameters.Add("#Path", DbType.String);
cmd.Parameters.Add(#"DateAddedToDb",DbType.DateTime); //uncomment on live code
cmd.Parameters.Add("#FileName", DbType.String);
foreach (var extractedRecord in extractedList)
{
cmd.Parameters[0].Value = extractedRecord.Date;
cmd.Parameters[1].Value = extractedRecord.Client;
cmd.Parameters[2].Value = extractedRecord.Path;
cmd.Parameters[3].Value = DateTime.Now; //uncomment on live code
cmd.Parameters[4].Value = extractedRecord.Path.Substring(extractedRecord.Path.LastIndexOf("/")+1);
cmd.ExecuteNonQuery();
}
conn.Close();
}
} catch(Exception ex)
{
Console.WriteLine(ex.Message.ToString());
Console.WriteLine("Error occured whilst inserting data into sql table FiletrckT");
log.Info("Error occured whilst inserting data into sql table FiletrckT");
log.Error(DateTime.Now + ": " + ex.Message.ToString());
}
}
catch(Exception ex)
{
log.Error(DateTime.Now.ToString() + " " + ex.Message.ToString());
Console.WriteLine(ex.Message);
}
}
A better option is to use FastMember's ObjectReader to create an IDataReader on top of the collection or IEnumerable. This way you avoid doubling memory usage by copying everything into a DataTable.
From the repo's samples:
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(myList, "Id", "Name", "Description"))
{
bcp.DestinationTableName = "SomeTable";
bcp.WriteToServer(reader);
}
You don't need to pass a field list if you want to export all fields.
The library is available through NuGet
If you want to create a DataTable for other reasons, you can use MoreLINQ's ToDataTable. It works the same as ToList or ToArray but generates a DataTable. The full MoreLINQ library is available through NuGet. Individual extensions are available as code packages, eg ToDataTable's source is available here
UPDATE
The ExtractedInfo class doesn't seem to match the fields of the FileTrckT table. This can be fixed by adding a Select call that converts the ExtractedInfo objects to the form expected by the table, eg:
var itemsToAdd= from it in extractedList
select new { Date= DateTime.Parse(it.Date), //Or ParseExact
it.Client,
it.Path,
DateAddedToDb = DateTime.Now,
FileName = it.Path.Substring(it.Path.LastIndexOf("/")+1)
};
var fields=new []{"Date", "Client", "Path","DateAddedToDb","FileName"};
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(itemsToAdd, fields))
{
bcp.DestinationTableName = "FileTrckT";
bcp.WriteToServer(reader);
}
You can write your own generic conversion method, like this for example:
private DataTable ConvertToDatatable<T>(IList<T> data)
{
var props = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in props)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in props)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
or an extension method:
static class MyExtenstions
{
public static DataTable ToDataTable<T>(this IList<T> data)
{
var props = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in props)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in props)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
}
Related
I'm trying to insert some values into the database using reflection. Here is my code, query works well, but how to pass values? I don't know what went wrong:
public class MyORM<T> where T : IData
{
public void Insert(T item)
{
var sql = new StringBuilder("Insert into ");
var type = item.GetType();
var properties = type.GetProperties();
sql.Append(type.Name);
sql.Append(" (");
foreach (var property in properties)
{
sql.Append(property.Name);
sql.Append(", ");
}
sql.Remove(sql.Length - 1, 1);
sql.Append(") values (");
foreach (var property in properties)
{
sql.Append('#').Append(property.Name).Append(',');
}
sql.Remove(sql.Length - 1, 1);
sql.Append(");");
var query = sql.ToString();
var command = new SqlCommand(query, _sqlConnection);
foreach (var property in properties)
{
command.Parameters.Add(property.Name);
}
}
}
command.Parameters.AddWithValue(property.Name, property.GetValue(item));
This line will solve your problem and will be able to pass the value.
public void DBInsertNewRecordIntoTable(DatabaseTableObject databaseTableObject, string tableName)
{
string connectMe = "Server=localhost;User ID=root;Database=test";
string sqlCommand = string.Concat("SELECT * FROM ", tableName, " LIMIT 0;");
//1. Get an instance of MySqlAdapter
MySqlDataAdapter mySqlDataAdapter = new MySqlDataAdapter(sqlCommand, connectMe);
//2. Retrieve schema from tableName and store it in DataSet
DataSet dataSet = new DataSet(string.Concat(tableName, "DataSet"));
mySqlDataAdapter.FillSchema(dataSet, SchemaType.Source, tableName);
//5. Get dataTable from dataSet
DataTable dataTable = dataSet.Tables[tableName];
//6. Add new row data
DataRow dataRow = dataTable.NewRow();
//6.1 Get a list of the properties in the databaseTableObject and store it into an array
PropertyInfo[] properties = databaseTableObject.GetType().GetProperties();
//6.2 Loop through all properties in databaseTableObject and assign their values to dataRow accordingly
foreach (var property in properties)
{ //6.3 Getting property value
var propertyValue = databaseTableObject.GetType().GetProperty(property.Name).GetValue(databaseTableObject, null);
//6.4 Only assign value to dataRow if databaseTableObject's property's value is not null
if (propertyValue != null)
dataRow[property.Name] = propertyValue;
}
//7. Add dataRow data to local table
dataTable.Rows.Add(dataRow);
//8. Apply the change to remote table
_ = new MySqlCommandBuilder(mySqlDataAdapter);
mySqlDataAdapter.Update(dataSet, tableName);
Console.WriteLine("Successfully updated the remote table");
}
interface DatabaseTableObject { }
public class DatabaseTableObjectEmployee: DatabaseTableObject
{
private string name;
private int? age = null;
private int? salary = null;
public int? Age { get => age; set => age = value; }
public int? Salary { get => salary; set => salary = value; }
public string Name { get => name; set => name = value; }
}
You can add property.GetValue(entity) in your iteration to get value and store it in a dictionary to pass it and use it as a parameter.Here my code.I have implemented it.Hope this will help.
public void Insert(TEntity entity)
{
if (entity == null) return;
Type type = entity.GetType();
PropertyInfo[] propertyInfos = type.GetProperties(); `
string s1 = "", s2 = "";
bool flag = false;
`Dictionary<string, object> dic = new Dictionary<string, object>();`
foreach (var property in propertyInfos)
{
Type type1 = property .GetType();
if (!flag)
flag = true;
else {
s1 += ",";
s2 += ",";
}
s1 += property .Name;
s2 += "#" + property .Name;
dic.Add(property .Name, property.GetValue(entity));//Here getting value
} `
`string sql = "Insert into " + type.Name + " (" + s1 + ") Values (" + s2 + ");";`
` ExecuteCommand(sql, dic);`
}```
//`Database Execution portion`
`public void ExecuteCommand(string command, Dictionary<string, object> parameters)`
{
using(SqlConnection connection = new SqlConnection(_conncectionstring))
{
using(SqlCommand sqlcommand = new SqlCommand(command, connection))
{
try
{
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
if (parameters != null)
{
foreach (var item in parameters)
{
sqlcommand.Parameters.Add(new SqlParameter(item.Key, item.Value));
}
}
sqlcommand.ExecuteNonQuery();
}
catch (Exception ex) {
}
}
}
}`
Hello everyone I'm trying to write code that can get select table information with (string)tableName, but i get error when i try to put value to Dictionary.
P.S : I have generated EF DB model.
public Dictionary<string, List<object>> GetTableInformation(string tableName, FinkonaDatabaseType type)
{
Dictionary<string, List<object>> _returnableDictionary = new Dictionary<string, List<object>>();
PropertyInfo prop = optimumEntities.GetType().GetProperty(tableName);
Type tableType = prop.PropertyType.GenericTypeArguments[0];
var items = optimumEntities.Database.SqlQuery(tableType, "SELECT * FROM " + tableName);
foreach (var item in items)
{
foreach (PropertyInfo info in item.GetType().GetProperties())
{
if (!_returnableDictionary.ContainsKey(info.Name))
{
_returnableDictionary.Add(info.Name, new List<object>());
}
_returnableDictionary[info.Name].Add(info.GetValue(info, null));
// System.Reflection.TargetException, Object does not match target type.
}
}
return _returnableDictionary;
}
Using ADO.NET DataTables will be easier here, as EF is used for strongly typing data. Since you are not too worried about the data types coming back, a DataTable will be easier to navigate.
Here is an examples of this:
public Dictionary<string, List<object>> GetTableInformation(string tableName, FinkonaDatabaseType type)
{
var sqlText = "SELECT * from " + tableName;
DataTable dt = new DataTable();
// Use DataTables to extract the whole table in one hit
using(SqlDataAdapter da = new SqlDataAdapter(sqlText, optimumEntities.Database.ConnectionString)
{
da.Fill(dt);
}
var tableData = new Dictionary<string, List<object>>();
// Go through all columns, retrieving their names and populating the rows
foreach(DataColumn dc in dt.Columns)
{
string columnName = dc.Name;
rowData = new List<object>();
tableData.Add(columnName, rowData);
foreach(DataRow dr in dt.Rows)
{
rowData.Add(dr[columnName]);
}
}
return tableData;
}
I have following Code to export data From CSV to Datatable
string cnstr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=CSVFilePath;Extended Properties=\"text;HDR=Yes;FMT=Delimited\";";
string sql = "SELECT A,B,C,D FROM Csvfile.csv";
using (OleDbDataAdapter adp = new OleDbDataAdapter(sql, cnstr))
{
DataTable dt = new DataTable();
adp.Fill(dt);
}
But Getting Error as The value of the parameters that are required of one or more has not been set
Query looks Ok for me. As all columns are available in CSV file. Even I tried
string sql = "SELECT [A],[B],[C],[D] FROM Csvfile.csv";
But no Luck.
How to overcome this problem.
Allow me to recommend this as an alternative for reading CSV files
http://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader
If you define a model class to match what data types you want for your DataTable columns you can use something like this:
public static DataTable CSVToDataTable(string pathToCsv, bool csvHasHeader, Type csvSchema)
{
DataTable dt = new DataTable();
var props = csvSchema.GetProperties();
foreach (PropertyInfo prop in props)
{
dt.Columns.Add(prop.Name, prop.PropertyType);
}
foreach (var line in System.IO.File.ReadAllLines(pathToCsv).Skip(csvHasHeader ? 1 : 0))
{
dt.Rows.Add(CSVLinetoDataRow(line, dt));
}
return dt;
}
private static object[] CSVLinetoDataRow(string csvLine, DataTable dt)
{
//remove commas within quotation marks
var regex = new Regex("\\\"(.*?)\\\"");
string[] values = regex.Replace(csvLine, m => m.Value.Replace(',', ':')).Split(',');
object[] arr = new object[values.Length];
for (int i = 0; i < values.Length; i++)
{
var converter = TypeDescriptor.GetConverter(dt.Columns[i].DataType);
if (values[i] != "\"\"")
{
arr[i] = converter.ConvertFrom(values[i].Replace("\"", ""));
}
else
{
arr[i] = null;
}
}
return arr;
}
For inserting a huge amount of data in a database, I used to collect all the inserting information into a list and convert this list into a DataTable. I then insert that list to a database via SqlBulkCopy.
Where I send my generated list LiMyList which contain information of all bulk data which I want to insert to database and pass it to my bulk insertion operation
InsertData(LiMyList, "MyTable");
Where InsertData is
public static void InsertData<T>(List<T> list,string TableName)
{
DataTable dt = new DataTable("MyTable");
clsBulkOperation blk = new clsBulkOperation();
dt = ConvertToDataTable(list);
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(ConfigurationManager.ConnectionStrings["SchoolSoulDataEntitiesForReport"].ConnectionString))
{
bulkcopy.BulkCopyTimeout = 660;
bulkcopy.DestinationTableName = TableName;
bulkcopy.WriteToServer(dt);
}
}
public static DataTable ConvertToDataTable<T>(IList<T> data)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
Now I want to do an update operation, is there any way as for inserting data is done by SqlBulkCopy for Updating data to DataBase From C#.Net
What I've done before is perform a bulk insert from the data into a temp table, and then use a command or stored procedure to update the data relating the temp table with the destination table. The temp table is an extra step, but you can have a performance gain with the bulk insert and massive update if the amount of rows is big, compared to updating the data row by row.
Example:
public static void UpdateData<T>(List<T> list,string TableName)
{
DataTable dt = new DataTable("MyTable");
dt = ConvertToDataTable(list);
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SchoolSoulDataEntitiesForReport"].ConnectionString))
{
using (SqlCommand command = new SqlCommand("", conn))
{
try
{
conn.Open();
//Creating temp table on database
command.CommandText = "CREATE TABLE #TmpTable(...)";
command.ExecuteNonQuery();
//Bulk insert into temp table
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(conn))
{
bulkcopy.BulkCopyTimeout = 660;
bulkcopy.DestinationTableName = "#TmpTable";
bulkcopy.WriteToServer(dt);
bulkcopy.Close();
}
// Updating destination table, and dropping temp table
command.CommandTimeout = 300;
command.CommandText = "UPDATE T SET ... FROM " + TableName + " T INNER JOIN #TmpTable Temp ON ...; DROP TABLE #TmpTable;";
command.ExecuteNonQuery();
}
catch (Exception ex)
{
// Handle exception properly
}
finally
{
conn.Close();
}
}
}
}
Notice that a single connection is used to perform the whole operation, in order to be able to use the temp table in each step, because the scope of the temp table is per connection.
In my personal experience, the best way to handled this situation is utilizing a Stored Procedure with a Table-Valued Parameter and a User-Defined Table Type. Just set up the type with the columns of the data table, and pass in said-data table as a parameter in the SQL command.
Within the Stored Procedure, you can either join directly on some unique key (if all rows you are updating exist), or - if you might run into a situation where you are having to do both updates and inserts - use the SQL Merge command within the stored procedure to handle both the updates and inserts as applicable.
Microsoft has both syntax reference and an article with examples for the Merge.
For the .NET piece, it's a simple matter of setting the parameter type as SqlDbType.Structured and setting the value of said-parameter to the Data Table that contains the records you want to update.
This method provides the benefit of both clarity and ease of maintenance. While there may be ways that offer performance improvements (such as dropping it into a temporary table then iterating over that table), I think they're outweighed by the simplicity of letting .NET and SQL handle transferring the table and updating the records itself. K.I.S.S.
Bulk Update:
Step 1: put the data which you want to update and primary key in a list.
Step 2: pass this list and ConnectionString to BulkUpdate Method As shown below
Example:
//Method for Bulk Update the Data
public static void BulkUpdateData<T>(List<T> list, string connetionString)
{
DataTable dt = new DataTable("MyTable");
dt = ConvertToDataTable(list);
using (SqlConnection conn = new SqlConnection(connetionString))
{
using (SqlCommand command = new SqlCommand("CREATE TABLE
#TmpTable([PrimaryKey],[ColumnToUpdate])", conn))
{
try
{
conn.Open();
command.ExecuteNonQuery();
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(conn))
{
bulkcopy.BulkCopyTimeout = 6600;
bulkcopy.DestinationTableName = "#TmpTable";
bulkcopy.WriteToServer(dt);
bulkcopy.Close();
}
command.CommandTimeout = 3000;
command.CommandText = "UPDATE P SET P.[ColumnToUpdate]= T.[ColumnToUpdate] FROM [TableName Where you want to update ] AS P INNER JOIN #TmpTable AS T ON P.[PrimaryKey] = T.[PrimaryKey] ;DROP TABLE #TmpTable;";
command.ExecuteNonQuery();
}
catch (Exception ex)
{
// Handle exception properly
}
finally
{
conn.Close();
}
}
}
}
Step 3: put The ConvertToDataTable Method as shown Below.
Example:
public static DataTable ConvertToDataTable<T>(IList<T> data)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
Notes: WhereEver SquareBracket[] is there, put your own value.
Try out SqlBulkTools available on Nuget.
Disclaimer: I'm the author of this library.
var bulk = new BulkOperations();
var records = GetRecordsToUpdate();
using (TransactionScope trans = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager
.ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
{
bulk.Setup<MyTable>()
.ForCollection(records)
.WithTable("MyTable")
.AddColumn(x => x.SomeColumn1)
.AddColumn(x => x.SomeColumn2)
.BulkUpdate()
.MatchTargetOn(x => x.Identifier)
.Commit(conn);
}
trans.Complete();
}
Only 'SomeColumn1' and 'SomeColumn2' will be updated. More examples can be found here
I would insert new values in a temporary table and then do a merge against the destination table, something like this:
MERGE [DestTable] AS D
USING #SourceTable S
ON D.ID = S.ID
WHEN MATCHED THEN
UPDATE SET ...
WHEN NOT MATCHED
THEN INSERT (...)
VALUES (...);
You could try to build a query that contains all data. Use a case. It could look like this
update your_table
set some_column = case when id = 1 then 'value of 1'
when id = 5 then 'value of 5'
when id = 7 then 'value of 7'
when id = 9 then 'value of 9'
end
where id in (1,5,7,9)
I'd go for a TempTable approach because that way you aren't locking anything. But if your logic needs to be only in the front end and you need to use bulk copy, I'd try a Delete/Insert approach but in the same SqlTransaction to ensure integrity which would be something like this:
// ...
dt = ConvertToDataTable(list);
using (SqlConnection cnx = new SqlConnection(myConnectionString))
{
using (SqlTranscation tran = cnx.BeginTransaction())
{
DeleteData(cnx, tran, list);
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(cnx, SqlBulkCopyOptions.Default, tran))
{
bulkcopy.BulkCopyTimeout = 660;
bulkcopy.DestinationTableName = TabelName;
bulkcopy.WriteToServer(dt);
}
tran.Commit();
}
}
Complete answer, disclaimer: arrow code; this is mine built from research; Published in SqlRapper. It uses custom attributes over properties to determine whether a key is primary. Yes, super complicated. Yes super reusable. Yes, needs to be refactored. Yes, it is a nuget package. No, the documentation isn't great on github, but it exists. Will it work for everything? Probably not. Will it work for simple stuff? Oh yeah.
How easy is it to use after setup?
public class Log
{
[PrimaryKey]
public int? LogId { get; set; }
public int ApplicationId { get; set; }
[DefaultKey]
public DateTime? Date { get; set; }
public string Message { get; set; }
}
var logs = new List<Log>() { log1, log2 };
success = db.BulkUpdateData(logs);
Here's how it works:
public class PrimaryKeyAttribute : Attribute
{
}
private static bool IsPrimaryKey(object[] attributes)
{
bool skip = false;
foreach (var attr in attributes)
{
if (attr.GetType() == typeof(PrimaryKeyAttribute))
{
skip = true;
}
}
return skip;
}
private string GetSqlDataType(Type type, bool isPrimary = false)
{
var sqlType = new StringBuilder();
var isNullable = false;
if (Nullable.GetUnderlyingType(type) != null)
{
isNullable = true;
type = Nullable.GetUnderlyingType(type);
}
switch (Type.GetTypeCode(type))
{
case TypeCode.String:
isNullable = true;
sqlType.Append("nvarchar(MAX)");
break;
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Int16:
sqlType.Append("int");
break;
case TypeCode.Boolean:
sqlType.Append("bit");
break;
case TypeCode.DateTime:
sqlType.Append("datetime");
break;
case TypeCode.Decimal:
case TypeCode.Double:
sqlType.Append("decimal");
break;
}
if (!isNullable || isPrimary)
{
sqlType.Append(" NOT NULL");
}
return sqlType.ToString();
}
/// <summary>
/// SqlBulkCopy is allegedly protected from Sql Injection.
/// Updates a list of simple sql objects that mock tables.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="rows">A list of rows to insert</param>
/// <param name="tableName">a Table name if your class isn't your table name minus s.</param>
/// <returns>bool success</returns>
public bool BulkUpdateData<T>(List<T> rows, string tableName = null)
{
var template = rows.FirstOrDefault();
string tn = tableName ?? template.GetType().Name + "s";
int updated = 0;
using (SqlConnection con = new SqlConnection(ConnectionString))
{
using (SqlCommand command = new SqlCommand("", con))
{
using (SqlBulkCopy sbc = new SqlBulkCopy(con))
{
var dt = new DataTable();
var columns = template.GetType().GetProperties();;
var colNames = new List<string>();
string keyName = "";
var setStatement = new StringBuilder();
int rowNum = 0;
foreach (var row in rows)
{
dt.Rows.Add();
int colNum = 0;
foreach (var col in columns)
{
var attributes = row.GetType().GetProperty(col.Name).GetCustomAttributes(false);
bool isPrimary = IsPrimaryKey(attributes);
var value = row.GetType().GetProperty(col.Name).GetValue(row);
if (rowNum == 0)
{
colNames.Add($"{col.Name} {GetSqlDataType(col.PropertyType, isPrimary)}");
dt.Columns.Add(new DataColumn(col.Name, Nullable.GetUnderlyingType(col.PropertyType) ?? col.PropertyType));
if (!isPrimary)
{
setStatement.Append($" ME.{col.Name} = T.{col.Name},");
}
}
if (isPrimary)
{
keyName = col.Name;
if (value == null)
{
throw new Exception("Trying to update a row whose primary key is null; use insert instead.");
}
}
dt.Rows[rowNum][colNum] = value ?? DBNull.Value;
colNum++;
}
rowNum++;
}
setStatement.Length--;
try
{
con.Open();
command.CommandText = $"CREATE TABLE [dbo].[#TmpTable]({String.Join(",", colNames)})";
//command.CommandTimeout = CmdTimeOut;
command.ExecuteNonQuery();
sbc.DestinationTableName = "[dbo].[#TmpTable]";
sbc.BulkCopyTimeout = CmdTimeOut * 3;
sbc.WriteToServer(dt);
sbc.Close();
command.CommandTimeout = CmdTimeOut * 3;
command.CommandText = $"UPDATE ME SET {setStatement} FROM {tn} as ME INNER JOIN #TmpTable AS T on ME.{keyName} = T.{keyName}; DROP TABLE #TmpTable;";
updated = command.ExecuteNonQuery();
}
catch (Exception ex)
{
if (con.State != ConnectionState.Closed)
{
sbc.Close();
con.Close();
}
//well logging to sql might not work... we could try... but no.
//So Lets write to a local file.
_logger.Log($"Failed to Bulk Update to Sql: {rows.ToCSV()}", ex);
throw ex;
}
}
}
}
return (updated > 0) ? true : false;
}
I have been struggling to get the right c# code for getting the values after a PRAGMA table_info query.
Since my edit with extra code was rejected in this post, I made this question for other people that would otherwise waste hours for a fast solution.
Assuming you want a DataTable with the list of field of your table:
using (var con = new SQLiteConnection(preparedConnectionString))
{
using (var cmd = new SQLiteCommand("PRAGMA table_info(" + tableName + ");"))
{
var table = new DataTable();
cmd.Connection = con;
cmd.Connection.Open();
SQLiteDataAdapter adp = null;
try
{
adp = new SQLiteDataAdapter(cmd);
adp.Fill(table);
con.Close();
return table;
}
catch (Exception ex)
{ }
}
}
Return result is:
cid: id of the column
name: the name of the column
type: the type of the column
notnull: 0 or 1 if the column can contains null values
dflt_value: the default value
pk: 0 or 1 if the column partecipate to the primary key
If you want only the column names into a List you can use (you have to include System.Data.DataSetExtension):
return table.AsEnumerable().Select(r=>r["name"].ToString()).ToList();
EDIT: Or you can avoid the DataSetExtension reference using this code:
using (var con = new SQLiteConnection(preparedConnectionString))
{
using (var cmd = new SQLiteCommand("PRAGMA table_info(" + tableName + ");"))
{
var table = new DataTable();
cmd.Connection = con;
cmd.Connection.Open();
SQLiteDataAdapter adp = null;
try
{
adp = new SQLiteDataAdapter(cmd);
adp.Fill(table);
con.Close();
var res = new List<string>();
for(int i = 0;i<table.Rows.Count;i++)
res.Add(table.Rows[i]["name"].ToString());
return res;
}
catch (Exception ex){ }
}
}
return new List<string>();
There are a lot of PRAGMA statements that you can use in SQLite, have a look at the link.
About the using statement: it's very simple, it is used to be sure that disposable objects will be disposed whatever can happen in your code: see this link or this reference
Code:
DB = new SQLiteConnection(#"Data Source="+DBFileName);
DB.Open();
SQLiteCommand command = new SQLiteCommand("PRAGMA table_info('tracks')", DB);
DataTable dataTable = new DataTable();
SQLiteDataAdapter dataAdapter = new SQLiteDataAdapter(command);
dataAdapter.Fill(dataTable);
DB.Close();
foreach (DataRow row in dataTable.Rows) {
DBColumnNames.Add((string)row[dataTable.Columns[1]]); }
//Out(String.Join(",",
DBColumnNames.ToArray()));//debug
All elements in the resulted rows:
int cid, string name, string type,int notnull, string dflt_value, int pk
More info on PRAGMA
Not sure if this exactly what you are after but this is how I have grabbed the data and subsequently used it. Hope it helps!
Obviously the switch is not covering all eventualities, just those I have needed to so far.
/// <summary>
/// Allows the programmer to easily update rows in the DB.
/// </summary>
/// <param name="tableName">The table to update.</param>
/// <param name="data">A dictionary containing Column names and their new values.</param>
/// <param name="where">The where clause for the update statement.</param>
/// <returns>A boolean true or false to signify success or failure.</returns>
public bool Update(String tableName, Dictionary<String, String> data, String where)
{
String vals = "";
Boolean returnCode = true;
//Need to determine the dataype of fields to update as this affects the way the sql needs to be formatted
String colQuery = "PRAGMA table_info(" + tableName + ")";
DataTable colDataTypes = GetDataTable(colQuery);
if (data.Count >= 1)
{
foreach (KeyValuePair<String, String> pair in data)
{
DataRow[] colDataTypeRow = colDataTypes.Select("name = '" + pair.Key.ToString() + "'");
String colDataType="";
if (pair.Key.ToString()== "rowid" || pair.Key.ToString()== "_rowid_" || pair.Key.ToString()=="oid")
{
colDataType = "INT";
}
else
{
colDataType = colDataTypeRow[0]["type"].ToString();
}
colDataType = colDataType.Split(' ').FirstOrDefault();
if ( colDataType == "VARCHAR")
{
colDataType = "VARCHAR";
}
switch(colDataType)
{
case "INTEGER": case "INT": case "NUMERIC": case "REAL":
vals += String.Format(" {0} = {1},", pair.Key.ToString(), pair.Value.ToString());
break;
case "TEXT": case "VARCHAR": case "DATE": case "DATETIME":
vals += String.Format(" {0} = '{1}',", pair.Key.ToString(), pair.Value.ToString());
break;
}
}
vals = vals.Substring(0, vals.Length - 1);
}
try
{
string sql = String.Format("update {0} set {1} where {2};", tableName, vals, where);
//dbl.AppendLine(sql);
dbl.AppendLine(sql);
this.ExecuteNonQuery(sql);
}
catch(Exception crap)
{
OutCrap(crap);
returnCode = false;
}
return returnCode;
}