Get table data from dynamically selected table in LINQ C# - c#

I obtain a table reference as such:
public static void MyDataGrabbingClass<T>(MyModelCls Model) where T : class
{
DataContext dc = new DataContext(Config.ConnectionString);
//var ITable = (Devart.Data.Linq.ITable)dc.GetType().GetProperty(tableName).GetValue(dc, null);
//var table = dc.GetTable(ITable.GetType());
//var dataModel = dc.Mapping;
//Type tableType = ITable.GetType();
//var t = dc.Mapping.MappingSource.GetModel(typeof(DataContext)).GetMetaType(tableType);
var table = dc.GetTable<T>();
}
I want to then select the specific columns of data using:
var Query = from c in table where Model.DateToColName < Model.DateTo select (Model.ColSelections);
obviously I need to somehow map the table column, and not use Model.DateToColName in the where clause, how do I do this?
In other words, with my dynamically chosen table, how do i get the column data from a string column name

Unfortunately I don't think you'll be able to construct a dynamic query in LINQ (at least not easily). Take a look at Dapper.NET - it's a "simple object mapper for .NET" that might work for you, created by the makers of StackOverflow. For example, using Dapper.NET, your method might look like:
public static void MyDataGrabbingClass<T>(MyModelCls Model) where T : class
{
using (SqlConnection conn = new SqlConnecrion(Config.ConnectionString)
{
conn.Open();
string tableName = ...;
string dateToColumnName = ...;
// depending on how dateToColumnName is constructed, ensure it is not a SQL-injection risk
if (tableName.Any(c => !char.IsLetterOrDigit(c))
throw new ArgumentException("Invalid table name.");
if (dateToColumnName.Any(c => !char.IsLetterOrDigit(c))
throw new ArgumentException("Invalid column name.");
// Query is a
var results = conn.Query<T>("SELECT * FROM [" + tableName + "] WHERE [" + dateToColumnName + "] < #DateTo", new { DateTo = someDate });
...
}
}
Security concern: Dynamic SQL can be susceptible to SQL injection - so be sure that the dateToColumnName variable is not from user-input, or is sanitized or validated appropriately.

Related

How to translate table defintion from MS Access to Oracle using C# and OleDbConnection?

I need to copy a table from MS Access to Oracle without using OracleBulkCopy (using Managed Data Access).
First step is to create the table.
As can be seen in the code below I am now converting the fields manually while querying for the column length.
I would like to use a statement that converts the fields for me without having to add rules for all types manually since there are a lot of types.
Or better, just extract some sort of DDL that I can execute in Oracle.
Is this possible?
private int GetLength(string accessTableName, string columnName, OleDbConnection accessConnection)
{
columnName = $"[{columnName}]";
var accessCommand = new OleDbCommand($"select max(len({columnName})) from {accessTableName}", accessConnection);
return int.Parse(accessCommand.ExecuteScalar().ToString());
}
private void CreateEmptyTable(DataTable schemaTable, string tableName, OracleConnection oracleConnection, string accessTableName, OleDbConnection accessConnection)
{
var columnSpecs = new string[schemaTable.Rows.Count];
for (int i = 0; i < schemaTable.Rows.Count; ++i)
{
var name = schemaTable.Rows[i].ItemArray[0].ToString();
var dataType = schemaTable.Rows[i].ItemArray[5];
//var length = schemaTable.Rows[i].ItemArray[2];
var length = GetLength(accessTableName, name.ToString(), accessConnection);
var precision = schemaTable.Rows[i].ItemArray[3];
var scale = schemaTable.Rows[i].ItemArray[4];
var oracleDt = "";
switch (dataType.ToString())
{
case "System.String":
oracleDt = $"nchar({length})";
break;
case "System.Int32":
case "System.Int16":
var iLng = int.Parse(length.ToString()) * 2;
oracleDt = $"number({iLng},0)";
break;
case "System.Double":
case "System.Decimal":
oracleDt = $"number({length},{precision})";
break;
default:
throw new Exception();
}
name = name.ToString().ToUpper().Replace(' ', '_');
columnSpecs[i] = $"{name} {oracleDt}";
}
var query = $"create table MDB_{tableName.ToUpper().Replace(' ', '_')} ( {string.Join(",", columnSpecs)} )";
var oracleCommand = new OracleCommand(query, oracleConnection);
oracleCommand.ExecuteNonQuery();
}
Perhaps you try executing the command on the base table in place of the query.
So try something like this:
DoCmd.TransferDatabase acExport, "ODBC Database", strCon,
acTable, "tblHotels2", "tblHotelEXPORT", True
Now above is from Access, but your posted answer is on the right track.
When I send above to SQL server, long, money, memo, text files all get sent out and created on sql server. I suppose you could use above, and then execute a series of alter table to change the types, but that would be painful.
So before the pain approach, I would try above. Note that:
I used the transfer command on the base table, not a query.
I used "True" as that last value - that means no data copy, just structure.
You could also try/test a different ORACLE odbc driver. I don't have a test oracle server around, but sending the above to SQL server - it did a VERY nice job of creating the data types on the server side.
This option uses Interop instead but the result is disappointing. Numeric fields are converted to VARCHAR2, haven't tested other types yet. Note that it also requires a DSN file, and it requires the Oracle ODBC drivers.
For the people who could use this, here is the code:
public void CreateTableDefsUsingInterop(string accessFilePath, string accessTable, string oracleUser, string oraclePassword, string oracleTable, string oracleDSNFilePath)
{
var strConn = $"ODBC;FILEDSN={oracleDSNFilePath};UID={oracleUser};PWD={oraclePassword}";
var sTypExprt = "ODBC Database";
var interop = new Application();
interop.OpenCurrentDatabase(accessFilePath);
var db = interop.CurrentDb();
var emptyTable = $"n{accessTable}";
QueryDef qd;
qd = db.CreateQueryDef("access2ora1", $"select top 1 * into [{emptyTable}] from [{accessTable}]");
qd.Execute();
qd = db.CreateQueryDef("access2ora2", $"delete from [{emptyTable}]");
qd.Execute();
interop.DoCmd.TransferDatabase(AcDataTransferType.acExport, sTypExprt, strConn, AcObjectType.acTable, emptyTable, oracleTable);
interop.Quit(AcQuitOption.acQuitSaveNone);
}

Parameterize query with IN keyword c#

I am in a application where Parameterized Sql queries are not written. Below is code block
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var query = new StringBuilder("SELECT * FROM table");
query.Append(" Id = ");
query.Append(Id);
query.Append(" AND state IN ('");
query.Append(string.Join("','", state));
query.Append("')");
return dataContext.ExecuteQuery<MyClass>(query.ToString()).ToList();
}
I am refactoring code using parameterized query like this :
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var statestring = new StringBuilder("'");
statestring.Append(string.Join("','", state));
statestring.Append("'");
string myStates= statestring.ToString();
string query = "SELECT * FROM table WHERE Id ={0} AND state IN ({1})";
return dataContext.ExecuteQuery<MyClass>(query, new object[] {Id, myStates}).ToList();
}
}
I get no data on running this query. On debugging i found my query is getting formed like this
SELECT * FROM table WHERE Id ={0} AND state IN ({1}) where in ({1})
For state I see data as "'error',' warning'".
In sql server I run query like this
SELECT * FROM table WHERE Id =34 AND state IN ('error','warning').
Do i need to remove " " around mystate? I tried removing " using trim method and assigning it back to string but it didn't work. I can still see double quotes.
myStates = myStates.trim('"');
How can parameterize my query better without using any string builder for the same
Alternative suggestion: dapper...
int x = ...;
int[] y = ...
var items = connection.Query<MyClass>(
"SELECT * FROM table WHERE X = #x AND Y in #y", new {x,y}).AsList();
Dapper will deal with this for you, using an appropriate query for 0, 1, or many items, including (optional configuration options) padding the parameters to avoid query plan saturation (so when you have large lists, you use the same query and query-plan for 47 items, 48 items and 49 items, but possibly a different query for 50), and using string_split if it is supported on your server.
To parameterize the in clause every case has to be an individual parameters. So the in clause has to reflect that.
See this similar question: How to pass sqlparameter to IN()?
public List<MyClass> GetData(int Id, IEnumerable<string> state)
{
using (var dataContext = new DataContext(_connectionString))
{
var stateParameterNumbers = Enumerable.Range(1, state.Count())
.Select(i => string.Format("{{{0}}}", i));
var stateParameterString = string.Join(",", stateParameterNumbers);
string query = "SELECT * FROM table WHERE Id ={0} AND state IN (" + stateParameterString + ")";
return dataContext.ExecuteQuery<MyClass>(query, new object[] { Id }.Concat(state).ToArray()).ToList();
}
}
I think that you should change the way you pass the parameters:
return dataContext.ExecuteQuery<MyClass>(query, Id, stateString).ToList();
For reference please have a look at the signature of this method, which can be found here.

