DataTables: restrictionValues and table names - c#

I have got a single function using OleDb:
I think maybe the use of a list to find out if a specific table exist could be made better, like this:
Check for MS Access database table if not exist create it
But I also want to understand the old code given, to learn something.
Questions:
1) What does the exact restrictionValues mean in the example code below? ( not solved)
2) Why row.ItemArray[2] of all cells is containing the table names? (solved)
3) Is there a better way to get table names out of a database? (solved)
This is the code I have got:
public List<string> GetTableNames(string tableName, string[] field_names)
{
List<string> retTableNames = new List<string>();
if (dbConnection != null)
{
dbConnection.Open();
string strSQL = "SELECT * ";
string[] restrictionValues = new string[4] { null, null, null, "TABLE" };
OleDbCommand cmd = new OleDbCommand(strSQL, dbConnection);
try
{
/*search after all possible tables in dataset*/
DataTable schemaInformation = dbConnection.GetSchema("Tables", restrictionValues);
foreach (DataRow row in schemaInformation.Rows)
{
retTableNames.Add(row.ItemArray[2].ToString());
}
}
catch
{
retTableNames = null;
}
}
return retTableNames;
}

(I just noticed that you said you already get this first part, but I'll leave it in anyway. There's a little more below about "TABLE".)
Some of the explanation of the restrictions is in the Main program in the link that #jdweng gave.
// You can specify the Catalog, Schema, Table Name, Table Type to get
// the specified table(s).
// You can use four restrictions for Table, so you should create a 4 members array.
String[] tableRestrictions = new String[4];
// For the array, 0-member represents Catalog; 1-member represents Schema;
// 2-member represents Table Name; 3-member represents Table Type.
// Now we specify the Table Name of the table what we want to get schema information.
tableRestrictions[2] = "Course";
DataTable courseTableSchemaTable = conn.GetSchema("Tables", tableRestrictions);
The rest of the explanation is in the overload of GetSchema: GetSchema Method (String, String[]), which is what you're using.
Verbatim:
In order to set values on a given restriction, and not set the values of other restrictions, you need to set the preceding restrictions to null and then put the appropriate value in for the restriction that you would like to specify a value for.
An example of this is the "Tables" collection. If the "Tables" collection has three restrictions -- database, owner, and table name--and you want to get back only the tables associated with the owner "Carl", you need to pass in the following values: null, "Carl". If a restriction value is not passed in, the default values are used for that restriction. This is the same mapping as passing in null, which is different from passing in an empty string for the parameter value. In that case, the empty string ("") is considered to be the value for the specified parameter.
A more complete article on schema restrictions.
It looks like you can omit some parameters, which is why the example above only lists 3 restrictions.
I've been looking for an explanations of the "TABLE" parameter but it's hard to find. It's either a default that gets all tables, or it's ignored, or something else. The easiest way to get all table types might be to do a basic DataTable table = connection.GetSchema("Tables"); then get the types of each table to see what the options are. Otherwise, sticking to "TABLE" will no doubt get the commonly used tables, not system tables or anything like that.
Hope your ears are ok.

Related

Create column from another table dynamically

