Initially I had a method in our DL that would take in the object it's updating like so:
internal void UpdateCash(Cash Cash)
{
using (OurCustomDbConnection conn = CreateConnection("UpdateCash"))
{
conn.CommandText = #"update Cash
set captureID = #captureID,
ac_code = #acCode,
captureDate = #captureDate,
errmsg = #errorMessage,
isDebit = #isDebit,
SourceInfoID = #sourceInfoID,
PayPalTransactionInfoID = #payPalTransactionInfoID,
CreditCardTransactionInfoID = #CreditCardTransactionInfoID
where id = #cashID";
conn.AddParam("#captureID", cash.CaptureID);
conn.AddParam("#acCode", cash.ActionCode);
conn.AddParam("#captureDate", cash.CaptureDate);
conn.AddParam("#errorMessage", cash.ErrorMessage);
conn.AddParam("#isDebit", cyberCash.IsDebit);
conn.AddParam("#PayPalTransactionInfoID", cash.PayPalTransactionInfoID);
conn.AddParam("#CreditCardTransactionInfoID", cash.CreditCardTransactionInfoID);
conn.AddParam("#sourceInfoID", cash.SourceInfoID);
conn.AddParam("#cashID", cash.Id);
conn.ExecuteNonQuery();
}
}
My boss felt that creating an object every time just to update one or two fields is overkill. But I had a couple places in code using this. He recommended using just UpdateCash and sending in the ID for CAsh and field I want to update. Well the problem is I have 2 places in code using my original method. And those 2 places are updating 2 completely different fields in the Cash table. Before I was just able to get the existing Cash record and shove it into a Cash object, then update the properties I wanted to be updated in the DB, then send back the cash object to my method above.
I need some advice on what to do here. I have 2 methods and they have the same signature. I'm not quite sure what to rename these because both are updating 2 completely different fields in the Cash table:
internal void UpdateCash(int cashID, int paypalCaptureID)
{
using (OurCustomDbConnection conn = CreateConnection("UpdateCash"))
{
conn.CommandText = #"update Cash
set CaptureID = #paypalCaptureID
where id = #cashID";
conn.AddParam("#captureID", paypalCaptureID);
conn.ExecuteNonQuery();
}
}
internal void UpdateCash(int cashID, int PayPalTransactionInfoID)
{
using (OurCustomDbConnection conn = CreateConnection("UpdateCash"))
{
conn.CommandText = #"update Cash
set PaymentSourceID = #PayPalTransactionInfoID
where id = #cashID";
conn.AddParam("#PayPalTransactionInfoID", PayPalTransactionInfoID);
conn.ExecuteNonQuery();
}
}
So I thought hmm, maybe change the names to these so that they are now unique and somewhat explain what field its updating:
UpdateCashOrderID
UpdateCashTransactionInfoID
ok but that's not really very good names. And I can't go too generic, for example:
UpdateCashTransaction(int cashID, paypalTransactionID)
What if we have different types of transactionIDs that the cash record holds besides just the paypalTransactionInfoID? such as the creditCardInfoID? Then what? Transaction doesn't tell me what kind. And furthermore what if you're updating 2 fields so you have 2 params next to the cashID param:
UpdateCashTransaction(int cashID, paypalTransactionID, someOtherFieldIWantToUpdate)
see my frustration? what's the best way to handle this is my boss doesn't like my first route?
Why not just:
UpdateCashPaymentSource(int cashID, int PayPalTransactionInfoID)
UpdateCashCapture(int cashID, int paypalCaptureID)
My boss felt that creating an object every time just to update one or two fields is overkill.
He would be right, if you have to create the object every time. The correct response to this is that you should already be using these business objects throughout your app. You don't create a new Cash object. You pass it the Cash object you already have to be saved.
"UpdateCashWithCapture" and "UpdateCashWithTransaction"?
UpdateCashByTransactionInfoID
UpdateCashByCaptureID()
?
Would one method and an enum cut it?
internal void UpdateCash(int cashID, int id, FieldName field)
{
using (OurCustomDbConnection conn = CreateConnection("UpdateCash"))
{
conn.CommandText = string.format("update Cash set {0} = #id where id = #cashID", field.ToString());
conn.AddParam("#id", id);
conn.AddParam("#cashId", cashId);
conn.ExecuteNonQuery();
}
}
public enum FieldName
{
PayPalCaptureId,
PayPalTransactionInfoID
}
EDIT:
On now reading your edit, I would agree that your original approach would be the way to go, in my opinion - passing in an object and updating all of the associated fields in a database compared to passing in an object property value and updating that in a database, the biggest performance killer will be opening the database connection, not the number of fields relating to one database record.
How about UpdateCashByCaptureID and UpdateCashByTransactionInfoID?
Add the name of the field being updated, i.e.
internal void UpdateCash_paypalCaptureID(...)
internal void UpdateCash_PayPalTransactionInfoID(...)
You could encapsulate the update query logic in a class:
public abstract class CashUpdateQuery
{
public CashUpdateQuery(int cashId)
{
this.CashId = cashId;
}
protected int CashId { get; private set; }
public abstract void SetConnectionProperties(OurCustomDbConnection conn);
}
Then you can have specific subclasses for each update scenario. So for your PayPal query you'd have something like this:
public PaypalTransactionCashUpdateQuery : CashUpdateQuery
{
private readonly int paypalCaptureId;
public PaypalTransationCashUpdateQuery(int cashId, int paypalCaptureId)
{
this.paypalCaptureId = paypalCaptureId;
}
public override void SetConnectionProperties(OurCustomDbConnection conn)
{
conn.CommandText = #"update Cash
set CaptureID = #paypalCaptureID
where id = #cashID";
conn.AddParam("#captureID", this.paypalCaptureId);
conn.AddParam("#cashID", this.CashId);
}
}
Then your update method can take a query object and use it to set the query properties on the connection and execute it:
internal void UpdateCash(CashUpdateQuery query)
{
using(OurCustomDbConnection conn = CreateConnection("UpdateCash"))
{
query.SetConnectionProperties(conn);
conn.ExecuteNonQuery();
}
}
This means that adding new queries is simply a case of adding a new subclass of CashUpdateQuery.
Related
I got a problem where I can't read the data from my database correctly.
To give you an example, I got the important value that is in the database 6.69879289850025E-06, but I read 6.0 (which is not accurate since it's suppose to be way smaller) in the C# program.
Something similar happen to ImportantValue2 where the value that is in the database is -0,000158976621370616 and in my C# program I get 0,0.
public double ImportantValue1 { get; set; }
public double ImportantValue2 { get; set; }
public string Note { get; set; }
public MyObject(SQLiteDataReader reader)
{
ImportantValue1 = Convert.ToDouble(reader["important_value_1"]); //Value in the database is REAL
ImportantValue2 = Convert.ToDouble(reader["important_value_2"]);//Value in the database is REAL
Note = reader["note"].ToString(); //Value in the database is TEXT
}
Update
And this is how I call it.
using (SQLiteConnection c = new SQLiteConnection(connection))
{
c.Open();
using (SQLiteCommand cmd = new SQLiteCommand(sqlCommand, c))
{
using (SQLiteDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
objectFromBD = new MyObject(reader);
}
}
}
}
And the SQLite code
CREATE TABLE "table"(
"id" INTEGER,
"important_value_1" REAL NOT NULL,
"important_value_2" REAL NOT NULL,
"note" TEXT NOT NULL,
PRIMARY KEY ("id" AUTOINCREMENT)
);
Thank you for your help!
reader["important_value_1"] will return index which column exists in your SQL script instead of your expectation value.
reader["important_value_1"] will get the value of the specified column.
You can try to use reader["important_value_1"] in reader.GetDouble function to read your data from columns.
ImportantValue1 = reader.GetDouble(reader["important_value_1"])
ImportantValue2 = reader.GetDouble(reader["important_value_2"])
SqliteDataReader.Item[] Property
Note
You might need to use reader.Read() let the reader point to read the next row if you want.
while (reader.Read()){
//... read all rows from your db
}
Here is a temporary solution I got, but that is not efficient.
I saw that I can read a string with the reader, but I couldn't do it with a double. So I used reader.GetString(rowValue); and than I converted the string to a double using the fonction below.
public double StringToDoubleWithNotation(string value)
{
return Convert.ToDouble(Decimal.Parse(value, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint));
}
Now everything is fine, but I would prefer to see a better solution since doing 3 conversion is not that efficient.
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.
I'm doing a wpf App and i've got a bit of an issue i would like to ask you about.
I'm querying a database on the window level and i pass the result of the query to a method in my object like this :
Window level code :
payrollEmailManager.SetListOfSalariesToEmailTo(
from Record in SqlInfo.SqlTable.T_SALs
where Record.EtatPaie == 3
select new {
Matricule = Record.MatriculeSalarie,
Nom = Record.Nom,
Prenom = Record.Prenom,
Email = Record.EMail });
This is my Method Definition :
public void SetListOfSalariesToEmailTo(object _ListOfSalaryToRecieveMail)
{
ListOfSalary = _ListOfSalaryToRecieveMail;
}
Where ListOfSalary is also of type object.
Now here is the issue for me, I have another method where I want to go trough each record of listofsalary and get the information I selected in query like Matricule or Email, something like this :
public void SendEmail()
{
foreach(var Salary in (dynamic)ListOfSalary)
{
Mail.To.Add(Salary.????
}
}
I can't reference the Nom column or the Email column any advice ??
If you consider your following query:
var query = from Record in SqlInfo.SqlTable.T_SALs
where Record.EtatPaie == 3
select new {
Matricule = Record.MatriculeSalarie,
Nom = Record.Nom,
Prenom = Record.Prenom,
Email = Record.EMail
};
After running this line the query is not yet executed to database. Only when you materialize it (using functions like ToList()/ToArray()/FirstOrDefault etc.) it is actually being executed in the database and information is returned.
Therefore if you just do SomeFunction(query); it does not execute the query and you can store it for later execution.
However you do need to change your code a bit:
The function should not get object but IQueryable<T>
public void SetListOfSalariesToEmailTo(IQueryable<T> query)
As you want to store the query you need to later on know the type of each item. To do so do not use an anonymous object (new { }) in the select. Use instead a custom object or use c# 7.0 named tuples and then the function will look like:
var query = from Record in SqlInfo.SqlTable.T_SALs
where Record.EtatPaie == 3
select new SomeType {
Matricule = Record.MatriculeSalarie,
Nom = Record.Nom,
Prenom = Record.Prenom,
Email = Record.EMail
};
public void SetListOfSalariesToEmailTo(IQueryable<SomeType> query)
{
ListOfSalary = query;
}
You can still use object and dynamic as you did, and just access the properties, but you will not have the intellisense showing you the properties and options, as it does not know the concrete type.
I'm having an issue when trying to update a value on my database and can't really find much if any help through Google.
I want to set a column called IsOpen (bool but because of SQLite I'm using integer) to 0 (false) if the EndDate for this entry is today (now). When I run my UPDATE query I get the following exception; "Cannot update List1: it has no PK".
I don't understand this because I've checked my Model class and I clearly have a PK set;
[SQLite.AutoIncrement, SQLite.PrimaryKey]
public int GoalID
{
get { return _goalID; }
set
{
if (_goalID != value)
_goalID = value;
OnPropertyChanged("GoalID");
}
}
I'm attempting to update this way;
string sql = #"UPDATE GoalsTrackerModel
SET IsOpen = '0'
WHERE EndDate = datetime('now')"; // I've also tried date('now')
_dbHelper.Update<GoalsTrackerModel>(sql);
My Update<> looks like;
public void Update<T>(string stmt) where T : new()
{
using (var conn = new SQLiteConnection(App.ConnectionString))
{
var result = conn.Query<T>(stmt);
if (result != null)
{
conn.RunInTransaction(() =>
{
conn.Update(result);
});
}
}
}
But like I said, I keep getting "Cannot update List1: it has no PK". What's throwing me off as well is if I change the WHERE to something like; WHERE IsOpen = '1' then it'll update all the values that have 1 to 0, but it'll still give me the "Cannot update List1: it has no PK" message.
Maybe my WHERE is wrong when checking if the EndDate = now? I'm implementing all this as soon as the page is opened. Any ideas?
Thanks.
"[SQLite.AutoIncrement, SQLite.PrimaryKey]" is C# code, not SQL code. Just because you've defined in C# what your primary key is, doesn't mean the SQLite table is really defined that way. You'll need to look at the table itself as it is defined within SQLite to fix that.
My Update method was causing the problem. Changed it and started using SQLiteCommand and ExecuteNonQuery instead of the SQLiteConnection's Update().
In case it helps anyone in the future, here's my new update method;
public void Update<T>(string stmt, string table) where T : new()
{
using (var conn = new SQLiteConnection(App.ConnectionString))
{
var result = conn.Query<T>("SELECT * FROM " + table);
if (result != null)
{
conn.RunInTransaction(() =>
{
SQLiteCommand cmd = conn.CreateCommand(stmt);
cmd.ExecuteNonQuery();
});
}
}
}
I got tired to search so here it goes my first SO question hoping someone had the same problem and can help me
Goal
I am trying to store my application data with a SQLite database
Application description
Windows 8 app C# XAML with local SQLite database using SQLite for Windows Runtime Extension and sqlite-net library
Table definition
public class Product {
private int _id;
[SQLite.PrimaryKey, SQLite.AutoIncrement]
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _date;
public string DATE
{
get { return _date; }
set { _date = value; }
}
private string _desc;
public string DESC
{
get { return _desc; }
set { _desc = value; }
}
}
Problem1
public int Insert (object obj) description says the following:
Inserts the given object and retrieves its auto incremented primary key if it has one.
However everytime I insert a row it return 1. I can sucessfully insert with a auto-incremet ID but somehow it does not return me its ID. Why?
Problem 2
I can insert new rows but not delete them
Working around problem 1 to get last row generated ID, I try to delete rows but with no success.
See this example test that always fails:
using (var db = new SQLiteConnection(Path.Combine(_path, _dbname)))
{
var p1 = new Product() { DESC = "insert1", DATE = DateTime.Now.ToString() };
db.Insert(p1);
p1.ID = 1;
var p2 = new Product() { DESC = "insert2", DATE = DateTime.Now.ToString() };
// I am sure that this row is getting ID 2, so it will not have a wrong ID
p2.ID = 2;
db.Insert(p2);
var p3 = new Product() { DESC = "insert3", DATE = DateTime.Now.ToString() };
db.Insert(p3);
p3.ID = 3;
db.Delete<Product>(p2);
}
As you can see I try to insert 3 rows and delete the second one. The rows are inserted but I get the following SQLite.SQLiteException exception:
unable to close due to unfinalized statements or unfinished backups
Why? I don't open other connections before and after that.
Thanks in advance
Solved
Problem 1
+1 and thanks for #Bridgey for pointing out that function does not match it description and for the relevant search
The function does not return ID as it says but it defines the object ID. So when I insert a new Product, Product.ID will have last inserted ID.
Problem 2
I changed db.Delete<Product>(p2); to db.Delete(p2); and now it works. SQLite-net correctly identify the row as Product. I still don't know why the unable to close due to unfinalized statements or unfinished backups exception was happening. If someone knows why tell me please.
I think for problem 2, the issue is that you are passing the Product object as the parameter for the Delete method. The documentation says: Deletes the object with the specified primary key. I think the following should work:
db.Delete<Product>(p1.ID);
Regarding problem 1, the code of the Insert method of the sqlite-net package ends:
var count = insertCmd.ExecuteNonQuery (vals);
if (map.HasAutoIncPK) {
var id = SQLite3.LastInsertRowid (Handle);
map.SetAutoIncPK (obj, id);
}
return count;
As you can see, count is returned, even if id is set.
EDIT: Actually, according to the author this is deliberate.
"Insert returns the number of rows modified. The auto incremented columns are stored in the object. Please see the doc comments."
https://github.com/praeclarum/sqlite-net/issues/37