Xamarin Forms Sqlite UpdateAsync updating database - sqlite

Xamarin Forms 5
VS2019
Currently trying to update android only.
I've tried several different ways to update the database, but nothing seems to work. It seems to be updating cache, because if I select the same entry it has the changes, but even the ObservableCollection isn't being updated.
Here's the latest:
Model
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Android.Resource;
#nullable disable
namespace Photography.Handbook.Models
{
public partial class Aperture : INotifyPropertyChanged
{
public Aperture()
{
}
//public Aperture()
//{
// ShutterApertures = new HashSet<ShutterAperture>();
//}
[PrimaryKey]
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public string Notes { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public virtual ICollection<ShutterAperture> ShutterApertures { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Update Code
public async Task UpdateAsync(Aperture entity)
{
String databasePath = await DB.GetDatabaseFilePath();
SQLiteAsyncConnection db = new SQLiteAsyncConnection(databasePath);
var x = App.db.DBInstance.Query<Aperture>($"SELECT * FROM Aperture WHERE Id = '{entity.Id}'");
if(x != null)
{
x[0].Name = entity.Name;
x[0].Active = entity.Active;
x[0].Notes = entity.Notes;
var y = await db.UpdateAllAsync(x[0]);
}
}

Found the problem. I was overwriting the database each time I re-started the app.
Thanks everyone for the suggestions.

Related

How do I save Xamarin TimePicker value to database?

I'm using SQLite to store data from a Xamarin.Forms app. I want the user to be able to use the DatePicker for a date and the TimePicker for a time and then store it. When I use the following:
<DatePicker Date="{Binding Date}"
x:Name="DatePicker"
Format="D" />
It stores the chosen date in the database correctly but when I use:
<TimePicker Time="{Binding Time}"
x:Name="TimePicker"
Format="T"/>
It stores the time as 12:00 AM every time. What am I missing?
Date and Time are both DateTime values in the database.
using System;
using SQLite;
namespace Items.Models
{
public class Item
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Location { get; set; }
public DateTime Date { get; set; } = DateTime.Now;
public DateTime Time { get; set; }
}
and
using System.Collections.Generic;
using System.Threading.Tasks;
using SQLite;
using Items.Models;
namespace Item.Data
{
public class ItemDatabase
{
readonly SQLiteAsyncConnection database;
public ItemDatabase(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<Item>().Wait();
}
public Task<List<Item>> GetItemsAsync()
{
//Get all items.
return database.Table<Item>().ToListAsync();
}
public Task<Item> GetItemAsync(int id)
{
// Get a specific item.
return database.Table<Item>()
.Where(i => i.ID == id)
.FirstOrDefaultAsync();
}
public Task<int> SaveItemAsync(Item item)
{
if (item.ID != 0)
{
// Update an existing item.
return database.UpdateAsync(item);
}
else
{
// Save a new item.
return database.InsertAsync(item);
}
}
public Task<int> DeleteItemAsync(Item item)
{
// Delete an Item.
return database.DeleteAsync(item);
}
}
}
You got it wrong the type of TimePicker.Time, it should be TimeSpan not DateTime , so the binding is created incorrectly, that is the reason why the value not change when select the time on TimePicker .
Modify your code as below
public class Item
{
public DateTime Date { get; set; } = DateTime.Now;
public TimeSpan Time { get; set; }
}

Xamarin Forms SQLLite add OneToOne and OneToMany subobjects to db

Im trying to create a persistent sqllite db (creation tables once you install the app, deletion of db when you uninstall app)
I have a issue that I cant save my sub objects for example
public class ObjectInstanceResponseModel : GenericResponseModel
{
public ObservableCollection<ObjectInstanceData> ObjectInstances { get; set; }
}
public class ObjectInstanceData : GenericResponseModel
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[JsonProperty("idObjectinstance")]
public int IdObjectInstance { get; set; }
[JsonProperty("objectclass_idObjectclass")]
public int ObjectClassIdObjectClass { get; set; }
[JsonProperty("objectclassname")]
public string ObjectClassName { get; set; }
[JsonProperty("visibilitylevel")]
public int VisibilityLevel { get; set; }
[JsonProperty("showname")]
public bool ShowName { get; set; }
[JsonProperty("showicon")]
public bool ShowIcon { get; set; }
[JsonProperty("creationtime")]
public DateTimeOffset CreationTime { get; set; }
[JsonProperty("users_idUsers")]
public int UsersIdUsers { get; set; }
[JsonProperty("isfavorite")]
public bool? IsFavorite { get; set; }
[OneToMany("ObjectInstanceDataStrings")]
[JsonProperty("strings")]
public List<String> Strings { get; set; }
}
public class String
{
[PrimaryKey, AutoIncrement]
[JsonProperty("idObjectparameterstring")]
public int? IdObjectParameterString { get; set; }
[ForeignKey(typeof(ObjectInstanceData))]
public int ObjectInstanceDataStrings { get; set; }
[JsonProperty("stringvalue")]
public string StringValue { get; set; }
[JsonProperty("objectparameterset_idObjectparameterset")]
public int? ObjectParameterSetIdObjectParameterSet { get; set; }
[JsonProperty("showinballon")]
public bool? ShowInBallon { get; set; }
[JsonProperty("idClassparameter")]
public int IdClassParameter { get; set; }
[JsonProperty("classparametername")]
public string ClassParameterName { get; set; }
}
So, my class String is always empty, although there are some rows in the table that I created Strings..
Am I need a lazy loading for this?
I implemented sqllite through depedency service in my app.cs like this:
public partial class App : Application
{
public static SQLiteConnection DatabaseConnection { get; set; }
public App()
{
InitializeComponent();
DatabaseConnection = DependencyService.Get<IConnection>().GetConnection();
CreateTables();
}
private void CreateTables()
{
DatabaseConnection.CreateTable<ObjectInstanceData>();
DatabaseConnection.CreateTable<Models.Objects.String>();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
So, basically the logic should be when no internet, work with sql lite (keep local changes), and when internet come back upload that changes that kept in db, and erase data from tables.
You noticed that Im using response model for api.
So, Im calling from my FavoriteObjectViewModel this:
var response = await ApiServiceProvider.GetObjectInstances(null, true);
and in the ApiServiceProvider:
public static async Task<ObjectInstanceResponseModel> GetObjectInstances(string queryString = null, bool? onlyFavorites = null)
{
HttpResponseMessage response;
response = await apiClient.GetAsync(objectInstancesEndpoint);
var resultContent = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ObservableCollection<ObjectInstanceData>>(resultContent);
objectInstanceResponse.ObjectInstances = result;
//after each api call I need to update db
//delete previous data, and add fresh data from api
App.DatabaseConnection.DeleteAll<ObjectInstanceData>();
foreach (var item in result)
{
App.DatabaseConnection.Insert(item);
if (item.Strings != null && item.Strings.Count > 0)
App.DatabaseConnection.InsertAll(item.Strings);
}
//I only get the data for ObjectInstanceData, Strings model is empty!
var objectsResponseDb = App.DatabaseConnection.GetAllWithChildren<ObjectInstanceData>();
objectInstanceResponse.Succeeded = true;
return objectInstanceResponse;
}
So, my questions are:
If I create tables each time in App.cs is that mean, that I not store data, when user quit application, and re-enter again?
Why is the model Strings empty? When I invoke var strings = App.DatabaseConnection.GetAllWithChildren<Models.Objects.String>(); I can see that there is data?
What is the best approach for doing offline "logging"? maybe there is a some better nuget for sqllite?
I don't know much about persistent databases but one thing I can tell you for sure:you're approach is wrong.
You should create a separate class for the database's logic like creating tables and instantiating the connection along with other methods for manipulating data.
In the App class you can create a static resource of the database class which you can call it and make use of the methods in it.
I'm not very good with explaining but here it is a very basic example of how it should look:https://learn.microsoft.com/en-us/xamarin/get-started/tutorials/local-database/?tutorial-step=2&tabs=vswin.

How to create a list page for Users in .Net Core 2

We extended the Identity Roles as well Users:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ApplicationRole : IdentityRole
{
public ApplicationRole() : base() { }
public ApplicationRole(string roleName) : base(roleName) { }
public ApplicationRole(string roleName, string description, DateTime createdDate) : base(roleName)
{
base.Name = roleName;
this.Description = description;
this.CreatedDate = createdDate;
}
public string Description { get; set; }
public DateTime CreatedDate { get; set; }
}
}
and
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ApplicationUser : IdentityUser
{
public ApplicationUser() : base() { }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Suburb { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public string Country { get; set; }
[InverseProperty("ApplicationUser")]
public IList<HostApplicationUser> HostApplicationUsers { get; set; }
[InverseProperty("HostApplicationCreatedUser")]
public HostApplicationUser HostApplicationCreatedUser { get; set; }
[InverseProperty("HostApplicationLastModifiedUser")]
public HostApplicationUser HostApplicationLastModifiedUser { get; set; }
[InverseProperty("ApplicationUser")]
public IList<ClubApplicationUser> ClubApplicationUsers { get; set; }
[InverseProperty("ClubApplicationCreatedUser")]
public ClubApplicationUser ClubApplicationCreatedUser { get; set; }
[InverseProperty("ClubApplicationLastModifiedUser")]
public ClubApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
We are trying to create a Razor Page list of users as well their role:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
public class IndexModel : PageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
public IndexModel(AthlosifyWebArchery.Data.ApplicationDbContext context)
{
_context = context;
}
public List<ApplicationUser> User { get; private set; }
public List<IdentityUserRole<string>> UsersRoles { get; set; } // get my roles or context of user
public List<IdentityRole> AllRoles { get; private set; }
public async Task OnGetAsync()
{
User = _context.Users.Include("UserRoles").ToList();
//UsersRoles = _context.UserRoles.ToList(); // get my roles or context of user
//AllRoles = _context.Roles.ToList();
}
}
}
We managed to get just the user list BUT not sure on how to include the Roles in this case.
Any pointer please?
Firstly, try to avoid using Include function's string overload. Using the lamda instead will help you be sure that the property exists. For instance, in this case, a property named UserRoles doesn't exist for your user class in the first place. Secondly, the syntax you are trying to use it generally used for a one to many relationships. Note that users and roles is a many to many relation and the identity context (that your dbcontext extended) has a UserRoles property for this. You should be able to get all users joined with their roles using a query like this:
IEnumerable<User> users = from u in context.Users
from r in context.Roles
from ur in context.UserRoles
where u.Id == ur.UserId && ur.RoleId == r.Id
select u;

create Multiple tables using Sqlite-net-pcl

I am using xamarin forms and Sqlite-net-pcl (nuget). I need help on creating multiple tables. I have set up the requirements as below. I need to do the following:
1) I need to create tables and database when the App launches. How to do this in App.cs?
Update Problem:
1) Tables are not created. Why?
---1--- in PCL : add these
-- classes for table
using SQLite;
namespace MyApp.Model
{
[Table("TblCountry")]
public class Country
{
public string Country { get; set; }
public string CountryCode { get; set; }
public string OfficialLanguage { get; set; }
}
[Table("TblEmployees")]
public class Employee
{
[PrimaryKey, AutoIncrement]
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
}
--- interface class
using System;
using System.Collections.Generic;
using System.Text;
using SQLite;
namespace MyApp.DataAccessHelpers
{
public interface ISQLite
{
SQLiteConnection GetConnection();
}
}
---2---in Xamarin.Droid: I add this class
using SQLite;
using System.IO;
using Xamarin.Forms;
using MyApp.Droid.Implementation;
using MyApp.DataAccessHelpers;
[assembly: Xamarin.Forms.Dependency(typeof(AndroidSQLite))]
namespace MyApp.Droid.Implementation
{
class AndroidSQLite : ISQLite
{
public SQLite.SQLiteConnection GetConnection()
{
string documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(documentsPath, DatabaseHelper.DbFileName);
var conn = new SQLite.SQLiteConnection(path);
return conn;
}
}
}
------- Update :
public class DatabaseHelper
{
static SQLiteConnection sqliteconnection;
public const string DbFileName = "MyDb.db3";
public DatabaseHelper()
{
try
{
sqliteconnection = DependencyService.Get<ISQLite>().GetConnection();
sqliteconnection.CreateTable<CountryModel>();
sqliteconnection.CreateTable<EmployeeModel>();
}
catch (Exception ex)
{
string strErr = ex.ToString();
}
}
public List<CountryModel> GetAllCountry()
{
return (from data in sqliteconnection.Table<CountryModel>()
select data).ToList();
}
public CountryModel GetCountryByHuNbr(string name)
{
return sqliteconnection.Table<CountryModel>().FirstOrDefault(c => c.Name == name);
}
public void DeleteAllCountry()
{
sqliteconnection.DeleteAll<CountryModel>();
}
public void DeleteCountryByid(int ID)
{
sqliteconnection.Delete<CountryModel>(ID);
}
public void InsertCountry(CountryModel country)
{
sqliteconnection.Insert(country);
}
public void UpdateCountry(CountryModel country)
{
sqliteconnection.Update(country);
}
//------- CRUD for employee
public void InsertEmployee(EmployeeModel employee)
{
sqliteconnection.Insert(employee);
}
.....
... and all the CRUD for employee
}
}
Thanks in advance.
I created a helper class which contains all methods I need in order to interact with SQLite Database. I use the CreateTable() to create a table.
In App.xaml.cs file, I create an instance of my DataAccess helper class and I call the CreateLocalDbTables() method.
DataAccessHelper
public class DataAccess : IDisposable
{
private SQLiteConnection Connection;
#region Constructor
public DataAccess(ISQLitePlatform sQLitePlatform, string dbPath)
{
this.Connection = new SQLiteConnection(sQLitePlatform, dbPath);
}
#endregion
#region Methods
public void CreateLocaldbTables()
{
this.Connection.CreateTable<Registration>();
this.Connection.CreateTable<TransmissionLog>();
this.Connection.CreateTable<Parameters>();
this.Connection.CreateTable<Guest>();
}
In APP.xaml.cs
public partial class App : Application
{
#region Properties
public static DataAccess DBConnection { get; set; }
#endregion
public App(string localDbPath, ISQLitePlatform sqlitePlatform)
{
InitializeComponent();
DBConnection = new DataAccess(sqlitePlatform,localDbPath);
DBConnection.CreateLocaldbTables();
Model
namespace AppRegistration.Models
{
using SQLite;
using System;
[Table("Activity")]
public class Actividad
{
[Column("IdActivity")]
[PrimaryKey, Autoincrement]
public int IdActivity { get; set; }
[Column("IdEvent")]
[PrimaryKey]
public int IdEvent { get; set; }
[Column("ActivityDescription")]
[NotNull]
public string ActivityDescription { get; set; }
[Column("Status")]
[NotNull]
public string Status { get; set; }
[Column("UserId")]
[NotNull]
public int UserId { get; set; }
}
}

Add tags to talk for many to many relationship in Entity Framework

I am creating an API endpoint that creates a new Talk with the tags that should be associated to the talk. I have a many to many relationship set up between tags and talks in my domain, see below for the relationship.
Tag.cs
using System;
using System.Collections.Generic;
namespace Conferency.Domain
{
public class Tag : IAuditable
{
public int Id { get; set; }
public string Name { get; set; }
public List<TalkTag> TalkTags { get; set; }
public DateTime ModifiedAt { get; set; }
public DateTime CreatedAt { get; set; }
}
}
Talk.cs
using System;
using System.Collections.Generic;
namespace Conferency.Domain
{
public class Talk : IAuditable
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<TalkTag> TalkTags { get; set; }
public DateTime Presented { get; set; }
public DateTime ModifiedAt { get; set; }
public DateTime CreatedAt { get; set; }
}
}
TalkTag.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Conferency.Domain
{
public class TalkTag
{
public int TalkId { get; set; }
public Talk Talk { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
}
ConferencyContext.cs (Deleted irrelevant code)
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using Conferency.Domain;
namespace Conferency.Data
{
public class ConferencyContext: DbContext
{
public DbSet<Talk> Talks { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<TalkTag> TagTalks { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TalkTag>()
.HasKey(s => new { s.TalkId, s.TagId });
modelBuilder.Entity<TalkTag>()
.HasOne(pt => pt.Talk)
.WithMany(p => p.TalkTags)
.HasForeignKey(pt => pt.TalkId);
modelBuilder.Entity<TalkTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.TalkTags)
.HasForeignKey(pt => pt.TagId);
base.OnModelCreating(modelBuilder);
}
}
}
TalkViewModel.cs
using System;
using System.Collections.Generic;
namespace Conferency.Application.Models
{
public class TalkViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public List<String> Tags { get; set; }
}
}
The problem is I can't figure out how to create a talk and its tags (attach if they exists, create if they don't). I am not sure in what order to accomplish this. Do I have to query each tag to check if they exist or is there a findOrCreate method I could use? How do I create a TalkTag record if the Talk isn't created yet? Is there an elegant way to accomplish this that I am not understanding?
TalkRepository.cs
using System.Collections.Generic;
using Conferency.Domain;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Conferency.Data
{
public class TalkRepository : ITalkRepository
{
private ConferencyContext _context;
public TalkRepository(ConferencyContext context)
{
_context = context;
}
public void Add(Talk entity)
{
_context.Add(entity);
}
public void AddWithTags(Talk entity, List<String> tags)
{
// Create Talk
// Query for each tag
// Create if they don't exist
// Attach to talk
// ??
}
public IEnumerable<Talk> GetAllTalks()
{
return _context.Talks
.Include(c => c.TalkTags)
.OrderBy(c => c.Presented)
.ToList();
}
public Talk GetTalk(int id)
{
return _context.Talks
.Include(c => c.TalkTags)
.Where(c => c.Id == id)
.FirstOrDefault();
}
public async Task<bool> SaveAllAsync()
{
return (await _context.SaveChangesAsync()) > 0;
}
}
}
I am new to c# and I'm trying to learn best practices and familiarizing myself with EF and ASP.NET Core so hopefully somebody can help guide me in the right path. The full solution is here if you want to take a look https://github.com/bliitzkrieg/Conferency
I tried solving it myself but Im getting a NullPointerException, here is my attempt at a solution:
TalksController.cs
[HttpPost()]
public async Task<IActionResult> Post([FromBody]TalkViewModel model)
{
try
{
_logger.LogInformation("Creating a new Talk");
List<Tag> tags = _tagRepo.FindOrCreateTags(model.Tags);
Talk talk = new Talk { Name = model.Name, Url = model.Url };
List<TalkTag> talkTags = new List<TalkTag>();
tags.ForEach(tag =>
{
var talkTag = new TalkTag { TagId = tag.Id, Talk = talk };
talkTags.Add(talkTag);
});
talk.TalkTags.AddRange(talkTags); // Exception being thrown here
_repo.Add(talk);
if (await _repo.SaveAllAsync())
{
string newUri = Url.Link("TalkGet", new { id = talk.Id });
return Created(newUri, talk);
}
else
{
_logger.LogWarning("Could not save Talk");
}
}
catch (Exception ex)
{
_logger.LogError($"Threw exception while saving Talk: {ex}");
}
return BadRequest();
}
}
TagRepository.cs
using System;
using System.Collections.Generic;
using Conferency.Domain;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace Conferency.Data
{
public class TagRepository: ITagRepository
{
private ConferencyContext _context;
public TagRepository(ConferencyContext context)
{
_context = context;
}
public void Add(Tag entity)
{
_context.Add(entity);
}
public List<Tag> FindOrCreateTags(List<string> tags)
{
List<Tag> _tags = new List<Tag>();
tags.ForEach(t =>
{
try
{
var tag = _context.Tags
.Where(c => c.Name == t)
.FirstOrDefault();
if (tag != null)
{
_tags.Add(tag);
}
else
{
Tag created = new Tag { Name = t };
this.Add(created);
_tags.Add(created);
}
}
catch (Exception ex)
{
}
});
return _tags;
}
public async Task<bool> SaveAllAsync()
{
return (await _context.SaveChangesAsync()) > 0;
}
}
}
On your TalkViewModel add a List<TagViewModel> property with the following properties:
public int TagId { get; set; }
public string TagName { get; set; }
public bool Selected { get; set; }
When you pass the TalkViewModel to your repo, filter out the selected TagViewModels and for each one, add a TalkTag with the proper TagId to your TalkTags property on your Talk. EF should take care of adding the proper TalkId upon _context.SaveChanges().
If the Tag doesn't exist, create a TalkTag with a new Tag and the new Talk as its properties, then add it to your _context. EF should take care of the rest.
You haven't initialized your TalkTags collection which causes the nullpointer. Try this when initializing the Talk object:
Talk talk = new Talk { Name = model.Name, Url = model.Url, TalkTags = new List<TalkTag>() };
Do you need more properties on a TalkTag object? Otherwise you could just have List<Talk> in the Tag class and List<Tag> in the Talk class and the mapping will be done by EF (a TalkTag table will be created in the DB).
Michael Tranchida already described the approach for adding the objects to the context.

Resources