-Hello, World!-
I am working on a C# with ASP.NET project and I have run into a snag. The project is to dynamically load metadata and records from tables to edit them without statically defining what tables could be edited. As such I need to get the schema/metadata of different tables.
Here's what I have so far:
// initialize the connection
using (SqlConnection con = new SqlConnection(metadata.DatabaseString))
{
// open the connection
con.Open();
// initialize a new SqlCommand to get the schema
SqlCommand command = con.CreateCommand();
command.CommandType = CommandType.Text;
// 0 = 1 ensures it's always an empty data set
command.CommandText = "SELECT * FROM " + metadata.TableName + " WHERE 0=1;";
// set to SchemaOnly to improve performance (i think)
SqlDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly);
// GetSchemaTable() gets the table's metadata
DataTable dataTable = reader.GetSchemaTable();
// loops through all the rows of the data table
foreach (DataRow row in dataTable.Rows)
{
// field names found here: https://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable(v=vs.110).aspx#Remarks
metadata.ColumnMetadata.Add(new ColumnWrapper()
{
ColumnType = GetTypeFromSql(row.Field<string>("DataTypeName")),
ColumnRawType = row.Field<string>("DataTypeName"),
ColumnName = row.Field<string>("ColumnName"),
ByteSize = row.Field<int>("ColumnSize"),
IsKey = row.Field<bool?>("IsKey") ?? false
});
}
}
The issue is the IsKey field is always empty. My SQL Server Table was created using the following query:
CREATE TABLE [dbo].[Dtm_LKUP_Role] (
[DtmRoleId] INT IDENTITY (1, 1) NOT NULL,
[RoleName] VARCHAR(32) NOT NULL,
[IsActive] BIT DEFAULT ((1)) NOT NULL,
PRIMARY KEY CLUSTERED ([DtmRoleId] ASC)
);
Here's what I have tried so far:
Use a different table, same results
Access dataTable.Columns["IsKey"]
No matter where I look I can't find the information I need. Does anyone have any ideas on what could cause this? In case it is relevant, I am using an MDF file and the LocalDB for my database connection rather than a live server.
Houston, we have lift off!
Based on the help from mjwills, I managed to get it working by changing my code to the following:
// initialize the connection
using (SqlConnection con = new SqlConnection(metadata.DatabaseString))
{
// open the connection
con.Open();
// initialize a new SqlCommand to get the schema. 0 = 1 ensures an empty data set
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM " + metadata.TableName + " WHERE 0=1", con);
// GetSchemaTable() gets the table's metadata
DataTable dataTable = new DataTable();
// tell the adapater to fill in the missing schema
adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
// fill the datatable with the schema
adapter.FillSchema(dataTable, SchemaType.Mapped);
// loops through all the rows of the data table
foreach (DataColumn column in dataTable.Columns)
{
// field names found here: https://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable(v=vs.110).aspx#Remarks
metadata.ColumnMetadata.Add(new ColumnWrapper()
{
ColumnType = column.DataType,
ColumnName = column.ColumnName,
ByteSize = column.MaxLength,
IsKey = dataTable.PrimaryKey.Contains(column)
});
}
}
I appreciate all the help from those who commented on my original question :)
Related
I have this user table type in SQL Server:
CREATE TYPE [dbo].[ListNew] AS TABLE
(
[Id] [int] NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC) WITH (IGNORE_DUP_KEY = OFF)
)
GO
And use this type in stored procedure parameter:
....
(#lstNew ListNew READONLY,
#UserName nvarchar(128))
AS
....
And using this stored procedure in ASP.NET MVC with this code:
List<int> lstNew = MyList.Select(o => o.Key).ToList();
List<XXXView> lstView = db.Database.SqlQuery<XXXView>("MyStoredProcedure #lstNew,#UserName",
new SqlParameter("#lstNew", lstNew),
new SqlParameter("#UserName", userName)).ToList();
but it's not working and get this error:
No mapping exists from object type System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] to a known managed provider native type.
I try without ListNew and used only username, it's working
Edit:
I use this code:
myParameter.SqlDbType = SqlDbType.Udt;
myParameter.UdtTypeName = "ListNew";
But I get the same warning
This is a solved problem and properly documented in - cough - the documentation.
YOu will need to define the table on the server side and then can pass in a table valued parameter.
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters
This runs down to using the SqlDbType Structured.
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.CategoryTableType";
You CAN use a DataTable, but then you introduce the most overhead approach possible as object model - or you just use...
https://forums.asp.net/t/1845039.aspx?Table+Value+Parameter+Use+With+C+without+using+DataTable
Basically you transform your data into SqlDataRecords and pass them in. Needs some metadata - but generally this can be generalized and fits in below a page of code. The link has the code (which I can not copy here due to - well - it not being MY code).
I always use XML to pass these type of DATA to sqlserver sproc.
When you pass your XML to sproc you can use something like this to have it as a table in your sproc:
(commented lines are my XML's structure. manipulate it for your own use.)
EXEC sp_xml_preparedocument #XmlHandle output,#FieldPermissionAccessXML
--'<FieldPermissions>
--<FieldPermission FieldName="" RoleID="1" Access="1" />
--<FieldPermission FieldName="" RoleID="2" Access="1" />'
--select * from JMP_FieldPermissions
INSERT INTO #FieldPermissionsTable
SELECT *--ID,Value, Value2, Navi_User
FROM OPENXML (#XmlHandle, './Row_ID/Elements')
WITH (TE_ID VARCHAR(MAX) '#ID',
Value VARCHAR(MAX) '#Value',
Value2 VARCHAR(MAX) '#Value2',
NAVI_USER VARCHAR(MAX) '#Navi_User'
)
Create the DataTable variable in c#, put the data in the DataTable, pass it to sp:
List<int> lstNew = MyList.Select(o => o.Key).ToList();
DataTable lstNewTable = new ListNew();
foreach (var id in lstNew )
{
lstNewTable.Rows.Add(id);
}
List<XXXView> lstView = db.Database.SqlQuery<XXXView>("MyStoredProcedure #lstNew,#UserName",new SqlParameter("#lstNew", lstNewTable),
new SqlParameter("#UserName", userName)).ToList();
Several answers were close but none gave a full working model. #Alison Niu just needs to add a column & name to populate the DataTable. #Saurabh Gadani the reflection is very flexible but the Props are Null. Also the cmd.Parameters needs some special values set for Sql to find the table definition. I am grateful for these answers that got me closer to a solution.
Populate a DataTable from a List of Integers
private DataTable ListInt_ToTable(List<int> tableThese)
{
DataTable lstNewTable = new DataTable();
//Columns
lstNewTable.Columns.Add("ID", typeof(int));
//Values
foreach (var id in tableThese)
{
lstNewTable.Rows.Add(id);
}
return lstNewTable;
}
The TYPE defines the table layout for SQL to receive the parameter. It can be written within the Stored Procedure but this is how I prefer to keep it in the application that built the data:
public static string SqlCmd_spStackOverflow_Loaded_ListIDs = #"
IF TYPE_ID(N'IdLoadedTableType') IS NULL
Begin
CREATE TYPE dbo.IdLoadedTableType
AS TABLE ( ID INT );
End
EXEC dbo.spStackOverflow_Loaded_ListIDs
#TblIds
";
Sending the Sql to run the Stored Procedure. Notice the parameters **
DataTable mappingTbl = ListInt_ToTable(mappingData);
using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))
{
try
{
DataTable results = new DataTable();
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(SqlCmd_spStackOverflow_Loaded_ListIDs, sqlConnection);
//apply parameters
//NOT VIA sqlDataAdapter.SelectCommand.Parameters.AddWithValue(...);
var specialParm = new SqlParameter(); // **
specialParm.ParameterName = "#TblIds"; // acts like a Declare in SQL
specialParm.Value = (object)mappingTbl; // sets the data valaes
specialParm.SqlDbType = SqlDbType.Structured; // unique to User-Defined Table parameters
specialParm.TypeName = "dbo.IdLoadedTableType"; // refers to created type
sqlDataAdapter.SelectCommand.Parameters.Add(specialParm); // done
sqlDataAdapter.Fill(results);
return results;
}
catch (Exception ex)
{
throw new Exception("An error occurred while executing a SQL Read statement with Parameters. SQL Statement: " + SqlCmd_spStackOverflow_Loaded_ListIDs + " Exception: " + ex.Message);
}
}
This model is working in my solution, except I use long in C# & BigInt in sql. Please improve options as you find something.
You can not pass any generic type list as SP parameter, must have to pass it as DataTable instead of List.
Here is an eaxample:
DataTable mappingTbl = ListToDataTable(mappingData);
SqlConnection con = new SqlConnection(conStr);
con.Open();
SqlCommand cmd = new SqlCommand(StoredProcedure.GetSavedFormList, con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#UDT_EBFromMappingTable", mappingTbl);
public DataTable ListToDataTable<T>(List<T> items)
{
DataTable dataTable = new DataTable(typeof(T).Name);
//Get all the properties
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
//Defining type of data column gives proper data table
var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType);
//Setting column names as Property names
dataTable.Columns.Add(prop.Name, type);
}
foreach (T item in items)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
//inserting property values to datatable rows
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
//put a breakpoint here and check datatable
return dataTable;
}
May this helps you. :)
Please note that when I using the word "schema" I mean Schema as the security feature provided by Sql Server ("dbo"...) that you must provide with the table name if you want your query to succeed, NOT its metadata (columns...).
I'm using this bit of code to get the table metadata (columns, types, etc) :
// Parameter table includes the schema name.
public DataTable GetTableSchema(string table)
{
var tbl = new DataTable();
using (var conn = new SqlConnection(ConnectionString))
using (var adapter = new SqlDataAdapter(String.Format("SELECT * FROM {0} WHERE 1=0", table), conn))
{
tbl = adapter.FillSchema(tbl, SchemaType.Source);
}
return tbl;
}
My issue is that the property DataTable.TableName doesn't contain the table schema ("dbo", or any custom schema) and I can't find any property in the object that allows me to get that information, so it is lost during the process (or I have to pass several variables to methods, while I'd like to keep everything in the DataTable object, which should be logical).
Where / how can I get it along with the database structure ?
Only solution I found : adding tbl.TableName = table; before returning the table but it feels... wrong.
You could query the INFORMATION_SCHEMA.TABLES view and get your info from the TABLE_SCHEMA field
"SELECT TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES where table_name = '" + table "'";
It is not clear why you need this info, so I can only suggest to run this code
SqlCommand cmd = new SqlCommand(#"SELECT TABLE_SCHEMA
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #table", con);
cmd.Parameters.Add("#table", SqlDbType.NVarChar).Value = table;
string schema = (string)cmd.ExecuteScalar();
There is also an alternative, that could return some of the info on your table and the table_schema in a single call
DataTable dt = new DataTable();
dt = cnn.GetSchema("COLUMNS", new string[] { null, null, table, null });
Console.WriteLine(dt.Rows[0]["TABLE_SCHEMA"].ToString());
This code returns a lot of info about your table and also the TABLE_SCHEMA colum.
Not sure if this approach is suitable for your purpose because here the datatable returned contains a row for each column and each column of this DataTable contains details about the columns (Like OrdinalPosition, IsNullable, DataType, Character_Maximum_Length)
a question from a newbie to c# and apologies for the lenght of it. I have the following scenario. I have a small console application that populates a datatable by connecting to an external system and then needs to update existing records and insert new ones into an oracle table. The columns in the datatable are not named the same as the oracle table columns and not in the same order. I read another post on here with a similar scenario (loading from a file into a table) and it mentioned that doing an update/insert with an OracleDataAdapter would work. A simplified datatable and oracle table are
DataTable table = new DataTable();
table.Columns.Add("Product", typeof(String));
table.Columns.Add("Price", typeof(double));
table.Columns.Add("Effective_Date", typeof(DateTime));
//sample data
table.Rows.Add("abcd", 1.011, DateTime.Today);
table.Rows.Add("efg", 1.00, DateTime.Today);
table.Rows.Add("hijk", 20, DateTime.Today);
The oracle table has the structure
ITEM VARCHAR2(20 BYTE) NOT NULL ENABLE,
EFF_DATE DATE,
VALUE NUMBER
I have tried the following code to use the datatable and an adapter to update the oracle table but I'm missing something. I'm also wondering if I'm barking up the wrong tree. The majority of examples I have seen of using a dataadapter first does a select from the table and then puts the results into a grid where a user would be able to add, update, insert, or delete records and then uses the dataadapter to update the table. In my case I'm wondering if I get it to work if all records in the datatable will be treated as an insert anyway as there is no connection between the datatable and the oracle table.
I'm using the Oracle.ManagedDataAccess.Client to connect and do the updates
public static void UpdateOrSaveItems(DataTable dt)
{
String insert_statement, update_statement, select_statement;
select_statement = "SELECT * from items";
insert_statement = "INSERT INTO items (item, eff_date, value) values (:pInsItem,:pInsEffDate,:pInsValue)";
update_statement = "UPDATE items set eff_date = :pUpdEffDate, value = :pUpdValue where item = :pUpdItem";
using (OracleConnection conn = theDatabase.ConnectToDatabase())
{
using (OracleDataAdapter oraAdapter = new OracleDataAdapter(select_statement, conn))
{
//build update/insert commands and parameters
oraAdapter.UpdateCommand = new OracleCommand(update_statement, conn);
oraAdapter.InsertCommand = new OracleCommand(insert_statement, conn);
oraAdapter.UpdateCommand.BindByName = true;
oraAdapter.InsertCommand.BindByName = true;
OracleParameter pUpdItem = new OracleParameter("pUpdItem", OracleDbType.Varchar2);
pUpdItem.SourceColumn = dt.Columns[0].ColumnName;
OracleParameter pUpdEffDate = new OracleParameter("pUpdEffDate", OracleDbType.Date);
pUpdEffDate.SourceColumn = dt.Columns[2].ColumnName;
OracleParameter pUpdValue = new OracleParameter("pUpdValue", OracleDbType.Double);
pUpdValue.SourceColumn = dt.Columns[1].ColumnName;
OracleParameter pInsItem = new OracleParameter("pInsItem", OracleDbType.Varchar2);
pUpdItem.SourceColumn = dt.Columns[0].ColumnName;
OracleParameter pInsEffDate = new OracleParameter("pInsEffDate", OracleDbType.Date);
pInsEffDate.SourceColumn = dt.Columns[2].ColumnName;
OracleParameter pInsValue = new OracleParameter("pInsValue", OracleDbType.Double);
pInsValue.SourceColumn = dt.Columns[1].ColumnName; oraAdapter.UpdateCommand.Parameters.Add(pUpdItem);
oraAdapter.UpdateCommand.Parameters.Add(pUpdEffDate);
oraAdapter.UpdateCommand.Parameters.Add(pUpdValue);
oraAdapter.InsertCommand.Parameters.Add(pInsItem);
oraAdapter.InsertCommand.Parameters.Add(pInsEffDate);
oraAdapter.InsertCommand.Parameters.Add(pInsValue);
oraAdapter.Update(dt);
}
}
}
When I run this I get an error that I cannot insert a null into column that is defined as a key. In the datatable none of them are null. I'm missing something on telling it where the data is but am unsure what it is. Also wondering if this is the right way to do this sort of thing. I wanted to avoid
loop through datatable
select to see if record is in oracle table
if in table update else insert
because the volume of records could a couple of hundred thousand and wasn't sure what the performance would be like.
Are you initializing ColumnName properties of the DataTable object that you are passing in? If not, they could be reading as null.
For instance
public static void Main()
{
Datatable myDataTable = new DataTable();
myDataTable.Columns = new Columns[3];
myDataTable.Columns[0].ColumnName = "Employees";
myDataTable.Columns[1].ColumnName = "Salary";
myDataTable.Columns[2].ColumnName = "Department";
UpdateOrSaveItems(myDataTable);
}
I found the error. I hadn't set the source column on one of my insert parameters. I had set the source column on the pUdpItem twice instead of setting it for pUdpItem and pInsItem
I have this SQL Table:
TABLE: Info
COLUMNS:| Name | Value
--------|----------|------------------
ROW: | Server | 255.255.255.255
ROW: | Host | 212.212.212.212
ROW: | User | Admin
I'm selecting this table like that: SELECT * FROM Info
Now after I got all in this table.
I want to get the value Where Name = 'Server' and put it into the Server variable.
What is the best method to do it in C#?
DataSet? DataReader? And how can I accomplish this?
If you didn't understand what I need here is another good explanation thx to Tim:
I'm trying to get a specified column's value based on the value in another column of the same row
Based on your latest comments, it appears that you want to get all the rows in the table, and then be able to select a given row based on the Name column.
A DataTable would be best if your program is going to need to access the different rows at different times - as long as the DataTable is in memory/cached, you can pull the value for any name at any time.
If you just need to do it once, a SqlDataReader would probably be faster, but its forward-only.
DataTable example:
Assuming you've already filled the DataTable (name info in the example), you can use the Select method:
DataRow[] selectedRows = info.Select("Name = 'Server'");
string serverIP = selectedRows[0]["Value"].ToString();
DateReader example:
Based upon #Kobe's code, simply check the Name each time you advance to the next record, and then pull the Value out:
bool valueFound = false;
while (reader.Read() && !valueFound)
{
if (reader["Name"].ToString() == "Server")
{
serverIP = reader["Value"].ToString();
valueFound = true;
}
}
There are some caveats to be aware of. First, the Select method of the DataTable returns an array of DataRow, so if more than one record has "Server" in the Name column, you'll get multiple results. If that's by design, that's fine - just loop through the array of DataRows.
Second, if there are a lot of rows in the table, or there is the potential down the road, the reader may be slower depending on where the record of interest is in the table. And if you're dealing with the possibility of multiple records in the table matching the Name criteria, it's probably easier all around to just stick with a DataTable.
Is this what you are looking for , if so let me know , i will refine the code with USING key word and remove the sql inline and post you one more answer soon
one more example from google ,
// instantiate and open connection
conn = new
SqlConnection("Server=(local);DataBase=Northwind;Integrated Security=SSPI");
conn.Open();
// don't ever do this!
// SqlCommand cmd = new SqlCommand(
// "select * from Customers where city = '" + inputCity + "'";
// 1. declare command object with parameter
SqlCommand cmd = new SqlCommand(
"select * from Customers where city = #City", conn);
// 2. define parameters used in command object
SqlParameter param = new SqlParameter();
param.ParameterName = "#City";
param.Value = inputCity;
// 3. add new parameter to command object
cmd.Parameters.Add(param);
// get data stream
reader = cmd.ExecuteReader();
// write each record
while(reader.Read())
{
Console.WriteLine("{0}, {1}",
reader["CompanyName"],
reader["ContactName"]);
}
}
finally
{
// close reader
if (reader != null)
{
reader.Close();
}
// close connection
if (conn != null)
{
conn.Close();
}
}
other example...
SqlCommand sqlComm = new SqlCommand("SELECT * FROM Info where name='+server+'", sqlConn);
SqlDataReader r = sqlComm.ExecuteReader();
while ( r.Read() ) {
string name = (string)r["Name"];
Debug.WriteLine(username + "(" + userID + ")");
}
r.Close();
I'm wanting to get a list of the column names returned from a SQL SELECT statement. Can someone suggest an easy way to do this?
I have a tool that lets users define a query using any SQL SELECT statement. The results of the query are then presented in a custom manner. To set up the presentation, I need to know the column names so that the user can store formatting settings about each column.
Btw, the formatting settings are all being created via ASP.NET web pages, so the query results will end up in .NET if that helps with any ideas people have.
Any ideas?
You should be able to do this using the GetName method. Something like this probably:
SqlDataReader mySDR = cmd.ExecuteReader();
for(int i = 0;i < mySDR.FieldCount; i++)
{
Console.WriteLine(mySDR.GetName(i));
}
This is something you could do entirely from a asp.net page. No special/extra SQL required.
Assuming SQL Server: You could use SET FMTONLY to just return metadata (and not the actual data), e.g.:
USE AdventureWorks2008R2;
GO
SET FMTONLY ON;
GO
SELECT *
FROM HumanResources.Employee;
GO
SET FMTONLY OFF;
GO
You can get by something as following
Note : You need to fill the DataTable of the Dataset.........
DataSet1 DataSet1 = new DataSet1();
DataTable dt = DataSet1.Tables(0);
DataColumn dc = null;
foreach (DataColumn dc_loopVariable in dt.Columns) {
dc = dc_loopVariable;
Response.write(dc.ColumnName.ToString() + " " + dc.DataType.ToString() + "<br>");
}
Another method to just return meta data is
select top 0 * from table
If you know the table name you could try using:
desc <table_name>
I'm assuming you are using SQL Server.
Or as an alternative:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableNameGoesHere'
ORDER BY ORDINAL_POSITION
You might want to use the second option if you are going to be using ASP.NET
This will get you more than the column name if you need more information about each column like size, ordinal,etc. A few of the most important properties are listed, but there are more.
Note, DataObjects.Column is a POCO for storing column information. You can roll your own in your code. Also, note I derive the .Net type as well, useful for converting SQL data types to .Net (C#) ones. ConnectionString and TableName would be supplied from a caller.
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand("Select top(1) * from " + TableName + " Where 1=0");
comm.CommandType = CommandType.Text;
comm.Connection = conn;
using (SqlDataReader reader = comm.ExecuteReader(CommandBehavior.KeyInfo))
{
DataTable dt = reader.GetSchemaTable();
foreach (DataRow row in dt.Rows)
{
//Create a column
DataObjects.Column column = new DataObjects.Column();
column.ColumnName = (string)row["ColumnName"];
column.ColumnOrdinal = (int)row["ColumnOrdinal"];
column.ColumnSize = (int)row["ColumnSize"];
column.IsIdentity = (bool)row["IsIdentity"];
column.IsUnique = (bool)row["IsUnique"];
//Get the C# type of data
object obj = row["DataType"];
Type runtimeType = obj.GetType();
System.Reflection.PropertyInfo propInfo = runtimeType.GetProperty("UnderlyingSystemType");
column.type = (Type)propInfo.GetValue(obj, null);
//Set a string so we can serialize properly later on
column.DataTypeFullName = column.type.FullName;
//I believe this is SQL Server Data Type
column.SQLServerDataTypeName = (string)row["DataTypeName"];
//Do something with the column
}
}
}