Flutter Sqflite multiple table models - sqlite

I've gone through many tutorials and samples on how to implement Sqflite in Flutter. Every other example is done using only one model or database table. As defined in these tutorials:
https://pub.dartlang.org/packages/sqflite
https://www.developerlibs.com/2018/07/flutter-sqlite-database-example.html
http://camposha.info/flutter/sqflite-insert-select-show
As far as I've understood, we need to create as many models and helpers as there are tables. For each database table there will be a model.dart file and a helper.dart file.
My question here, is there any way I can have only one helper for all models?
UPDATE
In helper.dart file there is a future "insert". How can I use the same insert future for all models?
Future<Todo> insert(Todo todo) async {
todo.id = await db.insert(tableTodo, todo.toMap());
return todo;
}

I made a few comments advising the contrary, but I just remembered in my last project I did something like that, it was for Firebase Database, but it would quite similar for sqflite.
I created a BaseItem abstract class, with a key, and all models would descend from that one.
I also created a BaseProvider abstract class, which would require a BaseItem and would define the simple methods to access the model.
The upsert and delete methods lay in FirebaseBaseProvider, which extends BaseProvider.
I'll paste parts of it here (removing quite a lot to make it more understandable):
abstract class BaseItem {
const BaseItem({this.key});
final String key;
}
abstract class BaseProvider<T extends BaseItem> {
Future<List<T>> find();
Future<BaseKey> upsert(T item);
Future<int> delete(T item);
}
abstract class FirebaseBaseProvider<T extends BaseItem> {
// Abstract methods which need to be implemented
T fromMap(BaseKey key, dynamic map);
Map<String, dynamic> toJson(BaseKey key, T item);
Future<DatabaseReference> getReference(BaseKey base) async { ... }
BaseKey compileKey(T item, {String useKey}) { ... }
Future<List<T>> find() async {
List<T> result = new List();
// my implementation doesnt work like this,
// as it's firebase based, but this would
// be the place in a Sqflite implementation to use
// fromMap and load the items
return result;
}
Future<BaseKey> upsert(T item) async {
if (item == null) return null;
BaseKey key = compileKey(item);
(await getReference(key)).set(toJson(key, item));
return key;
}
Future<int> delete(T item) async {
if (item == null) return null;
if (item.key != null && item.key != "") {
(await getReference(compileKey(item))).remove();
}
return 0;
}
}
Then, in order to implement a News model, or any other model, then I would create it by simply defining its contents, like that:
class News extends BaseItem {
News({String key, this.creation, this.messageSubject, this.messageBody}) : super(key: key);
final DateTime creation;
final String messageSubject;
final String messageBody;
bool operator ==(o) => o is News && (o.key == key);
int get hashCode => key.hashCode;
}
And it would require its specific provider, that would implement only the toJson and fromMap methods, like that:
class NewsProvider extends FirebaseBaseProvider<News> {
#override
Map<String, dynamic> toJson(BaseKey key, News news) {
return {
"creation": news.creation,
"messageSubject": news.messageSubject,
"messageBody": news.messageBody,
};
}
#override
News fromMap(BaseKey key, dynamic map) {
DateTime creation = map["creation"] == null ? null : DateTime.tryParse(map["creation"] as String);
return new News(
key: key.child.key,
creation: creation,
messageSubject: map["messageSubject"] as String,
messageBody: map["messageBody"] as String,
);
}
}
In the end, NewProvider is providing the find, upsert and delete methods, but their implementation lay on the abstract class, just one implementation of them for all the models, as you wanted.
Of course, my implementation is quite more complicated than that, both because Firebase requires a different approach to obtain/load the items and also as the find method ends up having to be different in each model specific provider. But yet, quite a lot can be simplified.
What I was saying in the comment is that this last class, the specific NewsProvider which have both toJson and fromMap specific implementations may also be generalized by using annotations in the News model class, but that brings quite a lot of problems and obscurity and - in my opinion, of course - it's not worth it.

I had a similar question about creating tables, all the examples just create one table. I found this webpage that created two tables putting an await on each command.
Like this:
Future _onCreate(Database db, int version) async {
await db.execute("CREATE TABLE table1 (id INTEGER, valuex TEXT)");
await db.execute("CREATE TABLE table2 (id INTEGER, valuey TEXT)");
await db.execute("CREATE TABLE table3 (id INTEGER, valuez TEXT)");
}

Related

ASP.NET Core StackExchange.Redis Filtering Value