I'm working with TSQL and C#. I have two queries that return strings:
string[] allSubcategories = dt.AsEnumerable().Select(x => x.Field<string>("SubcategoryName")).Distinct().ToArray();
var redMark = db.GetTableBySQL("SELECT * FROM RedMarkItems");
string[] redMarkColumns = redMark.Columns.Cast<DataColumn>().Select(x => x.ColumnName).ToArray();
So, as you can see I have two different arrays, first I get subcategoriesNames:
and all columns of table RedMarkItems:
That I want to do is to create column dynamically, I mean, if subcategorieName does not exist as column in RedMarkItems do an Update and create it someting like:
var createColumn = db.ExeSQL($"ALTER TABLE RedMarkItems ADD {ColumnName} BIT");
How can I compare if subcategorieName does not exist as column in RedMarkItems table? Then create column as my query? Regards
If you want to know if a particular column exists in an already filled DataTable using the Linq approach then it is just:
bool exists = redMark.Columns.Cast<DataColumn>().Any(x => x.ColumnName == "SubCategoryName");
Instead, if you want to ask this info directly to the database then use the INFORMATION_SCHEMA views The Columns view is the one to use with a query like this.
string query = #"IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.Column
WHERE Column_Name = #colName)
SELECT 1 ELSE SELECT 0";
SqlCommand cmd = new SqlCommand(query, connection);
cmd.Parameters.Add("#colName", SqlDbType.NVarChar).Value = "SubCategoryName";
bool exists = (cmd.ExecuteScalar() == 1);
Now, the part about creating the column is pretty simple as code per se. It is just an appropriate ALTER TABLE. But there are a lot of things to be cleared before. What will be the datatype of the new column? What will be its length and precision? What will be the constraints applied to it (Null/Not Null defaults etc)? As you can see all these info are very important and require to be defined somewhere in your code.

Table schema as DataTable?

I did a search and found some seemingly related answers, but they don't really do what I'm after.
Given a valid connection string and a table name, I want to get a DataTable of the table. I.e. if the table has a column called "Name", I want the DataTable set up so I can do dt["Name"] = "blah";
The trick is, I don't want to hard code any of that stuff, I want to do it dynamically.
People tell you to use SqlConnection.GetSchema, but that gives you back a table with a bunch of stuff in it.
Everybody has random tricks like TOP 0 * from the table and get the schema from there, etc.
But is there a way to get the table with the primary keys, unique indexes, etc. Ie.. in the final format to do a bulk insert.
You can use SqlDataAdapter.FillSchema:
var connection = #"Your connection string";
var command = "SELECT * FROM Table1";
var dataAdapter = new System.Data.SqlClient.SqlDataAdapter(command, connection);
var dataTable = new DataTable();
dataAdapter.FillSchema(dataTable, SchemaType.Mapped);
This way you will have an empty DataTable with columns and keys defined and ready to use in code like dataTable["Name"] = "blah";.

SqlBulkCopy - The given ColumnName does not match up with any column in the source or destination

