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);
}
}
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.
When calling the method from the .cs file, the app gets stuck on the await line; but if I call it from the .razor it works flawlessy.
.cs
public AccountService(IUserData userData)
{
_userData = userData;
}
...
public async Task<bool> Validate(string userId, string password)
{
...
try
{
List<UserModel> users = new List<UserModel<();
users = await _userData.GetUsers();
//NEVER GETS HERE
return true;
}
catch (Exception ex)
{
return false;
}
...
}
.razor
#inject IUserData _db;
#code {
private List<UserModel> users;
...
protected override async Task OnInitializedAsync()
{
users = await _db.GetUsers();
}
...
UserData
public class UserData : IUserData
{
private readonly ISqlDataAccess _db;
public UserData(ISqlDataAccess db)
{
_db = db;
}
public Task<List<UserModel>> GetUsers()
{
string sql = "Select *from dbo.Users";
return _db.LoadData<UserModel, dynamic>(sql, new { });
}
...
}
IUserData
public interface IUserData
{
Task<List<UserModel>> GetUsers();
...
}
DBAccess
public async Task<List<T>> LoadData<T, U>(string sql, U parameters)
{
string connectionString = _config.GetConnectionString(ConnectionStringName);
using (IDbConnection connection = new SqlConnection(connectionString))
{
var data = await connection.QueryAsync<T>(sql, parameters); //I GET STUCK HERE
return data.ToList();
}
}
IDBAccess
Task<List<T>> LoadData<T, U>(string sql, U parameters);
PS
I updated this post https://stackoverflow.com/questions/68225154/implementing-an-interface-on-a-class-with-dependency-injection with this question, but sinced I had already marked it as answered I decided to make a new one
Your problem is how your code is calling the asynchronous method:
if (((AccountService)AccountService).Validate(user.UserCredentials, user.Password).Result)
The .Result may seem weird, but otherwise I get an error: can't convert ...Task to bool
The proper solution for this error is to use await, not Result:
if (await ((AccountService)AccountService).Validate(user.UserCredentials, user.Password))
Using Result can cause deadlocks.
I've been following the official documentation here:
https://learn.microsoft.com/en-us/azure/cosmos-db/sql-api-get-started#Query
But I can't figure out how to correctly use a LINQ expression instead on an SQL string. I experimented with GetItemLinqQueryable, but I don't know if is the right way to use it. Also is not async.
var db = Client.GetDatabase(databaseId);
var container = db.GetContainer(containerId);
var q = container.GetItemLinqQueryable<Person>();
var result = q.Where(p => p.Name == "Name").ToList();
Is this the right way to use LINQ with Cosmos v3, and how to make it async?
You would use ToFeedIterator() and FeedIterator<T>.ReadNextAsync().
var db = Client.GetDatabase(databaseId);
var container = db.GetContainer(containerId);
var q = container.GetItemLinqQueryable<Person>();
var iterator = q.Where(p => p.Name == "Name").ToFeedIterator();
var results = await iterator.ReadNextAsync();
If your application follows a layered architecture and you'd like to give your domain layer full control over the query then it's possible to wrap cosmos IQueryable<Person> with a custom IQueryProvider that implements IAsyncEnumerable e.g.
By doing that you can hide the implementation details of asynchronously iterating over the result from your domain layer.
Persistence layer
public class PersonRepository
{
public IQueryable<Person> Persons => _cosmosContainer.GetItemLinqQueryable<Person>().ToCosmosAsyncQueryable();
}
Domain layer
var persons = await _personRepository.Persons
.Where(p => p.Name == "Name")
.AsAsyncQueryable()
.ToListAsync(cancellationToken);
ToListAsync is available from System.Linq.Async that can be referenced from your domain layer
Domain layer extensions
public static IAsyncEnumerable<T> AsAsyncQueryable<T>(this IQueryable<T> queryable)
{
return (IAsyncEnumerable<T>)queryable;
}
Persistence layer extensions
internal static class CosmosAsyncQueryableExtensions
{
internal static IQueryable<T> ToCosmosAsyncQueryable<T>(this IOrderedQueryable<T> source)
{
return new CosmosAsyncQueryable<T>(source);
}
}
internal class CosmosAsyncQueryable<TResult> : IEnumerable<TResult>, IQueryable<TResult>, IAsyncEnumerable<TResult>
{
private readonly IQueryable<TResult> _queryable;
public CosmosAsyncQueryable(IQueryable<TResult> queryable)
{
_queryable = queryable;
Provider = new CosmosAsyncQueryableProvider(queryable.Provider);
}
public Type ElementType => typeof(TResult);
public Expression Expression => _queryable.Expression;
public IQueryProvider Provider { get; }
public IEnumerator<TResult> GetEnumerator() => _queryable.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _queryable.GetEnumerator();
public async IAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
var iterator = _queryable.ToFeedIterator();
while (iterator.HasMoreResults)
{
foreach (var item in await iterator.ReadNextAsync(cancellationToken))
{
yield return item;
}
}
}
}
internal class CosmosAsyncQueryableProvider : IQueryProvider
{
private readonly IQueryProvider _provider;
public CosmosAsyncQueryableProvider(IQueryProvider provider) => _provider = provider;
public IQueryable<TElement> CreateQuery<TElement>(Expression expression) =>
new CosmosAsyncQueryable<TElement>(_provider.CreateQuery<TElement>(expression));
public IQueryable CreateQuery(Expression expression) => CreateQuery<object>(expression);
public object Execute(Expression expression) => _provider.Execute(expression);
public TResult Execute<TResult>(Expression expression) => _provider.Execute<TResult>(expression);
}
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
I have an asp.net webapi hosted using OWin and I use ninject for DI. Here is how I inject DbContext:
kernel.Bind<IAuthRepository>().To<AuthRepository>();
kernel.Bind<AuthDbContext>().ToSelf().InRequestScope();
I have a repository class that I use the context as below:
private AuthDbContext _ctx { get; set; }
public AuthRepository(AuthDbContext ctx)
{
_ctx = ctx;
}
There is this method in repository that I call to change some data:
var res = await _ctx
.Clients
.FirstOrDefaultAsync(clientEntity => clientEntity.UserRef.Id == p);
//do the modification
_ctx.Entry(res).State = EntityState.Modified;
_ctx.SaveChanges();
return res;
It saves the data correctly in db, but after, when I call another action and get that saved entity, I see that changes are not in the context.
public async Task<Client> FindClientById(Guid clientId)
{
return await _ctx
.Clients
.FirstOrDefaultAsync(clientEntity => clientEntity.Id == clientId);
}