Simple way to just store IEnumerable of some type into new SQL server database table (code first) C#

I'm looking for a simple way to store any data objects in SQL server without defining a table first.
Think about this pseudo code creating IEnumerable of anomyous type (LINQ):
var result = from item in items select new { item.First, item.Last, Age = 42 };
I'm looking for a simple solution, a function call like this:
// StoreResultInNewTable(database/context, tablename, result);
I'm aware of EF6 and code first, but I don't want to define an explicit type (class). And I don't need the other parts of entity framework like caching data or detailed tracking database layout. If table already exists and object cannot be stored in there, raise error. Otherwise (create table) and insert data.
Data inserts should not be too slow (SqlBulkCopy / BulkInsert).
edit: I really look for a solution where the result set is stored as plain database table in SQL server which means having a property of .NET type string stored as (n)varchar, decimal as money and so on. Column names in database should be 1:1 to property names. I'm flexible regarding details, but should be similar to EF6 mapping in effect (without explicitly defined types).
No key-value store, no storage of serialized objects, no NoSQL, no flat files.
edit 2: To make this more clear I give details about types in my example:
class Person
{
public string First {get; set;}
public string Last {get; set;}
}
IEnumerable<Person> items = ...
This means result is some IEnumerable<TypeWithoutName>. The compiler is the only one known the name TypeWithoutName but I can use it in a type-safe way, e.g. via LINQ. And I'm quite sure this type could be inspected by reflection.
As mentioned in comments: I'm looking for an ORM that takes the anonymous type in result and builds some create table statement with two nvarchar columns and one integer column (and corresponding inserts).
As I couldn't find an existing solution to my question, I hacked some code:
internal static void StoreEntitiesToDatabase<T>(this IEnumerable<T> elements, SqlConnection connection,
string tablename)
{
var sbc = new SqlBulkCopy(connection);
{
var table = new DataTable();
Type listType = typeof (T);
foreach (PropertyInfo propertyInfo in listType.GetProperties())
{
table.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
sbc.ColumnMappings.Add(propertyInfo.Name, propertyInfo.Name);
}
foreach (T value in elements)
{
DataRow dr = table.NewRow();
foreach (PropertyInfo propertyInfo in listType.GetProperties())
{
dr[propertyInfo.Name] = propertyInfo.GetValue(value, null);
}
table.Rows.Add(dr);
}
string sqlsc = "CREATE TABLE " + tablename + "(";
for (int i = 0; i < table.Columns.Count; i++)
{
sqlsc += "[" + table.Columns[i].ColumnName + "] ";
int maxlen = table.Columns[i].MaxLength;
if (maxlen == -1) maxlen = 255;
if (table.Columns[i].DataType.ToString().Contains("System.Int32"))
sqlsc += " int ";
else if (table.Columns[i].DataType.ToString().Contains("System.Int64"))
sqlsc += " bigint ";
else if (table.Columns[i].DataType.ToString().Contains("System.DateTime"))
sqlsc += " datetime ";
else if (table.Columns[i].DataType.ToString().Contains("System.String"))
sqlsc += " nvarchar(" + maxlen + ") ";
else if (table.Columns[i].DataType.ToString().Contains("System.Double"))
sqlsc += " float ";
else if (table.Columns[i].DataType.ToString().Contains("System.Decimal"))
sqlsc += " money ";
else
throw new Exception("no mapping for " + table.Columns[i].DataType);
if (table.Columns[i].AutoIncrement)
sqlsc += " IDENTITY(" + table.Columns[i].AutoIncrementSeed.ToString() + "," +
table.Columns[i].AutoIncrementStep.ToString() + ") ";
if (!table.Columns[i].AllowDBNull)
sqlsc += " NOT NULL ";
sqlsc += ",";
}
sqlsc = sqlsc.Substring(0, sqlsc.Length - 1) + ")";
SqlCommand cmd = new SqlCommand(sqlsc, connection);
cmd.ExecuteNonQuery();
sbc.DestinationTableName = tablename;
sbc.WriteToServer(table);
}
}
Can be called like this:
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
elems.StoreEntitiesToDatabase(conn, "myTable");
conn.Close();
}
The code above works just fine for my problem and supports elements of anonymous type.
Sidenode: I first tried to "trick" EF6 using this:
internal class DbQuickInsert<T> : DbContext where T : class
{
public DbSet<T> MyRecords { get; set; }
public DbQuickInsert(string databasename) : base(databasename)
{
}
}
internal static class HelperQuick
{
public static void InsertIntoDatabase<T>(this IEnumerable<T> records, string databasename) where T : class
{
var qi = new DbQuickInsert<T>(databasename);
qi.Configuration.AutoDetectChangesEnabled = false;
qi.BulkInsert(records);
qi.SaveChanges();
}
}
The latter code compiles but raises a runtime error because Entity Frameworkcannot handle anonymous types.
it sounds like you're hoping to use an RDBMs for exactly what it wasn't designed for. Look into a NoSQL solution like MongoDB for storing data like this.
Another potential option if you have to use SQL Server... I guess... would be to create an xml/json representation of your objects and store them in a table. That will make querying quite a challenge however.
A table such as the above described could be considered a key/value pair store, similar to the following:
CREATE TABLE keyValuePairs (
key varchar(200) not null primary key ,
value xml
)
or
CREATE TABLE keyValuePairs (
key varchar(200) not null primary key ,
value varchar(max)
)
In the first you could store your objects as xml, the second as json (or technically xml as well). You would need to query your table based on the appropriate key, or do some really fancy query work - assuming your value's "schema" can differ dependent on object type being stored.

