Inserting with Dapper.Contrib and DynamicParameters - c#

QUESTION :
Is there a clever way to dynamically add parameters to an object and insert them in to a SQL DB?
preferably using Dapper
I'm writing a WPF app where (amongest other things) I'm collecting questionnaire data. I'm using Dapper to map c# objects to SQL statements.
My problem is that I don't want to hard code all of the column names as object properties since I have a lot of them!
So I wanted to use Dapper's DynamicParameters to dynamically generate objects that Dapper can insert into the database by using the Dapper.Contrib Insert method.
I made an abstract class like this:
public abstract class IDbRecord : DynamicParameters
{
[Key]
public string H4Id { get; set; }
}
and added parameters in another method using the DynamicParameters.Add method.
When my parameters have been added to my IDbRecord derived object I try to insert it.
This results is the Insert medthod trying to insert the public properties of DynamicParameters and not the content of the private parameters Dictonary. Which makes sense when looking at the Readme of Dapper.Contrib. I was just hoping that they had implemented Insert() to grab the parameters Dictonary when the object was derived from DynamicParameters.

I enden up iterating through my objects and building a SQL INSERT statement with a StringBuilder. Not very elegant. But it works
if (!hasData)
{
var parameterList = new StringBuilder(null);
var valuesList = new StringBuilder(null);
var insertSql = new StringBuilder(null);
parameterList.AppendFormat("Id, ");
valuesList.Append(Id + ", ");
foreach (var questionBase in answerList)
{
if (string.IsNullOrEmpty(questionBase.VariableName))
{
throw new ArgumentException("Question " + questionBase.QuestionNumber +
" does not have a VariableName");
}
if (!string.IsNullOrEmpty(questionBase.VariableName) && questionBase.Answer != null)
{
// insert keys (variable names)
parameterList.AppendFormat("{0}", questionBase.VariableName);
if (questionBase.QuestionNumber != answerList.Last().QuestionNumber)
{
parameterList.Append(", ");
}
// insert values
valuesList.AppendFormat("{0}", questionBase.Answer);
if (questionBase.VariableName != answerList.Last().VariableName)
valuesList.Append(", ");
}
}
try
{
insertSql.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", tableName, parameterList, valuesList);
Connect(ConnectionHelper.DevConnString,
c => c.Execute(insertSql.ToString()));
return true;
}
catch (Exception e)
{
return false;
}
}

Related

Generating SQL insert statement using classes

I have a query regarding generating SQL insert statement using c# classes.
So I have a class called students.
There is a function which gets list of students and dump that in database.
Student Model
public class Student
{
public string ID { get; set; } = ""; // DB column name is studentID
public string Name { get; set; } = ""; // DB column name is studentName
public string address { get; set; } // DB column name is studentAddress
}
Function to dump Data
public async Task<Error> DumpStudentAsync()
{
List<Student> students = new List<Student>();
StringBuilder query = new StringBuilder();
query = query.Append("INSERT INTO Student(studentID,studentName,studentAddress) VALUES");
string columnList = "(#studentID{0},#studentName{0},#studentAddress{0})";
for (int i = 0; i < students.Count; i++)
{
query.AppendFormat($"{columnList},", i);
}
query = query.Replace(',', ';', query.Length - 1, 1);
SqlCommand cmd = new SqlCommand
{
CommandText = query.ToString(),
};
for (int i = 0; i < students.Count; i++)
{
cmd.Parameters.AddWithValue($"#studentID{i}", students[i].ID);
cmd.Parameters.AddWithValue($"#studentName{i}", students[i].Name);
cmd.Parameters.AddWithValue($"#studentAddress{i}", students[i].address);
}
SQLWrapper db = new SQLWrapper(_ctx, "", DSConfig.SQLConnectionKey);
return await db.ExecuteStatementAsync(cmd, "");
}
So here I want to make this function generic in such a way that if I add a new field in my student object there should be no code change done in my function.
I tried searching the answers but I didn't get anything.
Here firstly I'm appending the insert format in query where I have hard-coded.
Secondly I'm appending variables for command parameters on the basis of students count which is also hard-coded.
Also the class properties and database columns names are different how can I make use of class properties as DB column names?
Can I do something like this ?
StringBuilder studentQuery = new StringBuilder();
string columns = "";
// Add column list on the basis of students properties
foreach (var property in Student.Properties)
{
columns += "property.ID,"; // It should be studentID
}
// Add variables on the basis of students count
// Add command parameters value on the basis of students count
FYI: I'm using ADO.NET code to perform DB activities not Entity framework.
This kind of automatic is not easily done. .NET is strongly typed. Unless you go into stuff like reflection or dynamic code to do it. And I would not advise it. Strong Typisation is your friend. Without it you end up in the JavaScript and PHP examples for this comic. Hint: JS does the wrong thing in both cases.
For me at least, having to do some minor changes on the frontend for changes on the Database is acceptable work. If anything that is the smalest, least dangerous part of the whole process. So I can only advise against trying this.
However for databases and only databases, stuff like Entity Framework might be a good idea. It can generate your classes from the Database.

