How can I get the field names of an MS Access database table?
Is there an SQL query I can use, or is there C# code to do this?
Use IDataReader.GetSchemaTable()
Here's an actual example that accesses the table schema and prints it plain and in XML (just to see what information you get):
class AccessTableSchemaTest
{
public static DbConnection GetConnection()
{
return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=..\\Test.mdb");
}
static void Main(string[] args)
{
using (DbConnection conn = GetConnection())
{
conn.Open();
DbCommand command = conn.CreateCommand();
// (1) we're not interested in any data
command.CommandText = "select * from Test where 1 = 0";
command.CommandType = CommandType.Text;
DbDataReader reader = command.ExecuteReader();
// (2) get the schema of the result set
DataTable schemaTable = reader.GetSchemaTable();
conn.Close();
}
PrintSchemaPlain(schemaTable);
Console.WriteLine(new string('-', 80));
PrintSchemaAsXml(schemaTable);
Console.Read();
}
private static void PrintSchemaPlain(DataTable schemaTable)
{
foreach (DataRow row in schemaTable.Rows)
{
Console.WriteLine("{0}, {1}, {2}",
row.Field<string>("ColumnName"),
row.Field<Type>("DataType"),
row.Field<int>("ColumnSize"));
}
}
private static void PrintSchemaAsXml(DataTable schemaTable)
{
StringWriter stringWriter = new StringWriter();
schemaTable.WriteXml(stringWriter);
Console.WriteLine(stringWriter.ToString());
}
}
Points of interest:
Don't return any data by giving a where clause that always evaluates to false. Of course this only applies if you're not interested in the data :-).
Use IDataReader.GetSchemaTable() to get a DataTable with detailed info about the actual table.
For my test table the output was:
ID, System.Int32, 4
Field1, System.String, 50
Field2, System.Int32, 4
Field3, System.DateTime, 8
--------------------------------------------------------------------------------
<DocumentElement>
<SchemaTable>
<ColumnName>ID</ColumnName>
<ColumnOrdinal>0</ColumnOrdinal>
<ColumnSize>4</ColumnSize>
<NumericPrecision>10</NumericPrecision>
<NumericScale>255</NumericScale>
<DataType>System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</DataType>
<ProviderType>3</ProviderType>
<IsLong>false</IsLong>
<AllowDBNull>true</AllowDBNull>
<IsReadOnly>false</IsReadOnly>
<IsRowVersion>false</IsRowVersion>
<IsUnique>false</IsUnique>
<IsKey>false</IsKey>
<IsAutoIncrement>false</IsAutoIncrement>
</SchemaTable>
[...]
</DocumentElement>
this will work on sql server 2005 and up:
select * from INFORMATION_SCHEMA.COLUMNS
where TABLE_Name='YourTableName'
order by ORDINAL_POSITION
Run this query:
select top 1 *
From foo
and then walk the list fields (and returned values) in the result set to get the field names.
Are you asking how you can get the column names of a table in a Database?
If so it completely depends on the Database Server you are using.
In SQL 2005 you can select from the INFORMATION_SCHEMA.Columns View
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'MyTable'
IN SQL 2000 you can join SysObjects to SysColumns to get the info
SELECT
dbo.sysobjects.name As TableName
, dbo.syscolumns.name AS FieldName
FROM
dbo.sysobjects
INNER JOIN dbo.syscolumns
ON dbo.sysobjects.id = dbo.syscolumns.id
WHERE
dbo.sysobjects.name = 'MyTable'
Use the DAO automation classes. You may already have an interop library for it in your Visual Studio installation. If not, it's easy enough to create one; just add a reference to the DAO COM library.
using dao;
...
DBEngineClass dbengine = new DBEngineClass();
dbengine.OpenDatabase(path, null, null, null);
Database database = dbengine.Workspaces[0].Databases[0];
List<string> fieldnames = new List<string>();
TableDef tdf = database.TableDefs[tableName];
for (int i = 0; i < tdf.Fields.Count; i++)
{
fieldnames.Add(tdf.Fields[i].Name);
}
database.Close();
dbengine.Workspaces[0].Close();
This is just as easy as querying a system table (which I've found to be problematic in Access), and you can get a lot of additional information this way.
EDIT:
I've modified the code from what I posted yesterday, which I had just translated from VB.NET, and which was missing a couple of pieces. I've rewritten it and tested it in C# in VS2008.
This Code will print all column name of a table as a class with getter property of all column names which can be then used in c# code
declare #TableName sysname = '<EnterTableName>'
declare #Result varchar(max) = 'public class ' + #TableName + '
{'
select #Result = #Result + '
public static string ' + ColumnName + ' { get { return "'+ColumnName+'"; } }
'
from
(
select
replace(col.name, ' ', '_') ColumnName,
column_id ColumnId
from sys.columns col
join sys.types typ on
col.system_type_id = typ.system_type_id AND col.user_type_id = typ.user_type_id
where object_id = object_id(#TableName)
) t
order by ColumnId
set #Result = #Result + '
}'
print #Result
Output:
public class tblPracticeTestSections
{
public static string column1 { get { return "column1"; } }
public static string column2{ get { return "column2"; } }
public static string column3{ get { return "column3"; } }
public static string column4{ get { return "column4"; } }
}
Depending on the DB engine your using you can easily query the DB system tables for that information
For access i can't find the answer i know you can see the sys tables in access and from there you could try and determine where that information is but im not really sure how to do this part. tried using an example but got nowwhere
for microsoft SQL in c# you can do the following:
Dictionary<string, int> map =
(from DataRow row in Schema.Rows
let columnName = (string)row["ColumnName"]
select columnName)
.Distinct(StringComparer.InvariantCulture)
.Select((columnName, index) => new { Key = columnName, Value = index })
.ToDictionary(pair => pair.Key, pair => pair.Value);
thus creates a map of column name into its index which can be used as follows:
internal sealed class ColumnToIndexMap
{
private const string NameOfColumn = "ColumnName";
private DataTable Schema { get; set; }
private Dictionary<string, int> Map { get; set; }
public ColumnToIndexMap(DataTable schema)
{
if (schema == null) throw new ArgumentNullException("schema");
Schema = schema;
Map = (from DataRow row in Schema.Rows
let columnName = (string)row[NameOfColumn]
select columnName)
.Distinct(StringComparer.InvariantCulture)
.Select((columnName, index) => new { Key = columnName, Value = index })
.ToDictionary(pair => pair.Key, pair => pair.Value);
}
int this[string name]
{
get { return Map[name]; }
}
string this[int index]
{
get { return Schema.Rows[index][NameOfColumn].ToString(); }
}
}
I have had good luck with the GetSchema property of the OleDb.Connection:
A class to provide column data. This returns ALL columns in the database. The resulting DataTable can then be filtered by column names which correspond (mostly) to those found in a standard INFORMATION_SCHEMA (which MS Access does NOT provide for us):
class JetMetaData
{
/// <summary>
/// Returns a datatable containing MetaData for all user-columns
/// in the current JET Database.
/// </summary>
/// <returns></returns>
public static DataTable AllColumns(String ConnectionString)
{
DataTable dt;
using (OleDbConnection cn = new OleDbConnection(ConnectionString))
{
cn.Open();
dt = cn.GetSchema("Columns");
cn.Close();
}
return dt;
}
}
Then, Consuming the class in a rather crude and not-so-elegant example, and filtering on TABLE_NAME:
private void Form1_Load(object sender, EventArgs e)
{
DataTable dt = JetMetaData.AllColumns("", Properties.Settings.Default.JetConnection);
String RowFilter = "TABLE_NAME = 'YourTableName'";
DataView drv = dt.DefaultView;
drv.RowFilter = RowFilter;
DataGridView dgv = this.dataGridView1;
dgv.DataSource = drv;
}
Note that I do not pretend that this is all well-though out code. It is only an example. But I have used something like this on a number of occasions, and in fact even created an application to script out an entire MS Access database (contraints and all) using similar methods.
While I have seen others in this thread mention the get Schema, it seem slike some of the implementation was overly complicated . . .
Hope that helps!
Related
Currently, I have a database called "testDB.db" that has 100 rows and 3 columns. Is there some library in C# that allows me to easily check how many columns and rows there are in table "test001" in "testDB.db"?
namespace SQLiteExtractor
{
class Program
{
static void Main()
{
string liteString = #"Data Source = .\testDB.db";
connectToSQLite(liteString);
}
public static void connectToSQLite(string liteConString)
{
using SQLiteConnection liteCon = new SQLiteConnection(liteConString);
liteCon.Open();
string query = "SELECT * FROM test001";
int sizeOfDR = 0;
List<string> liteEntries = new List<string>();
using var cmd = new SQLiteCommand(query, liteCon);
using SQLiteDataReader SQLiteDR = cmd.ExecuteReader();
while (SQLiteDR.Read())
{
liteEntries.Add(SQLiteDR.GetString(1));
}
foreach (string entry in liteEntries)
Console.WriteLine(entry);
}
}
}
For rows, you can do a SELECT COUNT() query against the table.
For cols, you can inspect the table metadata via system table queries. Use PRAGMA table_info(table_name); to get the col info.
Specific to your table:
SELECT COUNT(*) FROM test001
and
PRAGMA table_info('test001')
We have a big list around 100000 records and want to insert it into a sql table.
What are we doing is; converting that list into data table and passing datatable to SqlBulkcopy method.
This conversion from list to Datatable taking more time. Tried using Parallel but as Datatable is not thread safe so avoided that.
Adding sample poc code which generates integer list and insert it into temp table
static void Main(string[] args)
{
List<int> valueList = GenerateList(100000);
Console.WriteLine("Starting with Bulk Insert ");
DateTime startTime = DateTime.Now;
int recordCount = BulkInsert(valueList);
TimeSpan ts = DateTime.Now.Subtract(startTime);
Console.WriteLine("Bulk insert for {0} records in {1} miliseconds.-> ", recordCount, ts.Milliseconds);
Console.WriteLine("Done.");
Console.ReadLine();
}
private static int BulkInsert(List<int> valueList)
{
SqlBulkHelper sqlBulkHelper = new SqlBulkHelper();
var eventIdDataTable = CreateIdentityDataTable(valueList, "SqlTable", "Id");
return FillBulkPoundTable(eventIdDataTable, "#SqlTable");
}
private static List<int> GenerateList(int size)
{
return Enumerable.Range(0, size).ToList();
}
private static DataTable CreateIdentityDataTable(List<int> ids, string dataTableName, string propertyName)
{
if (ids == null) return null;
using (var dataTable = new DataTable(dataTableName))
{
dataTable.Locale = CultureInfo.CurrentCulture;
var dtColumn = new DataColumn(propertyName, Type.GetType("System.Int32"));
dataTable.Columns.Add(dtColumn);
foreach (int id in ids)
{
DataRow row = dataTable.NewRow();
row[propertyName] = id;
dataTable.Rows.Add(row);
}
return dataTable;
}
}
private static int FillBulkPoundTable(DataTable dataTable, string destinationTableName)
{
int totalInsertedRecordCount = 0;
using (SqlConnection _connection = new SqlConnection(CongifUtil.sqlConnString))
{
string sql =
#"If object_Id('tempdb..#EventIds') is not null drop table #EventIds
CREATE TABLE #EventIds(EvId int) ";
_connection.Open();
using (var command = new SqlCommand(sql, _connection))
{
command.ExecuteNonQuery();
}
using (var sqlBulkCopy = new SqlBulkCopy(_connection))
{
sqlBulkCopy.BulkCopyTimeout = 0;
sqlBulkCopy.DestinationTableName = destinationTableName;
sqlBulkCopy.WriteToServer(dataTable);
}
using (var command = new SqlCommand(sql, _connection))
{
command.CommandText = "Select Count(1) as RecordCount from #EventIds";
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
totalInsertedRecordCount = Convert.ToInt32(reader["RecordCount"]);
}
}
}
}
return totalInsertedRecordCount;
}
Currently it is taking around 8 seconds but we need to make it more faster. Reason is our target is to insert 900,000 records which will be devided into 100,000 batch each.
Can you give us any hint how can we make it perfect and faster?
PS. Tried with Dapper insert too but it is not faster than BulkCopy.
First Covert your list into XML something like
List<int> Branches = new List<int>();
Branches.Add(1);
Branches.Add(2);
Branches.Add(3);
XElement xmlElements = new XElement("Branches", Branches.Select(i => new
XElement("branch", i)));
Then pass the xml to a SP as parameter and insert it directly to your table, Example :
DECLARE #XML XML
SET #XML = '<Branches>
<branch>1</branch>
<branch>2</branch>
<branch>3</branch>
</Branches>'
DECLARE #handle INT
DECLARE #PrepareXmlStatus INT
EXEC #PrepareXmlStatus= sp_xml_preparedocument #handle OUTPUT, #XML
SELECT * FROM OPENXML(#handle, '/Branches/branch', 2)
WITH (
branch varchar
)
EXEC sp_xml_removedocument #handle
Bach Size
From what I understand, you try to insert with a BatchSize of 100000. Higher is not always better.
Try to lower this amount to 5,000 instead and check for the performance difference.
You increase the amount of database round-trip but it may also go faster (Too much factor such as the row size are involved here)
TableLock
Using the SqlBulkCopyOptions.TableLock will improve your insert performance.
using (var sqlBulkCopy = new SqlBulkCopy(_connection, SqlBulkCopyOptions.KeepIdentity))
I have a SQL Server 2005 database. In a few procedures I have table parameters that I pass to a stored proc as an nvarchar (separated by commas) and internally divide into single values. I add it to the SQL command parameters list like this:
cmd.Parameters.Add("#Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";
I have to migrate the database to SQL Server 2008. I know that there are table value parameters, and I know how to use them in stored procedures. But I don't know how to pass one to the parameters list in an SQL command.
Does anyone know correct syntax of the Parameters.Add procedure? Or is there another way to pass this parameter?
DataTable, DbDataReader, or IEnumerable<SqlDataRecord> objects can be used to populate a table-valued parameter per the MSDN article Table-Valued Parameters in SQL Server 2008 (ADO.NET).
The following example illustrates using either a DataTable or an IEnumerable<SqlDataRecord>:
SQL Code:
CREATE TABLE dbo.PageView
(
PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
#Display dbo.PageViewTableType READONLY
AS
BEGIN
MERGE INTO dbo.PageView AS T
USING #Display AS S
ON T.PageViewID = S.PageViewID
WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END
C# Code:
private static void ExecuteProcedure(bool useDataTable,
string connectionString,
IEnumerable<long> ids)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "dbo.procMergePageView";
command.CommandType = CommandType.StoredProcedure;
SqlParameter parameter;
if (useDataTable) {
parameter = command.Parameters
.AddWithValue("#Display", CreateDataTable(ids));
}
else
{
parameter = command.Parameters
.AddWithValue("#Display", CreateSqlDataRecords(ids));
}
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.PageViewTableType";
command.ExecuteNonQuery();
}
}
}
private static DataTable CreateDataTable(IEnumerable<long> ids)
{
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(long));
foreach (long id in ids)
{
table.Rows.Add(id);
}
return table;
}
private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids)
{
SqlMetaData[] metaData = new SqlMetaData[1];
metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
SqlDataRecord record = new SqlDataRecord(metaData);
foreach (long id in ids)
{
record.SetInt64(0, id);
yield return record;
}
}
Further to Ryan's answer you will also need to set the DataColumn's Ordinal property if you are dealing with a table-valued parameter with multiple columns whose ordinals are not in alphabetical order.
As an example, if you have the following table value that is used as a parameter in SQL:
CREATE TYPE NodeFilter AS TABLE (
ID int not null
Code nvarchar(10) not null,
);
You would need to order your columns as such in C#:
table.Columns["ID"].SetOrdinal(0);
// this also bumps Code to ordinal of 1
// if you have more than 2 cols then you would need to set more ordinals
If you fail to do this you will get a parse error, failed to convert nvarchar to int.
Generic
public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector)
{
var tbl = new DataTable();
tbl.Columns.Add("Id", typeof(T));
foreach (var item in list)
{
tbl.Rows.Add(selector.Invoke(item));
}
return tbl;
}
The cleanest way to work with it. Assuming your table is a list of integers called "dbo.tvp_Int" (Customize for your own table type)
Create this extension method...
public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data)
{
if(paramCollection != null)
{
var p = paramCollection.Add(parameterName, SqlDbType.Structured);
p.TypeName = "dbo.tvp_Int";
DataTable _dt = new DataTable() {Columns = {"Value"}};
data.ForEach(value => _dt.Rows.Add(value));
p.Value = _dt;
}
}
Now you can add a table valued parameter in one line anywhere simply by doing this:
cmd.Parameters.AddWithValueFor_Tvp_Int("#IDValues", listOfIds);
Use this code to create suitable parameter from your type:
private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
DataTable dt = new DataTable();
var properties = typedParameter.GetType().GetProperties().ToList();
properties.ForEach(p =>
{
dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
});
var row = dt.NewRow();
properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
dt.Rows.Add(row);
return new SqlParameter
{
Direction = ParameterDirection.Input,
ParameterName = name,
Value = dt,
SqlDbType = SqlDbType.Structured
};
}
If you have a table-valued function with parameters, for example of this type:
CREATE FUNCTION [dbo].[MyFunc](#PRM1 int, #PRM2 int)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM MyTable t
where t.column1 = #PRM1
and t.column2 = #PRM2
)
And you call it this way:
select * from MyFunc(1,1).
Then you can call it from C# like this:
public async Task<ActionResult> MethodAsync(string connectionString, int? prm1, int? prm2)
{
List<MyModel> lst = new List<MyModel>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandText = $"select * from MyFunc({prm1},{prm2})";
using (var reader = await command.ExecuteReaderAsync())
{
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
MyModel myModel = new MyModel();
myModel.Column1 = int.Parse(reader["column1"].ToString());
myModel.Column2 = int.Parse(reader["column2"].ToString());
lst.Add(myModel);
}
}
}
}
}
View(lst);
}
Hi I have the following code which lists all column names for a given SQL table:
public static DataTable GetSQLTableSchema(string _connectionString,string _tableName)
{
string connectionString = _connectionString;
string tableName = _tableName.Trim();
DataTable schemaTable = new DataTable();
SqlDataReader sqlDataReader;
try {
using (SqlConnection connection = new SqlConnection(connectionString))
{
string queryString = "SELECT * FROM " + tableName;
using(SqlCommand queryCMD = new SqlCommand(queryString))
{
queryCMD.Connection = connection;
connection.Open();
sqlDataReader = queryCMD.ExecuteReader(CommandBehavior.KeyInfo);
schemaTable = sqlDataReader.GetSchemaTable();
connection.Close();
}
}
}catch(Exception ex)
{
Console.WriteLine(ex);
}
#region Print Table Schema
foreach(DataRow field in schemaTable.Rows)
{
foreach(DataColumn property in schemaTable.Columns)
{
Console.WriteLine("{0} - {1}",field[0],//HERE!);
}
}
#endregion
return schemaTable;
}
At the line I marked (HERE!) I want to print the column's name and a boolean whether it is allow NULL. But all I get from field is an itemarray[]. I am sure that one of the values the array contains shows the allow NULL value. I can not figure out which one it is. Besides I think there must be a more elegant way.
You can do:
Console.WriteLine(property.ColumnName +" = "+ field[property].ToString());
AllowDBNull Reference:
Gets or sets a value that indicates whether null values are allowed in this column for rows that belong to the table.
DataColumn has an AllowDBNull property.
http://msdn.microsoft.com/en-gb/library/system.data.datacolumn.allowdbnull(v=vs.100).aspx
Assuming the ODBC driver in question is returning the Schema table appropriately, this should give you what you need.
SELECT column_name, is_nullable FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?
I have a SQL Server 2005 database. In a few procedures I have table parameters that I pass to a stored proc as an nvarchar (separated by commas) and internally divide into single values. I add it to the SQL command parameters list like this:
cmd.Parameters.Add("#Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";
I have to migrate the database to SQL Server 2008. I know that there are table value parameters, and I know how to use them in stored procedures. But I don't know how to pass one to the parameters list in an SQL command.
Does anyone know correct syntax of the Parameters.Add procedure? Or is there another way to pass this parameter?
DataTable, DbDataReader, or IEnumerable<SqlDataRecord> objects can be used to populate a table-valued parameter per the MSDN article Table-Valued Parameters in SQL Server 2008 (ADO.NET).
The following example illustrates using either a DataTable or an IEnumerable<SqlDataRecord>:
SQL Code:
CREATE TABLE dbo.PageView
(
PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
#Display dbo.PageViewTableType READONLY
AS
BEGIN
MERGE INTO dbo.PageView AS T
USING #Display AS S
ON T.PageViewID = S.PageViewID
WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END
C# Code:
private static void ExecuteProcedure(bool useDataTable,
string connectionString,
IEnumerable<long> ids)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = "dbo.procMergePageView";
command.CommandType = CommandType.StoredProcedure;
SqlParameter parameter;
if (useDataTable) {
parameter = command.Parameters
.AddWithValue("#Display", CreateDataTable(ids));
}
else
{
parameter = command.Parameters
.AddWithValue("#Display", CreateSqlDataRecords(ids));
}
parameter.SqlDbType = SqlDbType.Structured;
parameter.TypeName = "dbo.PageViewTableType";
command.ExecuteNonQuery();
}
}
}
private static DataTable CreateDataTable(IEnumerable<long> ids)
{
DataTable table = new DataTable();
table.Columns.Add("ID", typeof(long));
foreach (long id in ids)
{
table.Rows.Add(id);
}
return table;
}
private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids)
{
SqlMetaData[] metaData = new SqlMetaData[1];
metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
SqlDataRecord record = new SqlDataRecord(metaData);
foreach (long id in ids)
{
record.SetInt64(0, id);
yield return record;
}
}
Further to Ryan's answer you will also need to set the DataColumn's Ordinal property if you are dealing with a table-valued parameter with multiple columns whose ordinals are not in alphabetical order.
As an example, if you have the following table value that is used as a parameter in SQL:
CREATE TYPE NodeFilter AS TABLE (
ID int not null
Code nvarchar(10) not null,
);
You would need to order your columns as such in C#:
table.Columns["ID"].SetOrdinal(0);
// this also bumps Code to ordinal of 1
// if you have more than 2 cols then you would need to set more ordinals
If you fail to do this you will get a parse error, failed to convert nvarchar to int.
Generic
public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector)
{
var tbl = new DataTable();
tbl.Columns.Add("Id", typeof(T));
foreach (var item in list)
{
tbl.Rows.Add(selector.Invoke(item));
}
return tbl;
}
The cleanest way to work with it. Assuming your table is a list of integers called "dbo.tvp_Int" (Customize for your own table type)
Create this extension method...
public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data)
{
if(paramCollection != null)
{
var p = paramCollection.Add(parameterName, SqlDbType.Structured);
p.TypeName = "dbo.tvp_Int";
DataTable _dt = new DataTable() {Columns = {"Value"}};
data.ForEach(value => _dt.Rows.Add(value));
p.Value = _dt;
}
}
Now you can add a table valued parameter in one line anywhere simply by doing this:
cmd.Parameters.AddWithValueFor_Tvp_Int("#IDValues", listOfIds);
Use this code to create suitable parameter from your type:
private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
DataTable dt = new DataTable();
var properties = typedParameter.GetType().GetProperties().ToList();
properties.ForEach(p =>
{
dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
});
var row = dt.NewRow();
properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
dt.Rows.Add(row);
return new SqlParameter
{
Direction = ParameterDirection.Input,
ParameterName = name,
Value = dt,
SqlDbType = SqlDbType.Structured
};
}
If you have a table-valued function with parameters, for example of this type:
CREATE FUNCTION [dbo].[MyFunc](#PRM1 int, #PRM2 int)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM MyTable t
where t.column1 = #PRM1
and t.column2 = #PRM2
)
And you call it this way:
select * from MyFunc(1,1).
Then you can call it from C# like this:
public async Task<ActionResult> MethodAsync(string connectionString, int? prm1, int? prm2)
{
List<MyModel> lst = new List<MyModel>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandText = $"select * from MyFunc({prm1},{prm2})";
using (var reader = await command.ExecuteReaderAsync())
{
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
MyModel myModel = new MyModel();
myModel.Column1 = int.Parse(reader["column1"].ToString());
myModel.Column2 = int.Parse(reader["column2"].ToString());
lst.Add(myModel);
}
}
}
}
}
View(lst);
}