How to find the table i want in an sql server using entity framework

I am using a method which its purpose it to read from tables. The method has an input parameter, a string which will hold the name of the table I will pass and I want to read from. There are multiple tables. My code so far:
public List<dataTable> GetData(string name)
{
TableEntities db = new TableEntities();
db.Database.Connection.Open();
foreach (var readDb in db.SOMETHING_HERE) //here it should find the table which is equal to the table I'm passing as string name
{
dataTable data = new dataTable();
data.name = readDb.name;
There are many option at db. but I don't know which fits my needs.
It's not pretty, but you can try this:
using (TableEntities db = new TableEntities())
{
var type = Type.GetType("namespace." + tableName);
var query = db.Database.SqlQuery(type, "SELECT * FROM " + tableName);
foreach (var row in query)
{
PropertyInfo prop = type.GetProperty("NAME");
string name = (string)prop.GetValue(row);
}
}
I think you need to use reflection to achieve that: MSDN Reflection reference
You may want to try something along these lines: (assuming sample tables Employees and Departments)
public List<dataTable> GetData(string name)
{
using(TableEntities db = new TableEntities())
{
if(new Employees().GetType().ToString().Equals(name))
//Do query
}
}

Get Columns of a Table by GetSchema() method

I want to get list of columns of a table using GetSchema method in ADO.Net, my code is:
var dtCols = con.GetSchema("Columns", new[] { "DBName", "TableName" });
And i get an empty DataTable, what is the problem?
You must specify a parameter for the "owner" restriction.
var dtCols = con.GetSchema("Columns", new[] { "DBName", null, "TableName" });
This is my complete solution.
You just need to provide tableName and connectionString to this method:
// I took HUGE help from this Microsoft website: - AshishK
// https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.getschema?view=netframework-4.7.2#System_Data_SqlClient_SqlConnection_GetSchema_System_String_System_String___
public static List<string> GetAllColumnNamesOfATable(string tableName, string connectionString)
{
var allColumnNames = new List<string>();
using (var connection = new SqlConnection(connectionString))
{
// Connect to the database then retrieve the schema information.
connection.Open();
// You can specify the Catalog, Schema, Table Name, Column Name to get the specified column(s).
// You can use four restrictions for Column, so you should create a 4 members array.
String[] columnRestrictions = new String[4];
// For the array, 0-member represents Catalog; 1-member represents Schema;
// 2-member represents Table Name; 3-member represents Column Name.
// Now we specify the Table_Name and Column_Name of the columns what we want to get schema information.
columnRestrictions[2] = tableName;
DataTable allColumnsSchemaTable = connection.GetSchema("Columns", columnRestrictions);
foreach (DataRow row in allColumnsSchemaTable.Rows)
{
var columnName = row.Field<string>("COLUMN_NAME");
//You can capture other fields as well, like so:
//var dataType = row.Field<string>("DATA_TYPE");
//var characterMaxLength = row.Field<int?>("CHARACTER_MAXIMUM_LENGTH");
allColumnNames.Add(columnName);
}
connection.Close();
}
return allColumnNames;
}
PS: If you'd like to capture other information about the columns this way, the following fields are also available:
Could both of these answers be generalized a bit with:
dtCols = con.GetSchema("Columns", new[] {con.DataSource, null, "TableName"});
This is assuming that "TableName" is the name of the table that you want the schema for.
I had a similar problem, the following worked..
using(SqlCommand command = new SqlCommand(sqlText, con)) {
var sqlReader = command.ExecuteReader();
var a = sqlReader.GetColumnSchema();
}

Categories