I have a .NET Core 6.0 project that I use StackExchange.Redis.
First, I am wondering if I can filter the value coming with Key-Value pair.
When I get the key, I get all the values and after that I am filtering them.
Is there anyway to filter values before getting them all or I have to filter it after getting all the values ?
-- TestModel2.cs
public class TestModel2
{
public List<Product> Products { get; set; }
public List<Category> Categories { get; set; }
}
-- RedisCacheService.cs
public async Task<T> GetAsync<T>(string key) where T : class
{
string value = await _client.GetDatabase().StringGetAsync(key);
return value.ToObject<T>();
}
--ToObject
public static T ToObject<T>(this string value) where T : class
{
return string.IsNullOrEmpty(value) ? null : JsonConvert.DeserializeObject<T>(value);
}
--CacheController.cs
[HttpGet]
[Route("GetProductsByCategoryId")]
public async Task<IActionResult> GetProductsByCategoryId(int id)
{
var models = await _cacheService.GetAsync<List<TestModel2>>("models3");
if (models != null && models?.Count() > 0)
{
try
{
var model = models[0].Products.Where(x => x.CategoryId == id);
if (model != null)
{
return Ok(model);
}
}
catch (Exception)
{
return StatusCode(500);
}
}
return BadRequest("Not Found");
}
If you use a single string, then no: redis doesn't have inbuilt filtering commands (unless you use something like RedisJSON on the server, but that isn't part of core redis). Redis doesn't have rich columnar filtering like you might find in, say, a SQL database. The idea is that you create your own explicit indexing using redis primitives. Storing all the data in a single string and fetching the entire thing out each time is not optimal.
I also found after searching about how to filter and something like that, RediSearch also can be used and integrated with ASP.NET Core Project...
If you are using Docker, RedisLabs/redismod is useful for it...

Dart/Flutter: How to refer to static variables on an abstract class?

