Saving a List to SQLite.NET using SQLiteAsyncConnection - c#

Working on a project thats Stores items to my sqlDb created it following this video by James Montemagno https://www.youtube.com/watch?v=XFP8Np-uRWc&ab_channel=JamesMontemagno my issue now comes when I'm trying to save a list to the sqlDb it shows that it was added however when i retrieve my data my List prop is null.
public class UserTask{
[PrimaryKey]
public string ID { get; set; }
public string Title { get; set; }
[TextBlob("TagBlobbed")]
public List<string> TagsList { get; set; }
public string TagBlobbed { get; set; }
public string Details { get; set; }
[Ignore]
public string Comment { get; set; }
public UserTask()
{
TagsList = new();
}
}
public static class PlannerDataService
{
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null) return;
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "DbTasks.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<UserTask>();
}
public static async Task AddNewTask(UserTask t)
{
await Init();
var task = new UserTask()
{
ID = t.ID,
TagsList = t.TagsList,
Details = t.Details,
Title = t.Title
};
await db.InsertAsync(task);
}
public static async Task<List<UserTask>> GetUserTasks()
{
await Init();
var tasks = await db.Table<UserTask>().ToListAsync();
var t = tasks.OrderByDescending(a => a.ID).ToList();
return t;
}
public static async Task RemoveTask(string id)
{
await Init();
await db.DeleteAsync<UserTask>(id);
}
public static async Task UpdateTask(UserTask t)
{
await Init();
var task = new UserTask()
{
ID = t.ID,
TagsList = t.TagsList,
Details = t.Details,
Title = t.Title
};
await db.UpdateAsync(task);
}
}
I've seen + read questions similar to this and I've tried following their advice to no luck which is why I'm posting for a better solution without changing much of my code.

Related

What causes a method to pass null values?