C# Entity Framework and stored procedures

I made my database with its stored procedures then attached it with my project Entity Framework database-first.
This function to insert a company info and return its id back and insert it to another table in relation with company table
public string InsertCompany(company company, out int index)
{
try
{
using (vendors_managerEntities db = new vendors_managerEntities())
{
db.companies.Add(company);
db.SaveChanges();
index = company.id_company;
return $"{company.name_company} Is Saved";
}
}
catch (Exception ex)
{
index = 0;
return ex.Message;
}
}
But when I tried to my stored procedure which has been created in database, I couldn't return any value the id always be 0
public string InsertCompany(company company, out int index)
{
try
{
using (vendors_managerEntities db = new vendors_managerEntities())
{
db.SP_insert_companies(company.name_company, company.website_company, company.adress_company, company.county_company, company.decription_company);
index = company.id_company;
return $"{company.name_company} Is Saved";
}
}
catch (Exception ex)
{
index = 0;
return ex.Message;
}
}
I read that I can make it in SQL but I'm looking for a solution in C#, so I opened the stored procedure definition in C# and found the following code and was thinking if can I change its return value because it's not return the id value
public virtual int SP_insert_companies(string name, string website, string address, string country, string description)
{
var nameParameter = name != null ?
new ObjectParameter("name", name) :
new ObjectParameter("name", typeof(string));
var websiteParameter = website != null ?
new ObjectParameter("website", website) :
new ObjectParameter("website", typeof(string));
var addressParameter = address != null ?
new ObjectParameter("address", address) :
new ObjectParameter("address", typeof(string));
var countryParameter = country != null ?
new ObjectParameter("country", country) :
new ObjectParameter("country", typeof(string));
var descriptionParameter = description != null ?
new ObjectParameter("description", description) :
new ObjectParameter("description", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("SP_insert_companies", nameParameter, websiteParameter, addressParameter, countryParameter, descriptionParameter);
}
Please tell me if there's a solution in C# or should I go back to old code without stored procedure in that case?
The issue is that you are using a stored procedure to insert the company entity which I suspect does not cause the object to be refreshed by the context:
db.SP_insert_companies(company.name_company, company.website_company, company.adress_company, company.county_company, company.decription_company);
You then try to get the id from the object which is 0 because it hasn't been refreshed:
index = company.id_company;
If you insist on using a stored procedure, what I would suggest is that you have the SP return the id of the company, then grab it from the call and use that as the value of index:
index = db.SP_insert_companies(company.name_company, company.website_company, company.adress_company, company.county_company, company.decription_company);
Once you modify the SP, make sure to update the definition in your code so it knows to make a function that returns a value.
If you prefer to have it in the object itself, then make sure to update it manually, although I don't recommend this as the object is not in true sync with the database:
index = db.SP_insert_companies(company.name_company, company.website_company, company.adress_company, company.county_company, company.decription_company);
company.id_company = index;
Based on what you're saying it's automatically going to the "Catch" and then the index is already set to 0. So chances are your code elsewhere, not listed is messing up. I suspect wherever your code for saving the company information isn't saving properly. Try manually inputting a company into the DB and then check it with your program. If it returns that it's "Saved" then you know your problem isn't within this method and is a result of your saving method.

C#/Entity Framework: locally add script to model

My team have read-access to a database maintained by a different team. We have a number of scripts that only we run, and so they've never been added as sprocs to the database (nor do we want them to be).
In Entity Framework 6, is it possible to include a file in my model which contains a stored procedure, so that we can leverage the code generation in EF?
We'd much rather have our code look like:
using (var db = new DatabaseEntities())
{
var properlyTypedResult = db.GetEntriesThatStartWith(firstName);
}
than:
using (var db = new DatabaseEntities())
{
var rawResult = db.Database.ExecuteSqlCommand("SELECT * FROM dbo.Person WHERE FirstName LIKE '#p0%'", firstName);
var properlyTypedResult = CastAppropriately(rawResult);
}
The functionality appears to be missing, but I thought I'd check regardless, I'd expect it to be in the designer view,
right-click, Add New -> Function Import...
... but this only allows adding sprocs to the model that are already in the database.
I think you're forgetting about LINQ - the second example would be something like...
List<People> people = (List<People>)db.Person.Where(f => f.FirstName.StartsWith(firstname)).ToList();
This should be close to what you're looking for. Linq is your friend.
I couldn't find exactly what I was after. I decided to simply write my own code generation, and leverage as much of Entity Framework as I could.
With query string in hand, execute against the database appropriately, using a SqlDataAdapter, with a DataTable
e.g.,
using (var context = new DbContext())
{
var dataTable = new DataTable();
var connection = (SqlConnection)context.Database.Connection;
if (connection != null && connection.State == ConnectionState.Closed)
connection.Open();
using (var adapter = new SqlDataAdapter(queryString, connection))
adapter.Fill(dataTable);
}
The DataTable contains the resulting column names along with all their types, now all we have to do is generate the code for the object.
i.e.,
var objectBuilder = new StringBuilder();
objectBuilder.AppendLine("public class QueryResult");
objectBuilder.AppendLine("{");
foreach (DataColumn column in dataTable.Columns)
{
objectBuilder.AppendLine(String.Format("public {0} {1} { get; set; }", column.DataType.Name, column.ColumnName));
}
objectBuilder.AppendLine("}");
Finally, create an extension method on the context object:
i.e.,
private static string GetQueryString(string firstName)
{
return String.Format($"SELECT * FROM dbo.Person WHERE FirstName LIKE '{firstName}%'", firstName);
}
public static partial class DbContextExtensions
{
public static List<QueryResult> GetEntriesThatStartWith(this DbContext context, string firstName)
{
return context.Database.SqlQuery<QueryResult>(GetQueryString(firstName)).ToList();
}
}
Now, we can use this as a regular sproc call:
using (var db = new DbContext())
{
var properlyTypedResult = db.GetEntriesThatStartWith(firstName);
}

How can I map custom sql queries to CSObjects in cool storage?

I want to use complicated sql queries to get lists of objects while using Vici.CoolStorage. Itself it provides a specific query syntax, but it is not powerful enough for my needs. On the website there is an example of how to map a custom query result to a custom object but what I want is to get a list (CSList) of CSObject descendants, same as I get with CSObject.List () method.
This is possible as CSObjects are mapped to tables. (Being abstract classes and using reflection etc...)
If you want to be able to read related objects, you can define properties that lazy load these records. E.g.:
public class MyCustomQueryResult
{
public int SomeId;
public string SomeStringField;
public int SomeScalar;
public CSList<MappedObject> MappedObjects
{
get { return MappedObject.List("SomeId = #SomeId", "#SomeId", SomeId); }
}
}
And then you could use it like this:
string sqlQuery = "SELECT a.SomeId, b.SomeString, COUNT(*) AS SomeScalar"
+ "FROM tblA a"
+ "INNER JOIN tblB b ON a.SoneId = b.SomeId"
+ "GROUP BY a.SomeId, b.SomeString"
+ "WHERE b.SomeField = #SomeParameter";
MyCustomQueryResult[] entries = CSDatabase.RunQuery<MyCustomQueryResult>(sqlQuery, new {SomeParameter:"123456"});
foreach (MyCustomQueryResult entry in entries)
{
foreach (MappedObject mappedObject in entry.MappedObjects)
{
DoSomethingUseful(mappedObject);
}
}

Using SqlCommand , how to add multiple parameters to its object , insertion via winform in sql table

I have ten textboxes in my winform, and i need to save the text typed in these textboxes into 10 columns of a sql database table. so, for this shall i write :
INSERT INTO item (c1,c2,c3...,c10) values (#a,#b....#j)
cmd.Parameters.Add("#a",SqlDbType.Varchar)
cmd.Parameteres["#a"].Value=textbox1.Text;
cmd.Parameters.Add("#b",SqlDbType.Varchar)
cmd.Parameteres["#b"].Value=textbox2.Text;.
.
.
.
.
cmd.Parameters.Add("#j",SqlDbType.Varchar)
cmd.Parameteres["#j"].Value=textbox10.Text;
OR ten separate queries for each textbox:
INSERT INTO item (c1) values (#a)
cmd.Parameters.Add("#a",SqlDbType.Varchar)
cmd.Parameteres["#a"].Value=textbox1.Text;
INSERT INTO item (c2) values (#b)
cmd.Parameters.Add("#b",SqlDbType.Varchar)
cmd.Parameteres["#b"].Value=textbox2.Text;.
.
.
INSERT INTO item (c10) values (#j)
cmd.Parameters.Add("#j",SqlDbType.Varchar)
cmd.Parameteres["#j"].Value=textbox10.Text;
or, please suggest an efficient code.
How to add multiple parameters to cmd in a single statement? is it possible?
You can use an extension method, like this:
public static class DbCommandExtensions
{
public static void AddInputParameters<T>(this IDbCommand cmd,
T parameters) where T : class
{
foreach (var prop in parameters.GetType().GetProperties())
{
object val = prop.GetValue(parameters, null);
var p = cmd.CreateParameter();
p.ParameterName = prop.Name;
p.Value = val ?? DBNull.Value;
cmd.Parameters.Add(p);
}
}
}
Then call it like this:
cmd.AddInputParameters(new { a = textBox1.Text, b = TextBox2.Text, /* etc */ });
I've used that in a few projects with no problems.
I think you can use Parameters.AddWithValue() method.
cmd.Parameters.AddWithValue("#j",textbox10.Text);
cmd.Parameters.AddWithValue("#k",textbox11.Text);
cmd.Parameters.AddWithValue("#l",textbox12.Text);
The 2 'solutions' that you suggest in your question, are semantically different.
Which one you should use, depends on your table-layout.
The first solution inserts one record in the table, the second insert statement inserts one record (row) for every value (textbox).
Difficult to give a good answer here, since we do not know what you're going to save in that table, and thus , we cannot say how you should save it (how you save it, is inherintly dependent on the way you should call the SQL insert statement).
You could use a function like this:
void AddParams(DBCommand cmd,params object[] parameters)
{
if (parameters != null)
{
int index = 0;
while (index < parameters.Length)
{
cmd.Parameters.AddWithValue("#"+(string)parameters[index], parameters[index + 1]);
index += 2;
}
}
}
Not the best one probably, but functional.
Call link this:
AddParams(a,"test1",b,3,c,DateTime.Now);
Or you can use an extension like suggested from #Matt Hamilton to add this function in DBCommand class.

Categories

Resources