I am trying to drop a collection of tables by using tables names in the list and then get the type of each string and drop it :
List<string> models = new List<string> { "WebBrowser", "Notebook", "Members"};
foreach (string mod in models)
{
Type type = Type.GetType(mod));
using (var dbConn = new SQLiteConnection(app.DBPath))
{
dbConn.RunInTransaction(() =>
{
dbConn.DropTable<type>();
//dbConn.DropTable<WebBrowser>();
dbConn.Dispose();
dbConn.Close();
});
}
}
Problem : I can't drop table using this way, DropTable need the name of class (e.g WebBrowser ) and I don't want to drop each table alone (i.e dbConn.DropTable< WebBrowser >();) since I have more than 50 tables to drop.
Error : "The type or namespace name 'type' could not be found". ( and this error is expected since there is no Class 'type' in my Namespace .
You can drop tables using a SQL command in SQLite. All you need to do is iterate over your collection and build a SQL string each time, and execute it
List<string> models = new List<string> { "WebBrowser", "Notebook", "Members"};
foreach (string mod in models)
{
using (var dbConn = new SQLiteConnection(app.DBPath))
{
SQLiteCommand command = new SQLiteCommand(dbConn);
command.CommandText = string.Format("DROP TABLE {0};", mod);
command.ExecuteNonQuery();
}
}
I'm not sure if this syntax is exactly correct for your case (I only ever use sqlite-net in Windows 8.1) but the general approach is sound
You could create your own Extensionmethod like this:
public static class SQLiteConnectionExtensions
{
public static int DropTable(this SQLiteConnection conn, string tableName)
{
var query = string.Format("drop table if exists \"{0}\"", tableName);
return conn.Execute(query);
}
}
And then use it like this:
var tables = new List<string> { "WebBrowser", "Notebook", "Members" };
using (var dbConn = new SQLiteConnection(app.DBPath))
{
dbConn.RunInTransaction(() =>
{
foreach (string table in tables)
{
dbConn.DropTable(table);
}
});
}
You could also use Reflections. Here are the two extension methods:
public static void DropTable(this SQLiteConnection Connection, Type TableType)
{
typeof(SQLiteConnection).GetMethod("DropTable", Array.Empty<Type>())?.MakeGenericMethod(TableType).Invoke(Connection, null);
}
public static void DropTable(this SQLiteConnection Connection, Type[] AllTableTypes)
{
MethodInfo? Method = typeof(SQLiteConnection).GetMethod("DropTable", Array.Empty<Type>());
if (Method != null)
{
foreach (Type? OneTableType in AllTableTypes)
{
Method.MakeGenericMethod(OneTableType).Invoke(Connection, null);
}
}
}
You can call them on a SQLiteConnection object:
TheSqlLiteConnection.DropTable(typeof(SomeClass));
TheSqlLiteConnection.DropTable(new Type[] { typeof(SomeClass), typeof(SomeOtherClass) });
Related
I need to execute SP, I need to call SP by SP name and get back object and returning value. My problem that my object related to several tables in DB and I cannot use: Context.ExampleTable.FromSQl(...), because it's returning values only from ExampleTable, however I need to get object which having fields from 3 separated tables and also my SP returning value. Someone could help me? I'm using EntityFrameworkCore 2.2.6
i was trying to use Context.ExampleTable.FromSQl(...), but it's not what I need.
----Edited----
SqlParameter returnVal = new SqlParameter("#return", SqlDbType.Int);
returnVal.Direction = ParameterDirection.ReturnValue;
using (var context = new Context())
{
var test = context.Set<RequiredObject>().FromSql("EXEC SP_Name #return", returnVal);
}
EF Core 2.2.6 is out of support.
But you can define a new class that represents your result shape, and add that as a DbSet to your DbContext, and use the new class with FromSQL.
Great question! Unfotunately, it seems that EF Core does not have this possibility.
Tried this:
await db.Database.SqlQueryRaw<SomeComplexModel>("select * from Receivers inner join ... ").ToListAsync()
and this:
public DbSet<SomeComplexModel> Items { get; set; }
[NotMapped] public class SomeComplexModel { ... }
await db.Items.FromSqlRaw("select * from Receivers inner join ... ").ToListAsync();
Both failed with exceptions...
https://learn.microsoft.com/en-us/ef/core/querying/sql-queries
"The SQL query can't contain related data. However, in many cases you can compose on top of the query using the Include operator to return related data (see Including related data)."
What you definitely could do (I believe that's not the best way, may be someone would suggest option better): additionally install in the application some micro-ORM f.e. Dapper, share same db connection string and in this rare case when multiple related entities have to be queried via pure SQL - utilize not EF, but this ORM
Here you have extension methods in dotnet 6 that you can adapt and use.
For scalar results:
Usage:
var sql = "select count(*) from my_custom_view";
var count = await db.ExecuteScalar<int>(sql);
public static class EFExtensions
{
//...
public static async Task<T> ExecuteScalar<T>(this DbContext db, string query)
{
T obj = default;
using (var cmd = db.Database.GetDbConnection().CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = CommandType.Text;
db.Database.OpenConnection();
obj = (T)(await cmd.ExecuteScalarAsync());
}
return obj;
}
//...
}
Entities and complex nested types:
Usage:
// the parameters should be pass in SqlParameter types
var sql = $"exec my_custom_sp '{id}', '{date.ToString("yyyy-MM-dd")}'";
var myEntityFromDB = await db.ExecuteToEntity<MyStruct>(sql);
public static class EFExtensions
{
//...
public static async Task<T> ExecuteToEntity<T>(this DbContext db, string query, DbTransaction trx = null)
{
T obj = default;
using (var cmd = db.Database.GetDbConnection().CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = CommandType.Text;
cmd.Transaction = trx;
db.Database.OpenConnection();
using (var reader = await cmd.ExecuteReaderAsync())
{
if (!reader.Read())
{
throw new InvalidOperationException();
}
var dict = new Dictionary<string, object>();
for (var i = 0; i < reader.FieldCount; ++i)
{
var key = reader.GetName(i);
var value = reader[key];
if (value == DBNull.Value)
{
continue;
}
dict.Add(key, value);
}
obj = Tools.CloneJson<Dictionary<string, object>, T>(dict);
}
}
return obj;
}
//...
}
CloneJson method:
//...
using System.Text.Json;
using System.Text.Json.Serialization;
//...
public static class Tools
{
//...
public static JsonSerializerOptions jsonSettings = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = false,
AllowTrailingCommas = true
};
public static TOut CloneJson<TIn, TOut>(TIn source)
{
if (Object.ReferenceEquals(source, null))
return default(TOut);
return JDeserialize<TOut>(JSerialize(source));
}
//...
}
I want to dynamically generate a class based on the results from a query that user submits. For instance, if the user enters Select name, age from tbl, the result is a name column which is string and age which is an int. The resulting class should be:
public class Test
{
public string Name { get; set; }
public int Age { get; set; }
}
Is there an efficient way to do this via EntityFramework or features in C# or I have to use maybe reflection to create a new type and instantiate it.
PS: My purpose is to run this query on the database and show the results in a Grid to the user and run some filter/sort/etc. on it.
You could use TypeBuilder to create a new type and execute the query against database using EF's SqlQuery() as mentioned here.
OR
A cleaner method would be to use dynamic objects to bind the grid. Extend EF to return a collection of dynamic objects as suggested by ChristineBoersen here. The code was written before EF went to RTM. Here's a version that works:
public static class EFExtensions
{
public static IEnumerable<dynamic> CollectionFromSql(this DbContext dbContext, string Sql, Dictionary<string, object> Parameters)
{
using (var cmd = dbContext.Database.Connection.CreateCommand())
{
cmd.CommandText = Sql;
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
foreach (KeyValuePair<string, object> param in Parameters)
{
DbParameter dbParameter = cmd.CreateParameter();
dbParameter.ParameterName = param.Key;
dbParameter.Value = param.Value;
cmd.Parameters.Add(dbParameter);
}
//var retObject = new List<dynamic>();
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var dataRow = GetDataRow(dataReader);
yield return dataRow;
}
}
}
}
private static dynamic GetDataRow(DbDataReader dataReader)
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var fieldCount = 0; fieldCount < dataReader.FieldCount; fieldCount++)
dataRow.Add(dataReader.GetName(fieldCount), dataReader[fieldCount]);
return dataRow;
}
}
You could invoke the above method as follows:
var results = context.CollectionFromSql("Select Name, Age from tbl", new Dictionary<string, object>()).ToList();
// Bind results to grid
I am trying the below way to return the dynamic results using dapper and stored procedure. Am I doing it in correct way?
using (IDbConnection dbConnection = Connection)
{
dbConnection.Open();
var result = dbConnection.Query<dynamic>("LMSSP_GetSelectedTableData",
new
{
TableName = TableName,
LangaugeID = AppTenant.SelectedLanguageID,
UserID = AppTenant.UserID
}, commandType: CommandType.StoredProcedure).ToList();
if (result != null)
{
// Added just for checking the data
foreach (var item in (IDictionary<string, object>)result.FirstOrDefault())
{
string key = item.Key;
string value = item.Value.ToString();
}
}
}
What my stored procedure do is, I will pass any table name and based on that it will return the results/records.So, obviously my number of records, columns will be varied as per the table name passed.
To achieve this I have used dynamic keyword along with dapper.
So my question is how can I pass this data to view as a model and render the controls on the view as per the properties/column data type. Can I get the data type of column OR PropertyInfo?
But, when dapper retrieves the records from database it returns as dapper row type?
Using same SP to fetch data from different table would be confusing (not good design). However to solve your problem technically, you can create model having list of control information. Example of control information
public class ControlInformation
{
public string Name { get; set; }
public dynamic Value { get; set; }
public string ControlType { get; set; }
// Applicable for drop down or multi select
public string AllValues { get; set; }
}
Model will have list of ControlInformations
public List<ControlInformation> ControlInformations { get; set; }
View will render the controls (partial view based on control type) Ex: very basic case to render different view for int and another view for rest. I have 2 partial views "IntCtrl" and "StringCtrl".
#foreach (var item in Model.ControlInformations)
{
if (#item.ControlType == "System.Int32")
{
Html.RenderPartial("IntCtrl", item);
}
else
{
Html.RenderPartial("StringCtrl", item);
}
}
Hope this help.
Here we are calling method which returns Datatable :
public DataTable GetMTDReport(bool isDepot, int userId)
{
using (IDbConnection _connection = DapperConnection)
{
var parameters = new DynamicParameters();
parameters.Add("#IsDepot", isDepot);
parameters.Add("#userId", userId);
var res = this.ExecuteSP<dynamic>(SPNames.SSP_MTDReport, parameters);
return ToDataTable(res);
}
}
In this we can call stored procedures by calling our custom method "ExecuteSP" :
public virtual IEnumerable<TEntity> ExecuteSP<TEntity>(string spName, object parameters = null)
{
using (IDbConnection _connection = DapperConnection)
{
_connection.Open();
return _connection.Query<TEntity>(spName, parameters, commandTimeout:0 , commandType: CommandType.StoredProcedure);
}
}
and here is "DapperConnection" method to connect the database:
You can give connection string with key ["MainConnection"]
public class DataConnection
{
public IDbConnection DapperConnection
{
get
{
return new SqlConnection(ConfigurationManager.ConnectionStrings["MainConnection"].ToString());
}
}
}
And at last we call "ToDataTable" method to change our response in datatable . We will receive response in DapperRow from the database because we passsed dynamic type in stored procedure.
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
if (items == null) return null;
var data = items.ToArray();
if (data.Length == 0) return null;
var dt = new DataTable();
foreach (var pair in ((IDictionary<string, object>)data[0]))
{
dt.Columns.Add(pair.Key, (pair.Value ?? string.Empty).GetType());
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}
I am trying to populate Autocomplete text from SQL Server in textbox using TextChanged() event. [WindowsForm, C#]
I have a partial class Form
TextChanged Event:
private void textBoxFilterCName_TextChanged(object sender, EventArgs e)
{
DataTable dt = FillCustomerName(textBoxFilterCName.Text);
List<string> listNames = CustomerName.ConvertDataTableToList(dt);
textBoxFilterCName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
AutoCompleteStringCollection dataName = new AutoCompleteStringCollection();
dataName.AddRange(listNames.ToArray());
textBoxFilterCName.AutoCompleteCustomSource = dataName;
textBoxFilterCName.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
I am getting this error when I call this method CustomerName.ConvertDataTableToList().
The type arguments for methods 'CustomerName.ConvertDataTableToList(DataTable)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
FillCustomerName() Method
public DataTable FillCustomerName(string cName)
{
DataTable dtName = new DataTable();
List<string> listName = new List<string>();
try
{
dbconnection.Open();
string query = "SELECT [Name] FROM Customers WHERE Is_Active=1 AND Name like #CName + '%'";
using (SqlCommand cmd = new SqlCommand(query, dbconnection))
{
cmd.Parameters.Add("CName", SqlDbType.VarChar).Value = cName;
dtName.Load(cmd.ExecuteReader());
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Connection Error");
}
finally
{
dbconnection.Close();
}
return dtName;
}
CustomerName Class:
public static class CustomerName
{
private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
if (property.PropertyType == typeof(System.DayOfWeek))
{
DayOfWeek day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), row[property.Name].ToString());
property.SetValue(item, day, null);
}
else
{
property.SetValue(item, row[property.Name], null);
}
}
return item;
}
public static List<T> ConvertDataTableToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = typeof(T).GetProperties().ToList();
List<T> result = new List<T>();
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, properties);
result.Add(item);
}
return result;
}
}
The query is executed and results are generated successfully when I debug the application. But I could not load the datatable results as list.
Possible suggestions for this conversion are most welcome.
So you want to convert your DataTable to a list of objects of a certain type. Right?
The generic method infers the generic type T from the parameters only. Since you have no parameters of type T then you need to explicitly specify it. So when you write this line:
List<string> listNames = CustomerName.ConvertDataTableToList(dt);
The Compiler can't infer what that the type T you want is a string because it is only used in the return type. So you need to explicitly specify that:
CustomerName.ConvertDataTableToList<string>(dt);
However, since you specify the condition where T : new(), this means the generic type should have a parameterless constructor, string doesn't satisfy that. You're matching the row by name, so your only option is to modify the generic methods or create a class like this:
public class CustomerNameHolder
{
public string Name { set; get; }
}
Then:
List<string> listNames = CustomerName.ConvertDataTableToList<CustomerNameHolder>(dt)
.Select(x=> x.Name).ToList();
I need a static method to convert DataTables(dynamic) to List(again dynamic Entity)
here is my code
help would be appereciated
public static ICollection<System.Data.Entity.Core.Objects.DataClasses.EntityObject> DtToEntity(DataTable DataTable,System.Data.Entity.Core.Objects.DataClasses.EntityObject EntityObject)
{
ICollection<System.Data.Entity.Core.Objects.DataClasses.EntityObject> _list = null;
System.Data.Entity.Core.Objects.DataClasses.EntityObject _tempClass;
foreach (DataRow dataRow in DataTable.Rows)
{
foreach(DataColumn dataColumn in DataTable.Columns)
{
foreach (var attribute in EntityObject.GetType().GetProperties())
{
if (attribute.Name == dataColumn.ColumnName && attribute.GetType().Equals(dataColumn.GetType()))
{
return _list;
}
}
}
}
private static List<T> ConvertDataTable<T>(DataTable dt)
{
List<T> data = new List<T>();
foreach (DataRow row in dt.Rows)
{
T item = GetItem<T>(row);
data.Add(item);
}
return data;
}
private static T GetItem<T>(DataRow dr)
{
Type temp = typeof(T);
T obj = Activator.CreateInstance<T>();
foreach (DataColumn column in dr.Table.Columns)
{
foreach (PropertyInfo pro in temp.GetProperties())
{
if (pro.Name == column.ColumnName)
pro.SetValue(obj, dr[column.ColumnName], null);
else
continue;
}
}
return obj;
}
Usage:
List< Student > studentDetails = new List<Student>();
studentDetails = ConvertDataTable<Student>(dt);
Source: http://www.c-sharpcorner.com/UploadFile/ee01e6/different-way-to-convert-datatable-to-list/
So basically you want use this structure for your mvc .net core app with dependency injection and ado.net for orm The problem that was first initiated this question was when using ado.net you have to map data table to c# object manually.But in extends of years it got grown and now you can easily implement this structure on your application which is dapper like functionality.I grant this is a small sized.But I think this is the fastest way you can actually get results from sql server.Personally you might use dapper which I also suggest to do.It is nice peace of code which I think deserve to go to wiki for future references
You start with repository which has db context factory injected into it. I used factory pattern for db context because you might want to use multiple instances of sql server on your application. Then factory pattern will be needed. I suggest create a small db for you basic information and make that factory singleton. In this manner a lot of time and effort of fetching data would be eliminated in the first place. In the first method of repo there is example functionality to showcase the design you so you use factory to fetch the result and cast it as object by covert data table function which was provided in previous answer(thank you very much #Nikhil Vartak) .So you have it!. In the further of the post I included convert data table functions to this post and which was main reason of this question. Others are parts of normal .net core or .net normality which is not concern of this post
/* repo */
public class repo
{
private readonly IDBContextFactory dBContextFactory;
public repo(IDBContextFactory _dbContextFactory)
{
_dbContextFactory=dBContextFactory;
}
public string GetLastRecord()
{
List< Student > studentDetails = new List<Student>();
studentDetails = ConvertDataTable<Student>(_dbCtextFactory.Select("mydb","select * from StudentDetail");
/*refrence from this post https://stackoverflow.com/questions/33515552/converting-datatable-to-listentity-projectdracula */;
}
}
/* interface of repo */
public interface IRepo
{
public string GetLastRecord();
}
/* controller */
public class mycontroller:BaseController
{
private readonly IRepo repo;
public mycontroller(IRepo _repo)
{
_repo=repo;
}
[httpGet]
public IActionResult<string> GetLastRecord()
{
return _repo.GetLastRecord();
}
}
/* some factory pattern for db context (multiple dbs) */
public class DBContextFactory
{
private SqlCommand BuildFactory(string dbName)
{
switch(dbName)
{
case 'mydb':
return CreateMyDB();
}
}
private SqlCommand CreateMyDB()
{
string connectionString = "your connection string";
SqlConnection connection =
new SqlConnection(connectionString));
SqlCommand command = new SqlCommand(connection);
return command.Open();
}
//Private SqlCommand GetMyOpenCommand()
public DataTable Select(string dbName,string query)
{
SqlDataAdapter dataAdapter=new SqlDataAdapter();
dataAdapter.SelectCommand=BuildFactory(dbName);
DataTable dt =new DataTable();
dataAdapter.Fill(dt);
con.Close();
return dt;
}
}
/* factory in dependncy pattern */
public inteface IDBContextFactory
{
SqlCommand BuildFactory(string dbName);
SqlCommand CreateMyDB();
DataTable Select(string dbName,string query)
}
/****** HERE IS YOUR GENERIC FILE ******/
private static List<T> ConvertDataTable<T>(DataTable dt)
{
List<T> data = new List<T>();
foreach (DataRow row in dt.Rows)
{
T item = GetItem<T>(row);
data.Add(item);
}
return data;
}
private static T GetItem<T>(DataRow dr)
{
Type temp = typeof(T);
T obj = Activator.CreateInstance<T>();
foreach (DataColumn column in dr.Table.Columns)
{
foreach (PropertyInfo pro in temp.GetProperties())
{
if (pro.Name == column.ColumnName)
pro.SetValue(obj, dr[column.ColumnName], null);
else
continue;
}
}
return obj;
}
/***** END OF GENERIC PART *****/
/* USAGE OF GENERIC */
List< Student > studentDetails = new List<Student>();
studentDetails = ConvertDataTable<Student>(dt);