So I wrote a method of code and it pulls from the database correctly (I am using Dapper), but it doesnt pass off to the next method. Can anyone tell me why and what I am doing wrong? Not quite understanding what I am doing wrong here. I have tried a few different ways including below and making and IEnumerable list. I can see the variables in the logger so I know I am pulling them correctly, just not sure why they arent sending to the CheckSite().
public class UptimeService
{
private readonly ILogger<UptimeService> _logger;
private readonly IWebsiteData _webdb;
private readonly IUptimeData _db;
public UptimeService(IWebsiteData webdb, IUptimeData db ,ILogger<UptimeService> logger)
{
_webdb = webdb;
_logger = logger;
_db= db;
}
public class SiteResponse
{
public int Websiteid { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool Status { get; set; }
public long ResponseTime { get; set; }
}
public async Task GetAllWebsites()
{
var websites = await _webdb.GetWebsites();
foreach (var website in websites)
{
_logger.LogInformation($"WEBSITE::::: {website.Url} | {website.Name} | {website.Websiteid}");
CheckSite(website.Url, website.Name, website.Websiteid);
}
return ;
}
public SiteResponse CheckSite(string Url, string Name, int Websiteid)
{
var result = new SiteResponse();
var stopwatch = new Stopwatch();
stopwatch.Start();
var client = new HttpClient();
_logger.LogInformation(
$"TEST URL: {result.Url}");
try
{
var checkingResponse = client.GetAsync(Url).Result;
result.Status = checkingResponse.IsSuccessStatusCode &&
checkingResponse.StatusCode == HttpStatusCode.OK;
}
catch
{
result.Status = false;
// offline
}
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
result.ResponseTime = elapsed;
if (result.Status)
{
// archive record
RecordToDb(result);
}
else
{
_logger.LogInformation(
$"Status is {result.Status}");
}
return result;
}
public async void RecordToDb(SiteResponse response)
{
var newRecord = new UptimeModel
{
Time = DateTime.Now,
Status = response.Status,
ResponseTime = (int)response.ResponseTime,
Websiteid = response.Websiteid,
Name = response.Name,
};
_logger.LogInformation(
$"Trying to Save {response.Name}");
await _db.InsertUptime(newRecord);
}
}
If the result.Url is empty here:
_logger.LogInformation($"TEST URL: {result.Url}");
that's because it's a new instance of SiteResponse() method.
If it is showing as null, you'll need to create constructors on the class. Here is an example:
public class SiteResponse
{
public SiteResponse(){ }
public SiteResponse(string url){
Url = url;
}
public int Websiteid { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool Status { get; set; }
public long ResponseTime { get; set; }
}
and then initialize the new one like:
var result = new SiteResponse(Url);
Based on the comments below, I would refactor to something like this.
public class UptimeService
{
private readonly ILogger<UptimeService> _logger;
private readonly IWebsiteData _webdb;
private readonly IUptimeData _db;
public UptimeService(IWebsiteData webdb, IUptimeData db ,ILogger<UptimeService> logger)
{
_webdb = webdb;
_logger = logger;
_db= db;
}
public class SiteResponse
{
public int Websiteid { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool Status { get; set; }
public long ResponseTime { get; set; }
}
public async Task GetAllWebsites()
{
var websites = await _webdb.GetWebsites();
foreach (var website in websites)
{
_logger.LogInformation($"WEBSITE::::: {website.Url} | {website.Name} | {website.Websiteid}");
await CheckSite(website);
}
return ;
}
public async Task CheckSite(SiteResponse siteResponse)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var client = new HttpClient();
_logger.LogInformation(
$"TEST URL: {siteResponse.Url}");
try
{
var checkingResponse = await client.GetAsync(siteResponse.Url);
siteResponse.Status = checkingResponse.IsSuccessStatusCode &&
checkingResponse.StatusCode == HttpStatusCode.OK;
}
catch
{
siteResponse.Status = false;
// offline
}
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
siteResponse.ResponseTime = elapsed;
if (siteResponse.Status)
{
// archive record
RecordToDb(siteResponse);
}
else
{
_logger.LogInformation(
$"Status is {siteResponse.Status}");
}
return;
}
public async void RecordToDb(SiteResponse response)
{
var newRecord = new UptimeModel
{
Time = DateTime.Now,
Status = response.Status,
ResponseTime = (int)response.ResponseTime,
Websiteid = response.Websiteid,
Name = response.Name,
};
_logger.LogInformation(
$"Trying to Save {response.Name}");
await _db.InsertUptime(newRecord);
}
}

includeDetails not working in ABP Framework

I have a simple project (ABP version: 3.1.2, Database: EF Core).
I run GetAsync:
var author = await _authorRepository.GetAsync(id, includeDetails: true);
But author.Films was not included. What may I have forgotten?
Author (AggregateRoot):
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
public List<Film> Films { get; set; }
private Author()
{
Films = new List<Film>();
/* This constructor is for deserialization / ORM purpose */
}
internal Author(
Guid id,
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
: base(id)
{
Name = name;
BirthDate = birthDate;
ShortBio = shortBio;
Films = new List<Film>();
}
}
Film (Entity):
public class Film : Entity<Guid>
{
public virtual Guid AuthorId { get; internal set; }
public string Name { get; set; }
}
SeedAsync in DataSeeder class (I checked whether data exists in database after DbMigrator ran, there are these data in tables as expected):
public async Task SeedAsync(DataSeedContext context)
{
if (await _authorRepository.GetCountAsync() == 0)
{
var authorId = _guidGenerator.Create();
await _authorRepository.InsertAsync(
new Author(authorId, "J. R. R. Tolkien", DateTime.Now.AddYears(-60), "bio1"),
autoSave: true
);
await _filmRepository.InsertAsync(
new Film { AuthorId = authorId, Name = "The Return of the King1" },
autoSave: true);
await _filmRepository.InsertAsync(
new Film { AuthorId = authorId, Name = "The Return of the King2" },
autoSave: true);
await _filmRepository.InsertAsync(
new Film { AuthorId = authorId, Name = "The Return of the King3" },
autoSave: true);
}
}
AuthorAppService:
public class AuthorAppService : BookStoreAppService, IAuthorAppService
{
private readonly IAuthorRepository _authorRepository;
private readonly AuthorManager _authorManager;
public AuthorAppService(
IAuthorRepository authorRepository,
AuthorManager authorManager)
{
_authorRepository = authorRepository;
_authorManager = authorManager;
}
public async Task<AuthorDto> GetAsync(Guid id)
{
var author = await _authorRepository.GetAsync(id, includeDetails: true);
return ObjectMapper.Map<Author, AuthorDto>(author);
}
}
From https://docs.abp.io/en/abp/latest/Best-Practices/Entity-Framework-Core-Integration:
Do create a IncludeDetails extension method for the IQueryable<TEntity> for each aggregate root which has sub collections.
...
Do override WithDetails method of the repository for aggregates root which have sub collections.
public static class AuthorEfCoreQueryableExtensions
{
public static IQueryable<Author> IncludeDetails(this IQueryable<Author> queryable, bool include = true)
{
if (!include)
{
return queryable;
}
return queryable
.Include(x => x.Films);
}
}
public class AuthorRepository : EfCoreRepository<IMyDbContext, Author, Guid>, IAuthorRepository
{
...
public override IQueryable<Author> WithDetails()
{
return GetQueryable().IncludeDetails(); // Uses the extension method defined above
}
}

Blank values in DB from async function with multiple awaits

I have a model as so
public class Action ()
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Completed { get; set; }
public DateTime CompletedOn { get; set; }
public virtual User CompletedBy { get; set; }
}
And an async function as so:
public async Task<ActionResult> MarkActionCompleted(int id)
{
using(MyDbContext db = new MyDbContext())
{
UserManager userManager = new UserManager(new UserStore(db));
var user = await userManager.FindByIdAsync(User.Identity.GetUserId());
var action = await db.Actions.SingleOrDefaultAsync(x => x.Id == id);
if (action == null)
{
return new HttpNotFoundResult();
}
action.Completed = true;
action.CompletedOn = DateTime.Now;
action.CompletedBy = user;
await db.SaveChangesAsync();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
}
I've been noticing, by looking at all the entries on my DB that about 2% (441/21547) of records are marked completed but both the CompletedBy and CompletedOn are null. I've been trying to wrap my head around how this can be. This is the only function that modifies this table.
So my question is, Am i using async/await incorrectly? Is it possible that SaveChangesAsync is being called before the user is retrieved? Or is perhaps an issue with Context? Should I be using ConfigureAwait(false) somewhere?

async Lazy<T> getting results right away

I have the following:
public class User
{
private readonly Lazy<Task<List<ReminderDb>>> _reminders;
public SmsUserDb()
{
// Get _reminderReader from IoC
_reminders = new Lazy<Task<List<ReminderDb>>>(async () => (List<ReminderDb>)await _reminderReader.Get(UserId));
}
public string UserId { get; set; }
public Task<List<ReminderDb>> Reminders => _reminders.Value;
}
When I instantiate an object Like so:
var n = new SmsUserDb {UserId = "123456"};
var rems = await n.Reminders;
This code works and I see that n.Reminders ="Waiting for activation" until I hit await n.Reminders line.
However, when I query this User object from cache like so:
var n1 = await _userReader.GetUserFromCache("+17084556675"); // return SmsUserDb
var n2 = await n1.Reminders;
when it hits GetUserFromCache() it right away calls _reminderReader.Get(UserId) which calls cache again to get reminders. Then it simply times out. So Lazy doesn't work and most likely causes a deadlock.
public async Task<SmsUserDb> GetUserFromCache(string phoneNumber)
{
var hash = CachedObjectType.smsuser.ToString();
var fieldKey = string.Format($"{CachedObjectType.smsuser.ToString()}:user-{phoneNumber}");
var result = await _cacheUserService.GetHashedAsync(hash, fieldKey);
return result;
}
private async Task<List<ReminderDb>> GetRemindersFromCache(string userId)
{
var hash = CachedObjectType.smsreminder.ToString();
var fieldKey = string.Format($"{CachedObjectType.smsreminder.ToString()}:user-{userId}");
var result = await _cacheService.GetHashedAsync(hash, fieldKey);
return result ?? new List<ReminderDb>();
}
What could be the problem?
This all works fine in my test code (shown below). Therefore the error must be in some code that you haven't shown us.
Here's some sample code that works - it proves that Lazy<T> is working correctly. Therefore the error is elsewhere in your code.
You must post a compilable repro in order for anyone to help you with this.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Demo
{
public class ReminderDb
{
public string Value;
}
public class ReminderReader
{
public async Task<List<ReminderDb>> Get(string userId)
{
Console.WriteLine("In Get() for " + userId);
await Task.Delay(1000);
Console.WriteLine("Returning from Get()");
return new List<ReminderDb>{new ReminderDb{Value = userId}};
}
}
public class Cache
{
public void Add(string key, SmsUserDb value)
{
_cache.Add(key, value);
}
public async Task<SmsUserDb> GetHashedAsync(string key)
{
await Task.Delay(1000);
return _cache[key];
}
readonly Dictionary<string, SmsUserDb> _cache = new Dictionary<string, SmsUserDb>();
}
public class SmsUserDb
{
readonly Lazy<Task<List<ReminderDb>>> _reminders;
readonly ReminderReader _reminderReader = new ReminderReader();
public SmsUserDb()
{
_reminders = new Lazy<Task<List<ReminderDb>>>(async () => (List<ReminderDb>) await _reminderReader.Get(UserId));
}
public string UserId { get; set; }
public Task<List<ReminderDb>> Reminders => _reminders.Value;
}
static class Program
{
static async Task Main()
{
var db = new SmsUserDb(){UserId = "user ID"};
var cache = new Cache();
cache.Add("key", db);
Console.WriteLine("Press RETURN to await cache.GetHashedAsync()");
Console.ReadLine();
var result = await cache.GetHashedAsync("key");
Console.WriteLine("Press RETURN to await Reminders[0].Value");
Console.ReadLine();
Console.WriteLine((await result.Reminders)[0].Value);
}
}
}
Found the problem. I was using the same class to write to cache, that is:
public class User
{
private readonly Lazy<Task<List<ReminderDb>>> _reminders;
public SmsUserDb()
{
// Get _reminderReader from IoC
_reminders = new Lazy<Task<List<ReminderDb>>>(async () => (List<ReminderDb>)await _reminderReader.Get(UserId));
}
public string UserId { get; set; }
public Task<List<ReminderDb>> Reminders => _reminders.Value;
}
After using a different class for writing (below) and reading (above), all works as desired. thanks
public class CacheUser {
public string UserId { get; set; }
public List<ReminderDb> Reminders {get; set; }
}

Dependent Object Creation

Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);

Categories