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
Related
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.
I'm very new to Xamarin and C# and trying to insert some sample data to my simple recipe database for testing purpourses.
However, when inserting a new recipe, the SQLiteConnection.Insert(data) does not return the ID of the inserted record as it should (see here), but instead returns 1 every time.
My insert data method:
public static int insertUpdate(Object data) {
string path = DB.pathToDatabase ();
DB.createDatabase ();
try {
var db = new SQLiteConnection(path);
int inserted = db.Insert(data);
if (inserted != 0)
inserted = db.Update(data);
return inserted;
}
catch (SQLiteException ex) {
return -1;
}
}
Inserting the sample data:
int stand = insertUpdate (new Recipe {
Name = "Standard",
Author = "Aeropress Nerd",
Description = "The perfect recipe for your first cup",
Type = "Default"
});
insertUpdate (new Step { Action = "Pour", Duration = 10, Recipe = stand });
insertUpdate (new Step { Action = "Stir", Duration = 20, Recipe = stand });
insertUpdate (new Step { Action = "Steep", Duration = 15, Recipe = stand });
int inv = insertUpdate (new Recipe {
Name = "Inverted",
Author = "Aeropress Nerd",
Description = "A more advanced brew using the inverted method.",
Type = "Default"
});
I cannot figure out what I am doing wrong. Thanks in advance for any help and sorry for the (probably) stupid question.
However, when inserting a new recipe, the SQLiteConnection.Insert(data) does not return the ID of the inserted record as it should (see here), but instead returns 1 every time.
I am pretty sure you downloaded some nuget package or component to include SQLite functionality to your Xamarin.Android App and it may be slightly different from the native implementation. Then, you should refer to the specific documentation for whatever it is that you're using on Xamarin.
My wild guess is that you are using this component. If I'm wrong, please comment to correct my answer. If I'm right, you should try this:
The object you want to insert
class Row
{
public int Id { get; set; }
}
class Recipe : Row
{
//Recipe's properties
}
class Step : Row
{
//Step's properties
}
The insertUpdate method definition
public static int insertUpdate(Row data) {
string path = DB.pathToDatabase ();
DB.createDatabase ();
try {
var db = new SQLiteConnection(path);
int inserted = db.Insert(data); //will be 1 if successful
if (inserted > 0)
return data.Id; //Acording to the documentation for the SQLIte component, the Insert method updates the id by reference
return inserted;
}
catch (SQLiteException ex) {
return -1;
}
}
The insertUpdate method usage
//As Step and Recipe both are derived classed from Row, you should be able to use insertUpdate indistinctively and without casting
insertUpdate (new Step { Action = "Steep", Duration = 15, Recipe = stand });
int inv = insertUpdate (new Recipe {
Name = "Inverted",
Author = "Aeropress Nerd",
Description = "A more advanced brew using the inverted method.",
Type = "Default"
});
why after inserting data to your db, you starting to update it by the same object right away. This code most likely redundant.
if (inserted != 0)
inserted = db.Update(data);
return inserted;
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 am trying to write a program to scan a directory containing tv show folders, look up some details about the shows using tvrage API and then save the details to a database using entity framework.
My TVShow table pkey is the same value as taken from the tvrage database show id, and I am having issues when duplicate or similar folder names are returning the same Show info. In a situation where I have a directory containing three folders, "Alias", "Alias 1" , "Band of Brothers" I get the following output from my code
* TV SHOWS *
Alias....... NO MATCH......ADDING........DONE
Alias 1 ...... NO MATCH.....ADDING....CANT ADD, ID ALREADY EXISTS IN DB
Band of Brothers ...... NO MATCH..ADDING....
Before getting an UpdateException on the context.SaveChanges(); line
Violation of PRIMARY KEY constraint 'PK_TVShows'.
I can see using SQL profiler that the problem is that my app is trying to perform an insert on the alias show for a second time with duplicate key, but I can't see why. When I step through the code on the second interaction of the foreach loop (second "alias" folder), the code to save the show entity to the database is bypassed.
It is only on the next iteration of the foreach loop when I have created a new TVShow entity for "Band of Brothers" do I
actually reach the code which adds a Tvshow to context and saves, at which point the app crashes. In visual studio I can see
at the point of the crash that;
"show" entity in context.TVShows.AddObject(show) is "Band of Brothers" w/ a unique ID
context.TVShows only contains one record, the first Alias Entity
But SQL profiler shows that EntityFramework is instead inserting Alias for a second time, and I am stumped by why this is
private void ScanForTVShowFolders( GenreDirectoryInfo drive ) {
IEnumerable<DirectoryInfo> shows = drive.DirInfo.EnumerateDirectories();
foreach (DirectoryInfo d in shows) {
//showList contains a list of existing TV show names previously queried out of DB
if (showList.Contains(d.Name)) {
System.Console.WriteLine(d.Name + ".....MATCH");
} else {
System.Console.Write(d.Name + "......NO MATCH..ADDING....");
TVShow show = LookUpShowOnline(d.Name, drive.GenreName);
if (show.Id == -1) { // id of -1 means online search failed
System.Console.Write("..........CANT FIND SHOW" + Environment.NewLine);
} else if (context.TVShows.Any(a => a.Id == show.Id)) { //catch duplicate primary key insert
System.Console.Write(".......CANT ADD, ID ALREADY EXISTS IN DB" + Environment.NewLine);
} else {
context.TVShows.AddObject(show);
context.SaveChanges();
System.Console.Write("....DONE" + Environment.NewLine);
}
}
}
private TVShow LookUpShowOnline( string name, string genre ) {
string xmlPath = String.Format("http://services.tvrage.com/feeds/search.php?show='{0}'", name);
TVShow aShow = new TVShow();
aShow.Id = -1; // -1 = Can't find
XmlDocument xmlResp = new XmlDocument();
try { xmlResp.Load(xmlPath); } catch (WebException e) { System.Console.WriteLine(e); }
XmlNode root = xmlResp.FirstChild;
if (root.NodeType == XmlNodeType.XmlDeclaration) { root = root.NextSibling; }
XmlNode tvShowXML;
//if (showXML["episode"] == null)
// return false;
tvShowXML = root["show"];
if (tvShowXML != null) {
aShow.Id = System.Convert.ToInt16(tvShowXML["showid"].InnerText);
aShow.Name = tvShowXML["name"].InnerText.Trim();
aShow.StartYear = tvShowXML["started"].InnerText.Trim();
aShow.Status = tvShowXML["status"].InnerText.Trim();
aShow.TVGenre = context.TVGenres.Where(b => b.Name.Trim() == genre).Single();
}
return aShow;
}
}
Edit
Doing some more reading I added context.ObjectStateManager to my debug watchlist and I can see everytime I create a new TVShow entity a new record is added to _addedEntityStore. Actually if I remove context.TVShows.AddObject(show) the code still updates the database so manually adding to the context seems redundant.
If your are inserting object by foreach loop > better to keep the Primary Key outside and make it increment!
eg: int newID= Shows.Select(d=>d.Id).Max();
foreach(............)
{
show.Id = newID++;
.
.
. //remaining fields
.
context.TVShows.AddObject(show);
}
context.SaveChanges();
it works for me...!!
Turns out context.TVShows.AddObject(show) is unnecessary in my case, I was inadvertently adding all created show entities to the context when this query runs
aShow.TVGenre = context.TVGenres.Where(b => b.Name.Trim() == genre).Single();
This is not what I wanted, I just wanted to create the object, then decide whether to add it. Will be pretty easy to fix now I know why it's happening.
For this example, I've defined the following class, which is saved inside an SQLite database:
[SQLite.AutoIncrement, SQLite.PrimaryKey, SQLite.Indexed]
public int ID { get; set; }
[SQLite.Indexed]
public string ImageName { get; set; }
I'm using the insert command supplied with sqlite-net, and it works:
SQLiteAsyncConnection CurrentConnection = new SQLiteAsyncConnection("DB");
int result = await CurrentConnection.InsertAsync(this);
Afterwards, I'm trying to select data from the SQLite DB.
List<Image> result = new List<Image>();
if (ImageName != null)
{
query = CurrentConnection.Table<Image>().Where(i => i.ImageName == ImageName);
result = await query.ToListAsync();
}
if (ID != null && ID > 0)
{
query = CurrentConnection.Table<Image>().Where(i => i.ID == ID);
result = await query.ToListAsync();
}
if (result.Count > 0)
{
LoadFromResult(result[0]);
return;
}
When selecting by ImageName, all works well and I get the results I need. However, when trying to select by ID, no results are selected.
I know the image with the given ID exists since I've just inserted it and checked the ID afterwards, but for some reason this just does not work.
Am I completely blind and missed a small letter here? Has anyone used SQLite-net to try and select by Primary key?
//Edit
Also tried this, which did not work:
var query1 = await CurrentConnection.QueryAsync<Image>("select * from Image where ID = ?", ID);
if (query1.Count > 0)
{
LoadFromResult(query1[0]);
return;
}
//edit 2
I've got a bit of a hunch on this - when I insert the image, the ID does get set to some ID, however when I select all the images in the DB, all of them have an ID of 0.
Any idea on why this could happen?
I think your problem might be with AutoIncrement attribute for your class.
Try removing the AutoIncrement attribute and see, if you can find the image by id.
I think what's happening is that the AutoIncrement attribute is setting the Id, overriding the id you create.
For Example
Let's say you create an instance of your MyImage with:
MyImage i = new MyImage() { Id=5,
ImageName="imageName"}
When you run,CurrentConnection.InsertAsync(i) (on an empty table), the entry inserted to the database has ID 1, not 5.
Hope that helped