I'm using Xamarin, also my SQLite tables contain a large amount of data.
Because I want to avoid UIThread problems in OnCreate(), I need to perform database actions asynchronously.
I'm looking for guidance if I am handling this properly.
First method, which I found on the net:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.InventoryPreviewMain);
Thread thread = new Thread(() =>
{
SQLiteConnection db = new SQLiteConnection(dpPath);
var table = db.Query<InventoryPreviewClass>("select * from InventoryPreviewClass where CategoryID =" + Connection.CategoryID + "");
mItems = new List<InventoryPreviewClass>();
foreach (var item in table)
{
mItems.Add(new InventoryPreviewClass() { InventoryItemID = item.InventoryItemID, InventoryItemName = item.InventoryItemName, InventoryItemPrice = item.InventoryItemPrice });
}
MyListViewAdapterInventory adapter = new MyListViewAdapterInventory(this, Resource.Layout.InventoryPreview, mItems);
mlistview.Adapter = adapter;
});
thread.Start();
Second Method, using async
public async void StartTimer()
{
SQLiteConnection db = new SQLiteConnection(dpPath);
var table = db.Query<InventoryPreviewClass>("select * from InventoryPreviewClass where CategoryID =" + Connection.CategoryID + "");
mItems = new List<InventoryPreviewClass>();
foreach (var item in table)
{
mItems.Add(new InventoryPreviewClass() { InventoryItemID = item.InventoryItemID, InventoryItemName = item.InventoryItemName, InventoryItemPrice = item.InventoryItemPrice });
}
MyListViewAdapterInventory adapter = new MyListViewAdapterInventory(this, Resource.Layout.InventoryPreview, mItems);
mlistview.Adapter = adapter;
await Task.Delay(500);
}
Which of two examples are more safe for keeping alive UIthread? Is there any other solution for making this?What is more reccomended to do?
Answer
Use the Cross-platform SQLite Library made by #FrankKruger to create/access SQLite databases for Xamarin mobile apps.
This library has a built-in asynchronous connection, so you'll never need to worry about accessing the database from the UI Thread again!
Xamarin.Android Example
"Second Method"
public async Task StartTimer()
{
mItems = await InventoryPreviewClassDatabase.GetAllInventoryPreviewClassAsync();
MyListViewAdapterInventory adapter = new MyListViewAdapterInventory(this, Resource.Layout.InventoryPreview, mItems);
mlistview.Adapter = adapter;
}
BaseDatabase Class
using System;
using System.Threading.Tasks;
using SQLite;
namespace SampleApp
{
public abstract class BaseDatabase
{
#region Constant Fields
static readonly Lazy<SQLiteAsyncConnection> _databaseConnectionHolder = new Lazy<SQLiteAsyncConnection>(() => GetDatabaseConnection());
#endregion
#region Fields
static bool _isInitialized;
#endregion
#region Properties
static SQLiteAsyncConnection DatabaseConnection => _databaseConnectionHolder.Value;
#endregion
#region Methods
protected static async Task<SQLiteAsyncConnection> GetDatabaseConnectionAsync()
{
if (!_isInitialized)
await Initialize().ConfigureAwait(false);
return DatabaseConnection;
}
static async Task Initialize()
{
await DatabaseConnection.CreateTableAsync<InventoryPreviewClass>().ConfigureAwait(false);
_isInitialized = true;
}
SQLiteAsyncConnection GetDatabaseConnection()
{
var sqliteFilename = "YourDatabaseName.db3";
string documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); // Documents folder
var path = Path.Combine(documentsPath, sqliteFilename);
var conn = new SQLiteAsyncConnection(path, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache);
return conn;
}
#endregion
}
}
Parent Database Class
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace SampleApp
{
public abstract class InventoryPreviewClassDatabase : BaseDatabase
{
#region Methods
public static async Task<IList<InventoryPreviewClass>> GetAllInventoryPreviewClassAsync()
{
var databaseConnection = await GetDatabaseConnectionAsync().ConfigureAwait(false);
return await databaseConnection.Table<InventoryPreviewClass>().ToListAsync().ConfigureAwait(false);
}
public static async Task<InventoryPreviewClass> GetInventoryPreviewClassByIDAsync(int id)
{
var databaseConnection = await GetDatabaseConnectionAsync().ConfigureAwait(false);
return await databaseConnection.Table<InventoryPreviewClass>().Where(x => x.ID.Equals(id)).FirstOrDefaultAsync().ConfigureAwait(false);
}
public static async Task<int> SaveInventoryPreviewClassAsync(InventoryPreviewClass inventoryPreview)
{
var databaseConnection = await GetDatabaseConnectionAsync().ConfigureAwait(false);
var isObjectInDatabase = await GetInventoryPreviewClassByIDAsync(inventoryPreview.ID).ConfigureAwait(false) != null;
if (isObjectInDatabase)
return await databaseConnection.UpdateAsync(inventoryPreview).ConfigureAwait(false);
return await databaseConnection.InsertAsync(inventoryPreview).ConfigureAwait(false);
}
public static async Task<int> DeleteItemAsync(OpportunityModel opportunity)
{
var databaseConnection = await GetDatabaseConnectionAsync().ConfigureAwait(false);
return await databaseConnection.DeleteAsync(opportunity).ConfigureAwait(false);
}
public static async Task<int> GetNumberOfRowsAsync()
{
var databaseConnection = await GetDatabaseConnectionAsync().ConfigureAwait(false);
return await databaseConnection.Table<InventoryPreviewClass>().CountAsync().ConfigureAwait(false);
}
#endregion
}
}
This code was inspired from this Xamarin.Forms sample app
Related
We have built an API with .NET Core 3.1 that extracts data from an Excel and stores it via
EF Core into a MS SQL database. We use Quartz. NET so that it is handled in a background thread. For DI we use Autofac.
We use Scoped Services to be able to use the DBContext via DI (as described here https://andrewlock.net/creating-a-quartz-net-hosted-service-with-asp-net-core/).
Unfortunately, saving the data still does not work when multiple users are using the application at the same time. We get the following error message:
The instance of entity type 'TABLENAME' cannot be tracked because another instance with the same key value for {'TABLEKEY'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Here our related code:
Startup.cs
// Add DbContext
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default"), b => b.MigrationsAssembly("XY.Infrastructure")));
// Add Quartz
services.AddQuartz(q =>
{
// as of 3.3.2 this also injects scoped services (like EF DbContext) without problems
q.UseMicrosoftDependencyInjectionJobFactory();
// these are the defaults
q.UseSimpleTypeLoader();
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 24;
});
});
services.AddQuartzServer(options =>
{
// when shutting down we want jobs to complete gracefully
options.WaitForJobsToComplete = true;
});
// Add Services
services.AddHostedService<QuartzHostedService>();
services.AddSingleton<IJobFactory, SingletonJobFactory>();
services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
services.AddTransient<ImportJob>();
services.AddScoped<IApplicationDbContext, ApplicationDbContext>();
services.AddScoped<IMyRepository, MyRepository>();
Logic.cs
// Grab the Scheduler instance from the Factory
var factory = new StdSchedulerFactory();
var scheduler = await factory.GetScheduler();
var parameters = new JobDataMap()
{
new KeyValuePair<string, object>("request", message),
new KeyValuePair<string, object>("sales", sales),
};
var jobId = $"processJob{Guid.NewGuid()}";
var groupId = $"group{Guid.NewGuid()}";
// defines the job
IJobDetail job = JobBuilder.Create<ImportJob>()
.WithIdentity(jobId, groupId)
.UsingJobData(parameters)
.Build();
// defines the trigger
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity($"Trigger{Guid.NewGuid()}", groupId)
.ForJob(job)
.StartNow()
.Build();
// schedule Job
await scheduler.ScheduleJob(job, trigger);
// and start it off
await scheduler.Start();
QuartzHostedService.cs
public class QuartzHostedService : IHostedService
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly IJobFactory _jobFactory;
private readonly ILogger<QuartzHostedService> _logger;
private readonly IEnumerable<JobSchedule> _jobSchedules;
public QuartzHostedService(
ISchedulerFactory schedulerFactory,
IJobFactory jobFactory,
IEnumerable<JobSchedule> jobSchedules,
ILogger<QuartzHostedService> logger)
{
_schedulerFactory = schedulerFactory;
_jobSchedules = jobSchedules;
_jobFactory = jobFactory;
_logger = logger;
}
public IScheduler Scheduler { get; set; }
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
Scheduler.JobFactory = _jobFactory;
foreach (var jobSchedule in _jobSchedules)
{
var job = CreateJob(jobSchedule);
var trigger = CreateTrigger(jobSchedule);
await Scheduler.ScheduleJob(job, trigger, cancellationToken);
}
await Scheduler.Start(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Scheduler?.Shutdown(cancellationToken);
}
private static IJobDetail CreateJob(JobSchedule schedule)
{
var jobType = schedule.JobType;
return JobBuilder
.Create(jobType)
.WithIdentity(jobType.FullName)
.WithDescription(jobType.Name)
.Build();
}
private static ITrigger CreateTrigger(JobSchedule schedule)
{
return TriggerBuilder
.Create()
.WithIdentity($"{schedule.JobType.FullName}.trigger")
.StartNow()
.Build();
}
}
SingletonJobFactory.cs
public class SingletonJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public SingletonJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
}
catch (Exception ex)
{
throw;
}
}
public void ReturnJob(IJob job) { }
}
Importjob.cs
[DisallowConcurrentExecution]
public class ImportJob : IJob
{
private readonly IServiceProvider _provider;
private readonly ILogger<ImportJob> _logger;
public ImportJob(IServiceProvider provider, ILogger<ImportJob> logger)
{
_provider = provider;
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
try
{
using (var scope = _provider.CreateScope())
{
var jobType = context.JobDetail.JobType;
var job = scope.ServiceProvider.GetRequiredService(jobType) as IJob;
var repo = _provider.GetRequiredService<MYRepository>();
var importFactSales = _provider.GetRequiredService<IImportData>();
var savedRows = 0;
var request = (MyRequest)context.JobDetail.JobDataMap.Get("request");
var sales = (IEnumerable<MyData>)context.JobDetail.JobDataMap.Get("sales");
await importFactSales.saveValidateItems(repo, request, sales, savedRows);
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
}
I have found a solution in the meantime. As #marko-lahma described in the comment, use the built-in hosted service and don't implement your own JobFactory.
Remove the SingletonJobFactory.cs and QuartzHostedService.cs
Use the Autofac.Extras.Quartz and Quartz.Extensions.Hosting Nuget Package
Don't use CreateScope anymore, inject all needed Dependencies over the Constructor
Register QuartzAutofacFactoryModule and QuartzAutofacJobsModule in the Startup.
I am having one frustrating time attempting to follow the Couchbase documentation (which is horribly outdated and the examples just do not even compile).
I already have several other persistence stacks following this same pattern. I would like to migrate to Couchbase from CosmosDb and I simply cannot figure out how to implement this in a generic pattern. FYI, I am not here to debate the move from Cosmos to Couchbase, so the let please leave that topic alone.
I have already done this for Mongo, Dynamo, PostresSql, MsftSql, Cosmos Document, Cosmos SQL, and Cosmos Table.
Nuget Packages:
Couchbase.Lite # 2.7.1
CouchbaseNetClient # 3.0.1
The IPlatformRepository interface, used by all repositories in the system
Task<T> CreateAsync(T data);
Task<T> ReadAsync(Guid id);
Task<bool> UpdateAsync(T data);
Task<bool> DeleteAsync(Guid id);
Task<IEnumerable<T>> AllAsync();
Task<IEnumerable<T>> QueryAsync(Expression<Func<T, bool>> predicate = null);
Here is the PoC code that I have in my Couchbase provider, but I am stuck at trying to figure out how to do the AllAsync() and QueryAsync() functions (commented out QueryAsync() is from the CosmosDocument provider I have. I have not started on that one yet. I started with the AllAsync() to just retrieve the documents.
public class CouchbaseRepository<T> : IPlatformRepository<T>
where T: DomainAggregate
{
private readonly string collectionName;
private readonly CouchbaseSettings couchbaseSettings;
private ICluster cluster;
private IBucket bucket;
private ICollection<T> collection;
private ICouchbaseCollection cbc;
public CouchbaseRepository(CouchbaseSettings settings)
{
Inflector.Inflector.SetDefaultCultureFunc = () => new CultureInfo("en");
collectionName = typeof(T).Name.Pluralize().Uncapitalize();
}
public async Task<T> ReadAsync(Guid id)
{
var result = await bucket.Collection(collectionName).GetAsync(id.ToString());
return result.ContentAs<T>();
}
public async Task<IEnumerable<T>> QueryAsync(Expression<Func<T, bool>> predicate)
{
// var results = new List<T>();
//
// var query = client.CreateDocumentQuery<T>(
// UriFactory.CreateDocumentCollectionUri(
// databaseId,
// collectionId),
// new FeedOptions
// {
// MaxItemCount = -1,
// EnableCrossPartitionQuery = true
// });
//
// predicate = null;
// var conditionalQuery = predicate == null
// ? query
// : query.Where(predicate);
//
// var documentQuery = conditionalQuery.AsDocumentQuery();
//
// while (documentQuery.HasMoreResults)
// {
// results.AddRange(await documentQuery.ExecuteNextAsync<T>());
// }
//
// return results;
}
public async Task<IEnumerable<T>> AllAsync()
{
using (var cluseter = new Cluster())
{
}
}
public async Task<T> CreateAsync(T item)
{
var result = await bucket.Collection(collectionName).InsertAsync(item.Id.ToString(), item);
return item;
}
public async Task<bool> UpdateAsync(T item)
{
}
public async Task<bool> DeleteAsync(Guid id)
{
}
private async Task EstablishConnection()
{
cluster = await Cluster.ConnectAsync(couchbaseSettings.Endpoint, couchbaseSettings.User, couchbaseSettings.Password);
bucket = await cluster.BucketAsync(couchbaseSettings.Bucket);
}
}
I'm trying to make use of System.Text.Json serialization for a project I'm working on. When I make use of a custom CosmosSerializer and call GetItemQueryIterator, the ToStream call is being sent a Microsoft.Azure.Cosmos.SqlQuerySpec that cannot be serialized. Here is a sample that should easily reproduce the problem. Any and all help is appreciated!
using Microsoft.Azure.Cosmos;
using System;
using System.IO;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace CosmosTestApp
{
class Program
{
const string endpoint = "<REPLACE>";
const string key = "<REPLACE>";
const string dbName = "test";
const string containerName = "items";
public static async Task Main(string[] args)
{
var options = new CosmosClientOptions()
{
Serializer = new CosmosJsonSerializer()
};
var client = new CosmosClient(endpoint, key, options);
var dbResponse = await client.CreateDatabaseIfNotExistsAsync(dbName);
var db = dbResponse.Database;
var containerDef = new ContainerProperties(containerName, "/id");
var containerResposne = await db.CreateContainerIfNotExistsAsync(containerDef);
var testContainer = containerResposne.Container;
var testDoc = new TestDoc();
var docResponse = await testContainer.CreateItemAsync(testDoc, new PartitionKey(testDoc.Id));
Console.WriteLine($"Created document {docResponse.Resource.Id}");
var query = testContainer.GetItemQueryIterator<TestDoc>("SELECT * FROM c");
while (query.HasMoreResults)
{
var doc = await query.ReadNextAsync();
foreach(var x in doc.Resource)
{
Console.WriteLine($"Retrieved document {x.Id}");
}
}
}
}
internal class CosmosJsonSerializer : CosmosSerializer
{
public override T FromStream<T>(Stream stream)
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
stream.Close();
var item = JsonSerializer.Parse<T>(memoryStream.ToArray());
return item;
}
}
//This errors on the SqlQuerySpec
public override Stream ToStream<T>(T input)
=> new MemoryStream(JsonSerializer.ToUtf8Bytes(input));
}
internal class TestDoc
{
[JsonPropertyName("id")]
public string Id { get; set; } = "1";
public string TestString { get; set; } = "testing CosmosJsonSerializer";
}
}
EDIT: Bug has been filed and confirmed here: https://github.com/Azure/azure-cosmos-dotnet-v3/issues/575
I am using firebase to track my device on my app. When network is disconnected on my device, I want to notify on the firebase. Based on the below link,
https://firebase.google.com/docs/database/android/offline-capabilities
I am using the following code:
#if __ANDROID__
FirebaseDatabase firebase = FirebaseDatabase.Instance;
FirebaseDatabase.Instance.SetPersistenceEnabled(true);
var userListRef = firebase.GetReference("USERS_ONLINE");
var myUserRef = userListRef.Push();
DatabaseReference connectedRef = firebase.GetReference(".info/connected");
connectedRef.AddValueEventListener(new ValueEventListener());
ValueEventListener Class:
internal class ValueEventListener : Java.Lang.Object, IValueEventListener
{
// public IntPtr Handle => throw new NotImplementedException();
public void Dispose()
{
throw new NotImplementedException();
}
public void OnCancelled(DatabaseError error)
{
throw new NotImplementedException();
}
public void OnDataChange(DataSnapshot snapshot)
{
FirebaseDatabase firebase = FirebaseDatabase.Instance;
DatabaseReference myConnectionsRef = firebase.GetReference("users/joe/connections");
bool connected = Convert.ToBoolean(snapshot.GetValue(true));
myConnectionsRef.KeepSynced(true);
if (connected)
{
DatabaseReference con = myConnectionsRef.Push();
con.OnDisconnect().RemoveValue();
myConnectionsRef.SetValue(Boolean.TrueString);
}
else
{
DatabaseReference con = myConnectionsRef.Push();
con.OnDisconnect().RemoveValue();
myConnectionsRef.SetValue(Boolean.FalseString);
}
}
}
But it is working only if online only. Can anyone help me to resolve this issue.
In my xaml.cs file:
#if __ANDROID__
string myConnectionsRefNode = "users/Truckers/Joe/connections";
Constants.connectedRef = FirebaseDatabase.Instance.GetReference(".info/connected");
Constants.myConnectionsRef = FirebaseDatabase.Instance.GetReference(myConnectionsRefNode);
Constants.connectedRef.AddValueEventListener(new MyProject.Droid.Services.ValueEventListener());
#elif __IOS__
var context = AppDelegate.Instance;
if(context!=null)
{
context.FCMOfflineAccess();
}
#endif
In Constants.cs file:
public static bool IsFCMOffline = true;
public static DatabaseReference connectedRef;
public static DatabaseReference myConnectionsRef;
In Droid/ValueEventListener.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Firebase.Database;
using MyProject.Common;
namespace MyProject.Droid.Services
{
public class ValueEventListener : Java.Lang.Object, IValueEventListener
{
public void OnCancelled(DatabaseError error)
{
throw new NotImplementedException();
}
public void OnDataChange(DataSnapshot snapshot)
{
DatabaseReference con = Constants.myConnectionsRef.Ref;
con.OnDisconnect().SetValue("Offline");
con.SetValue("Online");
}
}
}
In iOS/AppDelegate.cs file:
public void FCMOfflineAccess()
{
var connectedRef = Database.DefaultInstance;
var MyConnectedRef = connectedRef.GetReferenceFromPath("users/Truckers/" + TruckerId);
var connectedRef1 = connectedRef.GetReferenceFromPath(".info/connected");
connectedRef1.ObserveEvent(DataEventType.Value, HandleAction);
}
private void HandleAction(DataSnapshot snapshot)
{
try
{
var connectedRef = Database.DefaultInstance;
var con = connectedRef.GetReferenceFromPath("users/Truckers/Joe");
string[] statOffline = { "Offline" };
string[] keyOffline = { "connections" };
var dataOffline = NSDictionary.FromObjectsAndKeys(statOffline, keyOffline, keyOffline.Length);
con.SetValueOnDisconnect(dataOffline);
string[] statOnline = { "Online" };
string[] keyOnline = { "connections" };
var dataOnline = NSDictionary.FromObjectsAndKeys(statOnline, keyOnline, keyOnline.Length);
con.SetValue(dataOnline);
}
catch (Exception ex)
{
}
}
Hi guys, I wrote the following code to return a pdf report. The code is based on a multi-threading sample code. Can you guys provide some feedback about it, I am new to mulit-thread.
Much appriciate !
Jeffery
public delegate void StreamResultDelegate(Stream streamResults);
public class GenerateReport
{
private StreamResultDelegate callback;
public GenerateReport(StreamResultDelegate _callback)
{
callback = _callback;
}
public void ThreadProc()
{
if (callback != null)
{
callback(Testing());
}
}
public Stream Testing()
{
var reportsService = new ReportsService();
var nameValueCollection = new NameValueCollection();
byte[] pdfReportContents = reportsService.GetReport("/Rocket.Reports/RocketReport", nameValueCollection);
var stream = new MemoryStream(pdfReportContents);
return stream;
}
}
//following
[HandleError]
public class HomeController : Controller
{
private Stream streamTesting = null;
public void StreamResultCallBack(Stream s)
{
streamTesting = s;
}
public FileStreamResult GeneratePdfReport()
{
var g = new GenerateReport(_callback: new StreamResultDelegate(StreamResultCallBack));
var t = new Thread(new ThreadStart(g.ThreadProc));
t.Start();
t.Join();
HttpContext.Response.AddHeader("content-disposition", "attachment; filename=Rockets_List_Printout.pdf");
return new FileStreamResult(streamTesting, "application/pdf");
}}
I would suggest to use async controllers -> MSDN and stop using threads in controller methods=)
My suggestion is make a private object to store the result. It's the easiest way
update
public delegate void work_handler(Stream streamResults);
public class Report
{
public object Result = null;
private Thread workThread = new ...;
public void Work(object param)
{
this.Result = ....;
// signal finish. eg. if winapp use someControl.Invoke(signal_Handler);
// for web app use this.Session["isDone"] = true;
}
// for .net 4.0
private object param = null;
public void Work()
{
// for serial invoking
var taskOption = System.Threading.Tasks.TaskCreationOptions.LongRunning;
System.Threading.Tasks.Task task = new System.Threading.Tasks.Task(() => {... }, taskOption);
// for multiple method parallel invoke
System.Threading.Tasks.Parallel.Invoke(() => { this.Result = genReport(param); }, () => {... }, () => {...});
}
}