I'm trying to use SqlBulkCopy to copy data into an SQL database table however it is (wrongly) saying that the columns don't match. They do match. If I use a breakpoint to see the names of the columns being mapped, they're correct. The error message shows the name of the column, and it is correct.
This is my method. I have an identical method that does work and the only difference is where it gets the column names from. The strings containing the column names, however, are EXACTLY identical.
public static bool ManualMapImport(DataTable dataTable, string table)
{
if(dataTable != null)
{
SqlConnection connection = new SqlConnection(connectionString);
SqlBulkCopy import = new SqlBulkCopy(connection);
import.DestinationTableName = "[" + table + "]";
foreach (string s in Global.SelectedColumns)
{
/* The s string variable here is the EXACT same as
the c.ToString() in the other method below */
if (ColumnExists(table, s))
import.ColumnMappings.Add(s, s);
else
return false;
}
connection.Open();
import.WriteToServer(dataTable); //Error happens on this line
connection.Close();
return true;
}
else
{
return false;
}
}
This is the almost identical, working method:
public static bool AutoMapImport(DataTable dataTable, string table)
{
if (dataTable != null)
{
SqlConnection connection = new SqlConnection(connectionString);
SqlBulkCopy import = new SqlBulkCopy(connection);
import.DestinationTableName = "[" + table + "]";
foreach (DataColumn c in dataTable.Columns)
{
if (ColumnExists(table, c.ToString()))
import.ColumnMappings.Add(c.ToString(), c.ToString());
else
return false;
}
connection.Open();
import.WriteToServer(dataTable);
connection.Close();
return true;
}
else
{
return false;
}
}
If it helps, the column names are: ACT_Code, ACT_Paid, ACT_Name, ACT_Terminal_Code, ACT_TCustom1, ACT_TCustom2. These are exactly the same in the database itself. I'm aware that SqlBulkCopy mappings are case sensitive, and the column names are indeed correct.
This is the error message:
An unhandled exception of type 'System.InvalidOperationException'
occurred in System.Data.dll
Additional information: The given ColumnName 'ACT_Code' does not match
up with any column in data source.
Hopefully I'm just missing something obvious here, but I am well and truly lost.
Many thanks.
EDIT: For anyone happening to have the same problem as me, here's how
I fixed it.
Instead of having the ManualMapImport() method be a near-clone of
AutoMapImport(), I had it loop through the columns of the datatable
and change the names, then called AutoMapImport() with the amended
datatable, eliminating the need to try and map with plain strings at
all.
According to MSDN (here), the DataColumn.ToString() method returns "The Expression value, if the property is set; otherwise, the ColumnName property.".
I've always found the ToString() method to be wonky anyway (can change based on current state/conditions), so I'd recommend using the ColumnName property instead, as that's what you are actually trying to get out of ToString().
OK, failing that, then I'd have to guess that this is a problem with case-sensitivity in the names of the columns in the source datatable, as SQLBulkCopy is very case-sensitive even if the SQL DB is not. To address this, I would say that when you check to see if that column exists, then you should return/use the actual string from the datatable's column list itself, rather than using whatever string was passed in. This should be able to fix up any case or accent differences that your ColumnsExist routine might be ignoring.
I had the same problem... The message might seem a bit misleading, as it suggests you didn't perform the correct mapping.
To find the root of the problem I have decided to go step by step in adding table columns and calling the WriteToServer method.
Assuming you have a valid column mapping, you will have to ensure the following between the source DataTable and the destination table:
The column types and lengths (!) do match
You have provided a valid value for each non-empty (NOT NULL) destination column
If you don't control your identity column values and would like the SQL Server do this job for you, please make sure not to specify the SqlBulkCopyOptions.KeepIdentity option. In this case you don't add the identity column to your source either.
This should be all for your bulk insert to work. Hope it helps.

How to return column values from two different tables?

I'm trying to recieve data from an database table and my sql query also selects a column from another table that i would like to return aswell
while (dr.Read())
{
Inlagg comentsar = new Inlagg
{
names = (int)dr["name"],
dates = (DateTime)dr["Date"]
//get column value from the other table not the Inlagg table here pobbisble?
};
//This gets the column value from the other table but i dont know how to return it
string test = (string)dr["test"];
comen.Add(comentsar);
}
return comen;
How can i return a result that includes columns both columns from different tables? Side note: I have a column that i dont need to recieve here, altough the column is an int and i'm trying to recieve an string, is there any way to cast the column to an string? Any help or input highly appreciated, thanks!
EDIT: "Solved" it by simply creating a new class with the values i wanted which included columnvalues from different tables
Inside of your reader, your missing the following:
if(reader["Column"] != DBNull.Value)
model.Name = reader["Column"].ToString();
Your query for your SQL should correctly join the multiple tables, then in the returned result set you would have a column name to return by. You call the reader["..."] to access the value for said column.
As you notice the model class calls a name of a property.
The model is important as it represents your data, but to access through the reader you simply add another property.
That is because the column is null, so you can't cast a ToString() that is why the error occurs. You could attempt to also do:
int? example = reader["Column"] as int?;
Extend your class:
public class Inlagg{
...
addMyString(String myString){
this.myString = myString;
}
getMyString(){
return this.myString;
}
}

check if values are in datatable