I am writing a Firestore Model abstract class that handles common operations. Each model matches a collection in Firestore... I want to refer to that both from a create instance and before the instance exists. But I can't make collectionPath static because I can't override static methods, variables, getters, etc... I get that.
Could create an instance as needed maybe Model().collectionPath but I couldn't get that to work.
Is there a way to do this? How are others making these types of Models?
This is what I'm trying to do:
abstract class Model {
String get collectionPath => "";
void create() async {
CollectionReference collection = FirebaseFirestore.instance.collection(this.collectionPath);
this.reference = await collection.add(this.toMap());
}
static Stream<QuerySnapshot> snapshots() {
return FirebaseFirestore.instance.collection(this.collectionPath).snapshots();
}
//...
class User extends Model {
String get collectionPath => "users";
//...
I want to be able add an existing instance to the firestore:
User user = User("Values");
user.create();
And I'd also like to load all the users before I've created any particular one:
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: User.snapshots(),

Fetching data from large number of independent tables from DB using Entity Framework codefirst

I am creating a Web API. I need to fetch ~50 table data in a single request for some calculations. Since I am fetching these many tables together my Web API response time is high. All the tables are independent. I am using repository pattern and EF code first approach. Any suggestions to improve the response time for the given scenario :
The code looks like below :
public static class FetchDataExtensions
{
public static DbData GetData(this IRepository repo, Request request)
{
return new DbData
{
// fetching data from db using repository pattern
Table1 = repo.GetTable1Data(request.name, request.gender),
Table2 = repo.GetTable2Data(request.gender),
Table3 = repo.GetTable3Data(),
........
Table50 = repo.GetTable50Data()
};
}
}
Repository.cs
public class Repository : IRepository
{
private readonly DBContext context;
public Repository(DBContext context)
{
this.context = context;
}
public Table1 GetTable1Data(string name, string gender)
{
try
{
return context.Table1.Single(a => a.Name.Equals(name.ToString(), StringComparison.OrdinalIgnoreCase)
&& a.Gender.Equals(gender.ToString(), StringComparison.OrdinalIgnoreCase));
}
catch (Exception ex)
{}
}
}
You can use async-await threading concept. Where at a time multiple tread will read data and it will help you to minimize table reading time.
for refer [example of async-await][1]
[1]: https://www.tutorialspoint.com/entity_framework/entity_framework_asynchronous_query.htm
a part from that you can using indexing. See how to apply indexing on table in EF.

Linq to Entities method: Select and ToListAsync

I want to retrieve only a few columns in the query. I want to achieve this query using entity framework:
select FirstName from Employee
However, I am not able to use Select and ToListAsync at the same time. I am getting an error and it looks like I can't use both at the same time.
Is there any other way to do this?
[HttpGet]
public async Task<ActionResult<IEnumerable<Employee>>> GetEmployee()
{
return await _context.Employee.Select(s => s.FirstName).ToListAsync();
}
Gert's comment covered the reason for the error. It will be due to the function expecting a set of Employee entity entities but you are trying to just return a set of employee names by using Select(s => s.FirstName)
If your intention is to return just Employee names, then update the method signature to something like:
public async Task<ActionResult<IEnumerable<string>>> GetEmployeeNames()
Typically situations like this will be when you want to return something like search results or a summary list. You don't necessarily want to return everything about employees (and possibly serializing related data on top of that) just for displaying a list of employee names for users to select from. Still, returning just an employee name isn't much use on its own in case you want to select one of them and request more information or perform an action against them. (Not to mention you could have two "Peter"s as employees...)
In these cases it is helpful to define a simple view model to represent just the data the front end will need. For example the employee name and ID:
[Serializable]
public class EmployeeSummaryViewModel
{
public int EmployeeId { get; set; }
public string Name { get; set; }
}
Then the method to retrieve the employees:
[HttpGet]
public async Task<ActionResult<IEnumerable<EmployeeSummaryViewModels>>> GetEmployeeSummaries()
{
return await _context.Employee
.Select(s => new EmployeeSummaryViewModel
{
EmployeeId = s.EmployeeId,
Name = s.FirstName + " " + s.LastName
}).ToListAsync();
}
The view model is a simple serializable C# class. By leveraging Select to populate it, EF can generate a very efficient query to return just those fields we want. This can include fields from related tables such as a Role or such without needing to worry about eager loading or serializing everything from those related tables.
In the above example the view model contains the ID for each employee returned so we can pass that ID to future calls if needed, such as selecting an employee to load a complete view of, or perform an action against. The server also formats the name. Alternatively you can return the FirstName and LastName and leave the formatting up to the client side.
Based on your code, I think you would need something like this:
[HttpGet]
public async Task<ActionResult> GetEmployee()
{
var employeeNames = await _context.Employee.Select(s => s.FirstName).ToListAsync();
return Ok(employeeNames);
}
Since your return type is ActionResult<IEnumerable<Employee>> .simply use below code to return List of Employee
[HttpGet]
public async Task<ActionResult<IEnumerable<Employee>>> GetEmployee()
{
var result = await _context.Employee
.Select(s => new Employee
{
Name = s.FirstName
}).ToListAsync();
return Ok(result);
}
It's wrong syntax. You just change like this:
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> GetEmployee()
{
return await _context.Employee.Select(s => s.FirstName).ToListAsync();
}
or
[HttpGet]
public async Task<IActionResult> GetEmployee()
{
return Ok(await _context.Employee.Select(s => s.FirstName).ToListAsync());
}

Entity Framework telling me an object is attached when it isn't - why?

I have an object I want to update in the database. I'm new to EF but have done a fair bit of reading. Clearly my approach is wrong, but I don't understand why. FYI the Context referenced throughout is an ObjectContext which is newly instantiated as this code begins and is disposed immediately after. Here is my Update method - the View is the object I want to update in the database and it has 4 ICollection properties whose changes I also wish to save to the database:
public void Update(View view)
{
var original = Read(view.Username, view.ViewId);
original.ViewName = view.ViewName;
ProcessChanges<CostCentre, short>(Context.CostCentres, original.CostCentres, view.CostCentres, "iFinanceEntities.CostCentres", "CostCentreId");
ProcessChanges<LedgerGroup, byte>(Context.LedgerGroups, original.LedgerGroups, view.LedgerGroups, "iFinanceEntities.LedgerGroups", "LedgerGroupId");
ProcessChanges<Division, byte>(Context.Divisions, original.Divisions, view.Divisions, "iFinanceEntities.Divisions", "DivisionId");
ProcessChanges<AnalysisCode, short>(Context.AnalysisCodes, original.AnalysisCodes, view.AnalysisCodes, "iFinanceEntities.AnalysisCodes", "AnalysisCodeId");
int test = Context.SaveChanges();
}
First I get the original from the database because I want to compare its collections with the new set of collections. This should ensure the correct sub-objects are added and removed. I compare each collection in turn using this ProcessChanges method:
private void ProcessChanges<TEntity, TKey>(ObjectSet<TEntity> contextObjects, ICollection<TEntity> originalCollection, ICollection<TEntity> changedCollection, string entitySetName, string pkColumnName)
where TEntity : class, ILookupEntity<TKey>
{
List<TKey> toAdd = changedCollection
.Select(c => c.LookupKey)
.Except(originalCollection.Select(o => o.LookupKey))
.ToList();
List<TKey> toRemove = originalCollection
.Select(o => o.LookupKey)
.Except(changedCollection.Select(c => c.LookupKey))
.ToList();
toAdd.ForEach(a =>
{
var o = changedCollection.Single(c => c.LookupKey.Equals(a));
AttachToOrGet<TEntity, TKey>(entitySetName, pkColumnName, ref o);
originalCollection.Add(o);
});
toRemove.ForEach(r =>
{
var o = originalCollection.Single(c => c.LookupKey.Equals(r));
originalCollection.Remove(o);
});
}
This compares the new collection to the old one and works out which objects to add and which to remove. Note that the collections all contain objects which implement ILookupEntity.
My problems occur on the line where I call AttachToOrGet. This method I got from elsewhere on stackoverflow. I'm using this because I was often getting a message saying that "An object with the same key already exists in the ObjectStateManager" when attaching a new subobject. Hopefully you'll understand my confusion around this when I post the code of this method below:
public void AttachToOrGet<TEntity, TKey>(string entitySetName, string pkColumnName, ref TEntity entity)
where TEntity : class, ILookupEntity<TKey>
{
ObjectStateEntry entry;
// Track whether we need to perform an attach
bool attach = false;
if (Context.ObjectStateManager.TryGetObjectStateEntry(new EntityKey(entitySetName, pkColumnName, entity.LookupKey), out entry))
//if (Context.ObjectStateManager.TryGetObjectStateEntry(Context.CreateEntityKey(entitySetName, entity), out entry))
{
// Re-attach if necessary
attach = entry.State == EntityState.Detached;
// Get the discovered entity to the ref
entity = (TEntity)entry.Entity;
}
else
{
// Attach for the first time
attach = true;
}
if (attach)
Context.AttachTo(entitySetName, entity);
}
Basically this is saying if the entity is not already attached then attach it. But my code is returning false on the Context.ObjectStateManager.TryGetObjectStateEntry line, but throwing an exception on the final line with the message "An object with the same key already exists in the ObjectStateManager". To me this is paradoxical.
As far as I'm concerned I'm trying to achieve something very simple. Something it would take 20 minutes to write a stored procedure for. A simple database update. Frankly I don't care what is attached and what isn't because I don't wish to track changes or create proxies or lazy load or do anything else EF offers me. I just want to take a very simple object and update the database using a minimal number of trips between servers. How is this so complicated? Please someone help me - I've spent a whole day on this!
Update
Here's my ILookupEntity class:
public interface ILookupEntity<TKey>
{
TKey LookupKey { get; }
string DisplayText { get; }
}
Here's how it is implemented in CostCentre:
public partial class CostCentre : IFinancialCode, ILookupEntity<short>
{
#region IFinancialCode Members
public short ID { get { return CostCentreId; } }
public string DisplayText { get { return string.Format("{0} - {1}", Code, Description); } }
#endregion
#region ILookupEntity Members
public short LookupKey
{
get { return ID; }
}
#endregion ILookupEntity Members
}
Well, I've worked through this and found a solution, but I can't say I understand it. The crucial ingredient came when I was performing a check after the comment by #Slauma. I wanted to check I was using the correct entity set name etc so I included the following lines near the top of my AttachToOrGet method:
var key = new EntityKey(entitySetName, pkColumnName, entity.LookupKey);
object temp;
if (!Context.TryGetObjectByKey(key, out temp))
throw new Exception(string.Format("No entity was found in {0} with key {1}", entitySetName, entity.LookupKey));
Bizarrely this alone resolved the problem. For some reason, once I'd called the TryGetObjectByKey then the ObjectStateManager.TryGetObjectStateEntry call actually started locating the attached entity. Miraculous. I'd love it if anyone can explain this.
By the way, I also needed to include the following code, but that's just because in my case the modelled entities are located in a separate assembly from the context itself.
Assembly assembly = typeof(CostCentre).Assembly;
Context.MetadataWorkspace.LoadFromAssembly(assembly);

Resources