I have been trying something with LINQ to get all the data from a table customers in a data table. I have used SQL to LINQ class here.
SampleDataContext sdc = new SampleDataContext();
DataTable Cust = new DataTable();
// var query = from c in sdc.customers
// select c;
var query = (IEnumerable<DataRow>)sdc.customers.Select(c => c);
Cust = query.CopyToDataTable();
Console.WriteLine(Cust);
Now when I run this, I get an exception:
Unable to cast object of type 'System.Data.Linq.DataQuery1[BasicLearning.customer]' to type 'System.Collections.Generic.IEnumerable1[System.Data.DataRow]'
Why am I getting this exception?
ANS - from my understanding, which may be wrong, the (IEnumerable<DataRow>) casting in above logic won't work because the result set is not an IEnumerable but IQueryable. But I am not sure if I understand this exception correctly.
So, here I am stuck now, on how to get this code running. I want to use CopyToDataTable() method. But Casting is not working.
It would be great if anyone could help me understand what I am doing wrong here and how to correct it.
as #Svyatoslav Danyliv mention you need to convert object to table. there is an extention which dynamically convert object to datatable.
void Main()
{
DataTable Cust = new DataTable();
//var query = from c in sdc.customers
// select c;
var query = Customers.Select(c => c).ConvertClassToDataTable<Customer>().AsEnumerable();
Cust = query.CopyToDataTable();
Console.WriteLine(Cust);
}
public static class Extensions
{
public static DataTable ConvertClassToDataTable<T>(this IEnumerable<T> list)
{
Type type = typeof(T);
var properties = type.GetProperties();
DataTable dataTable = new DataTable();
foreach (PropertyInfo info in properties)
{
dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
}
foreach (T entity in list)
{
object[] values = new object[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
values[i] = properties[i].GetValue(entity);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
}
I'm currently writing a server application for a chat app which receives the phone contact list from an users adressbook as a List<string> where the string is a standardized phone number. I now want to compare it to my SQL Database where all registered users are saved.
Right now I iterate trough every item on the list and query
SELECT * FROM Users WHERE Number = #0 and open on every query a new connection with SqlConnection(connectionString). I do so because I know that .NET will handle/pool the open connections.
var phoneNumbers = new List<string> { "0049123456789", "001123456789" };
var registeredUsers = new List<string>();
foreach (var phoneNumber in phoneNumbers)
{
try
{
var userName = GetFrom(phoneNumber);
registeredUsers.Add(userName);
}catch{}
}
//...
public static string GetFrom(string telephoneNumber)
{
string retVal = null;
using (var connection = new SqlConnection(connectionString))
{
using (var cmd = SetSQLCommand(connection, "SELECT * FROM Users WHERE Number = #0", telephoneNumber))
{
connection.Open();
using (var reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())//Get last
{
retVal = (string)reader["Name"];
}
}
else
{
throw (new KeyNotFoundException());
}
}
}
}
return retVal;
}
This method feels quite stupid as an user contact list could contain 1000 or more contacts.
Is there a more elegant way to find all matching telephone numbers from a List of telephone numbers I have as an C# object in an SQL table?
As performance is quite important to me I use the .NET 4.5 implementation of SQL System.Data.SqlClient, the Entity Framework is too slow for my purposes.
Here's my take when I face similar case like this :
I will concatenate all the phone numbers into one single formatted string
I will pass that string in a dynamic SQL query by utilizing the "IN" T-SQL
It could be as simple as this :
public List<string> ReturnContactNames(string telephoneNumbers)
{
var retVal = new List<string>();
using (var connection = new SqlConnection(connectionString))
{
using (var cmd = new SqlCommand("SELECT * FROM Customers WHERE Phone IN (" + telephoneNumbers + ")", connection))
{
connection.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
retVal.Add((string)reader["ContactName"]);
}
}
}
}
return retVal;
}
And then this is how you may use this method :
var phoneNumbers = "'030-0074321','0621-08460'";
var list = ReturnContactNames(phoneNumbers);
Warning : use parameterized query whenever possible. This is just a quick sample only to give you an idea.
Hope it helps.
I should a DB Handler class develop. I want a method like dynamic Select, Update, Delete...
This is my code;
using (var db = new SQLiteConnection(this.dbPath))
{
lst = db.Table<tabCOProzessRow>()).ToList();
}
But I wanna like this; (with where condition)
using (var db = new SQLiteConnection(this.dbPath))
{
lst = db.Table<***DYNAMIC***>()).ToList();
}
Is it possible?
Thank you...
I could make a dynamic query with this code;
public List<object> getTable(string TableName)
{
object[] obj = new object[] { };
TableMapping map = new TableMapping(Type.GetType(TableName));
string query = "select * from " + TableName;
return db.Query(map, "query", obj).ToList();
}
We can be more dynamically with buildQuery method.
While retrieving the results using stored procedure how can I retrieve and store multiple result set in view model in .net core
For e.g. from stored procedure I am returning records for below two queries
Select * LMS_Survey
Select * from LMS_SurveyQuestion
Select * from LMS_SurveyQuestionOptionChoice
and below is view model for two tables
public class LMS_SurveyTraineeViewModel
{
public LMS_SurveyDetailsViewModel SurveyDetailsViewModel { get; set; }
public LMS_SurveyQuestionsViewModel SurveyQuestionsViewModel { get; set; }
public LMS_SurveyQuestionOptionChoiceViewModel SurveyQuestionOptionChoiceViewModel { get; set; }
}
This is how I am executing the stored procedure
public List<LMS_SurveyTraineeViewModel> GetTraineeSurvey(int surveyID)
{
try
{
List<LMS_SurveyTraineeViewModel> modelList = new List<LMS_SurveyTraineeViewModel>();
modelList = dbcontext.Set<LMS_SurveyTraineeViewModel>().FromSql("LMSSP_GetTraineeSurvey #surveyID = {0},#LanguageID = {1}", surveyID, AppTenant.SelectedLanguageID).ToList();
return modelList;
}
catch (Exception ex)
{
throw ex;
}
}
How can stored the multiple result set using stored procedure in view model ?
Currently, EF Core doesn't not support this. There is an open issue to address this.
https://github.com/aspnet/EntityFramework/issues/8127
Update 12th Sep 2018: This is still not a priority for EF Core even for release 3.0; so best use Dapper or plain ADO.NET when you have multiple results scenario
Update 25th Jun 2020: still on the backlog for EF Core even for release 5.0; so best use Dapper or plain ADO.NET when you have multiple results scenario
Update 7th Feb 2021: still on the backlog for EF Core
Update 8th Aug 2022: still on the backlog for EF Core, looks like its not a high priority use-case. Recommend to follow alternatives like using straight ADO.NET or Dapr or the below workaround for this
In the interim an alternative solution can be achieved via extension method(s)
public static async Task<IList<IList>> MultiResultSetsFromSql(this DbContext dbContext, ICollection<Type> resultSetMappingTypes, string sql, params object[] parameters)
{
var resultSets = new List<IList>();
var connection = dbContext.Database.GetDbConnection();
var parameterGenerator = dbContext.GetService<IParameterNameGeneratorFactory>()
.Create();
var commandBuilder = dbContext.GetService<IRelationalCommandBuilderFactory>()
.Create();
foreach (var parameter in parameters)
{
var generatedName = parameterGenerator.GenerateNext();
if (parameter is DbParameter dbParameter)
commandBuilder.AddRawParameter(generatedName, dbParameter);
else
commandBuilder.AddParameter(generatedName, generatedName);
}
using var command = connection.CreateCommand();
command.CommandType = CommandType.Text;
command.CommandText = sql;
command.Connection = connection;
for (var i = 0; i < commandBuilder.Parameters.Count; i++)
{
var relationalParameter = commandBuilder.Parameters[i];
relationalParameter.AddDbParameter(command, parameters[i]);
}
var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
if (connection.State == ConnectionState.Closed)
await connection.OpenAsync();
using var reader = await command.ExecuteReaderAsync();
foreach (var pair in resultSetMappingTypes.Select((x, i) => (Index: i, Type: x)))
{
var i = pair.Index;
var resultSetMappingType = pair.Type;
if (i > 0 && !(await reader.NextResultAsync()))
throw new InvalidOperationException(string.Format("No result set at index {0}, unable to map to {1}.", i, resultSetMappingType));
var type = resultSetMappingType;
var entityType = dbContext.GetService<IModel>()
.FindEntityType(type);
if (entityType == null)
throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
var columns = Enumerable.Range(0, reader.FieldCount)
.Select(x => new
{
Index = x,
Name = reader.GetName(x)
})
.ToList();
var relationalValueBufferFactoryFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>();
int discriminatorIdx = -1;
var discriminatorProperty = entityType.GetDiscriminatorProperty();
var entityTypes = entityType.GetDerivedTypesInclusive();
var instanceTypeMapping = entityTypes.Select(et => new
{
EntityType = et,
Properties = et.GetProperties()
.Select(x =>
{
var column = columns.FirstOrDefault(y => string.Equals(y.Name,
x.GetColumnName() ?? x.Name, StringComparison.OrdinalIgnoreCase)) ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));
if (x == discriminatorProperty)
discriminatorIdx = column.Index;
return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
})
.ToArray()
})
.Select(x => new
{
EntityType = x.EntityType,
Properties = x.Properties,
ValueBufferFactory = relationalValueBufferFactoryFactory.Create(x.Properties)
})
.ToDictionary(e => e.EntityType.GetDiscriminatorValue() ?? e.EntityType, e => e)
;
var resultSetValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
while (await reader.ReadAsync())
{
var instanceInfo = discriminatorIdx < 0 ? instanceTypeMapping[entityType] : instanceTypeMapping[reader[discriminatorIdx]];
var valueBuffer = instanceInfo.ValueBufferFactory.Create(reader);
var materializationAction = materializerSource.GetMaterializer(instanceInfo.EntityType);
resultSetValues.Add(materializationAction(new MaterializationContext(valueBuffer, dbContext)));
}
resultSets.Add(resultSetValues);
}
return resultSets;
}
And the extension typed methods
public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet)> MultiResultSetsFromSql<T1, T2>(this DbContext dbContext, string sql, params object[] parameters)
{
var resultSetMappingTypes = new[]
{
typeof(T1), typeof(T2)
};
var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);
return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1]);
}
public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet, IReadOnlyCollection<T3> ThirdResultSet)> MultiResultSetsFromSql<T1, T2, T3>(this DbContext dbContext, string sql, params object[] parameters)
{
var resultSetMappingTypes = new[]
{
typeof(T1), typeof(T2), typeof(T3)
};
var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);
return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1], (IReadOnlyCollection<T3>)resultSets[2]);
}
Currently, EF Core doesn't not support this. see this example for retrieve multiple result sets.
https://github.com/nilendrat/EfCoreMultipleResults/
It works with this tiny change on EF core 5 according to Ricky G answer
change
command.CommandType = CommandType.Text;
to
command.CommandType = CommandType.StoredProcedure;
and as sql parameter value for this extension method type your stored procedure name "dbo.testproc"
example of usage:
var t1 = await _context.MultiResultSetsFromSql(new [] {typeof(proctestprocResult) },"dbo.testproc", sqlParameters);
works for me
my problem is very common, but I have not found any solution.
This is my code:
public async Task<QueryResult> RollbackQuery(ActionLog action)
{
var inputParameters = JsonConvert.DeserializeObject<Parameter[]>(action.Values);
var data = DeserailizeByteArrayToDataSet(action.RollBackData);
using (var structure = PrepareStructure(action.Query, action.Query.DataBase, inputParameters))
{
//_queryPlanner is the implementor for my interface
return await _queryPlanner.RollbackQuery(structure, data);
}
}
I need to load DataTable (from whereever) and replace data to database. This is my Rollback function. This function use a "CommandStructure" where I've incapsulated all SqlClient objects. PrepareStructure initialize all objects
//_dataLayer is an Helper for create System.Data.SqlClient objects
//ex: _dataLayer.CreateCommand(preSelect) => new SqlCommand(preSelect)
private CommandStructure PrepareStructure(string sql, string preSelect, DataBase db, IEnumerable<Parameter> inputParameters)
{
var parameters = inputParameters as IList<Parameter> ?? inputParameters.ToList();
var structure = new CommandStructure(_logger);
structure.Connection = _dataLayer.ConnectToDatabase(db);
structure.SqlCommand = _dataLayer.CreateCommand(sql);
structure.PreSelectCommand = _dataLayer.CreateCommand(preSelect);
structure.QueryParameters = _dataLayer.CreateParemeters(parameters);
structure.WhereParameters = _dataLayer.CreateParemeters(parameters.Where(p => p.IsWhereClause.HasValue && p.IsWhereClause.Value));
structure.CommandBuilder = _dataLayer.CreateCommandBuilder();
structure.DataAdapter = new SqlDataAdapter();
return structure;
}
So, my function uses SqlCommandBuilder and DataAdapter to operate on Database.
PreSelectCommand is like "Select * from Purchase where CustomerId = #id"
The table Purchase has one primaryKey on ID filed
public virtual async Task<QueryResult> RollbackQuery(CommandStructure cmd, DataTable oldData)
{
await cmd.OpenConnectionAsync();
int record = 0;
using (var cmdPre = cmd.PreSelectCommand as SqlCommand)
using (var dataAdapt = new SqlDataAdapter(cmdPre))
using (var cmdBuilder = new SqlCommandBuilder(dataAdapt))
{
dataAdapt.UpdateCommand = cmdBuilder.GetUpdateCommand();
dataAdapt.DeleteCommand = cmdBuilder.GetDeleteCommand();
dataAdapt.InsertCommand = cmdBuilder.GetInsertCommand();
using (var tbl = new DataTable(oldData.TableName))
{
dataAdapt.Fill(tbl);
dataAdapt.FillSchema(tbl, SchemaType.Source);
tbl.Merge(oldData);
foreach (DataRow row in tbl.Rows)
{
row.SetModified();
}
record = dataAdapt.Update(tbl);
}
}
return new QueryResult
{
RecordAffected = record
};
}
I Execute the code and I don't have any errors, but the data are not updated.
variable "record" contain the right number of modified (??) record, but..... on the table nothing
can someone help me?
EDIT 1:
With SQL Profiler I saw that no query is executed on DB. Only select query on .Fill(tbl) command.
EDIT 2:
Now I have made one change:
tbl.Merge(oldData) => tbl.Merge(oldData, true)
so I see perform the expected query but, with reversed parameters.
UPDATE Purchase SET price=123 where id=6 and price=22
instead of
UPDATE Purchase SET price=22 where id=6 and price=123