I have an array or string:
private static string[] dataNames = new string[] {"value1", "value2".... };
I have table in my SQL database with a column of varchar type. I want to check which values from the array of string exists in that column.
I tried this:
public static void testProducts() {
string query = "select * from my table"
var dataTable = from row in dt.AsEnumerable()
where String.Equals(row.Field<string>("columnName"), dataNames[0], StringComparison.OrdinalIgnoreCase)
select new {
Name = row.Field<string> ("columnName")
};
foreach(var oneName in dataTable){
Console.WriteLine(oneName.Name);
}
}
that code is not the actual code, I am just trying to show you the important part
That code as you see check according to dataNames[index]
It works fine, but I have to run that code 56 times because the array has 56 elements and in each time I change the index
is there a faster way please?
the Comparison is case insensitive
First, you should not filter records in memory but in the datatabase.
But if you already have a DataTable and you need to find rows where one of it's fields is in your string[], you can use Linq-To-DataTable.
For example Enumerable.Contains:
var matchingRows = dt.AsEnumerable()
.Where(row => dataNames.Contains(row.Field<string>("columnName"), StringComparer.OrdinalIgnoreCase));
foreach(DataRow row in matchingRows)
Console.WriteLine(row.Field<string>("columnName"));
Here is a more efficient (but less readable) approach using Enumerable.Join:
var matchingRows = dt.AsEnumerable().Join(dataNames,
row => row.Field<string>("columnName"),
name => name,
(row, name) => row,
StringComparer.OrdinalIgnoreCase);
try to use contains should return all value that you need
var data = from row in dt.AsEnumerable()
where dataNames.Contains(row.Field<string>("columnName"))
select new
{
Name = row.Field<string>("columnName")
};
Passing a list of values is surprisingly difficult. Passing a table-valued parameter requires creating a T-SQL data type on the server. You can pass an XML document containing the parameters and decode that using SQL Server's convoluted XML syntax.
Below is a relatively simple alternative that works for up to a thousand values. The goal is to to build an in query:
select col1 from YourTable where col1 in ('val1', 'val2', ...)
In C#, you should probably use parameters:
select col1 from YourTable where col1 in (#par1, #par2, ...)
Which you can pass like:
var com = yourConnection.CreateCommand();
com.CommandText = #"select col1 from YourTable where col1 in (";
for (var i=0; i< dataNames.Length; i++)
{
var parName = string.Format("par{0}", i+1);
com.Parameters.AddWithValue(parName, dataNames[i]);
com.CommandText += parName;
if (i+1 != dataNames.Length)
com.CommandText += ", ";
}
com.CommandText += ");";
var existingValues = new List<string>();
using (var reader = com.ExecuteReader())
{
while (read.Read())
existingValues.Add(read["col1"]);
}
Given the complexity of this solution I'd go for Max' or Tim's answer. You could consider this answer if the table is very large and you can't copy it into memory.
Sorry I don't have a lot of relevant code here, but I did a similar thing quite some time ago, so I will try to explain.
Essentially I had a long list of item IDs that I needed to return to the client, which then told the server which ones it wanted loaded at any particular time. The original query passed the values as a comma separated set of strings (they were actually GUIDs). Problem was that once the number of entries hit 100, there was a noticeable lag to the user, once it got to 1000 possible entries, the query took a minute and a half, and when we went to 10,000, lets just say you could boil the kettle and drink your tea/coffee before it came back.
The answer was to stick the values to check directly into a temporary table, where one row of the table represented one value to check against. The temporary table was keyed against the user who performed the search, so this meant other users searches wouldn't become corrupted with each other, and when the user logged out, then we knew which values in the search table could be removed.
Depending on where this data comes from will depend on the best way for you to load the reference table. But once it is there, then your new query will look something like:-
SELECT Count(t.*), rt.dataName
FROM table t
RIGHT JOIN referenceTable rt ON tr.dataName = t.columnName
WHERE rt.userRef = #UserIdValue
GROUP BY tr.dataName
The RIGHT JOIN here should give you a value for each of your reference table values, including 0 if the value did not appear in your table. If you don't care which one don't appear, then changing it to an INNER JOIN will eliminate the zeros.
The WHERE clause is to ensure that your search only returns the unique items that you are looking for at the moment - the design should consider that concurrent access will someday occur here (even if it doesn't at the moment), so writing something in to protect it is advisable.

Categories

Resources