Indexed db blazor

TLDR: I made IndexedDb work just like LINQ in C#. The code also uses attributes to build everything for you. Using client side storage made easy!



I will be posting the open source code on my github here this weekend:

https://github.com/magiccodingman



I simply haven't posted it yet because I need to remove some of my encryption keys and seperate some private company logic from my business out of the code.



Thank you to nwestfall for his BlazorDB work, which was a frok of Blazor.IndexedDB.Framework by Reshiru

https://github.com/nwestfall/BlazorDB



My project heavily worked off of the work that nwestfall created. I additionally went down the route of utilizing Dexie.js as Dexie reliably gets updates. So, what's special about what I made? First, any of the projects I found were left archived, dead, or didn't have the ability to query the way I'd want. There's a ton of ways I can make my code more efficient, but honestly it's good enough for what I need it for as my main goal was to make IndexedDb easier to use. I want to use IndexedDb just like I use my LINQ to SQL code.



Here's how it works:



1.) Setup some services in your program.cs and create as many databases as you want. Don't worry about creating the schemas as my code will auto build your store schemas. Additionally I will likely remove the EncryptionFactory service, but as I'll show later, I've added methods to encrypt your data. It's not the most secure method in the world, but it's better than native no encryption:

builder.Services.AddEncryptionFactory(); builder.Services.AddBlazorDB(options => { options.Name = DbNames.Client; options.Version = "2.4"; options.EncryptionKey = "256 bit encryption key of your choice"; options.StoreSchemas = SchemaHelper.GetAllSchemas(DbNames.Client); });

2.) Get your manager like so:

var manager = await _dbFactory.GetDbManager(DbNames.Client);



3.) Setup your model with attributes. Note that the Encrypt attribute uses your encryption key that you provide. It will encrypt any item as it goes into the IndexedDb database but you will need to make a specific call I made to decrypt it when you choose to decrypt it. I don't decrypt it automatically on each read or that'd be too much overhead:

[SchemaAnnotationDb("Person", DbNames.Client)] public class Person { [PrimaryKeyDb] public int id { get; set; } [IndexDb] public string Name { get; set; } [IndexDb] public int Age { get; set; } [IndexDb] public int TestInt { get; set; } [UniqueIndexDb] public Guid guid { get; set; } = Guid.NewGuid(); [EncryptDb] public string Secret { get; set; } [NotMappedDb] public string DoNotMapTest { get; set; } }



4.) Now you can call all kinds of methods similar if not exactly like you would with LINQ. Note that based on the attributes on your model, there's a ton of smart logic that handles the table names for you and much more. Here's some examples:

Adding a single item

Guid transactionId = await manager.Add(new Person { Name = "nakedMan1", Age = 45, guid = Guid.NewGuid(), Secret = "Hello World!", DoNotMapTest = "I'm naked! But nobody can know!" });



Adding bulk items

Person[] persons = new Person[] { new Person { Name = "nakedMan2", TestInt = 3, Age = 46, guid = Guid.NewGuid(), Secret = "Hello World!", DoNotMapTest = "I'm naked! But nobody can know!" }, new Person { Name = "nakedMan3", TestInt = 3 , Age = 47, guid = Guid.NewGuid(), Secret = "Hello World!", DoNotMapTest = "I'm naked! But nobody can know!" } }; var TransactionId = await manager.AddRange(persons);



Make queries with Where

IEnumerable<Person> people = await manager.Where<Person>(x => x.Age == 46 || x.Age == 47); IEnumerable<Person> people = await manager.Where<Person>(x => x.Age >= 21 && x.Name.StartsWith("nak", StringComparison.OrdinalIgnoreCase) && x.Age < 46 || x.Age > 47 && !x.Name.StartsWith("nak", StringComparison.OrdinalIgnoreCase)); IEnumerable<Person> people = await manager.Where<Person>(x => x.Age >= 21 && x.Name.Equals("nakedman", StringComparison.OrdinalIgnoreCase)); IEnumerable<Person> people = await manager.Where<Person>(x => x.Age >= 21 && x.Name.Contains("nAkedM", StringComparison.OrdinalIgnoreCase));



The Where statements were honestly really annoying to code. I had to build a custom protocol and a lot of smart logic to properly translate the predicate dynamically to Dexie.Js. But it does actually build behind the scenes a dynamic query that's as efficient as it gets when writing your code this way. I'm sure if you wanted extreme performance you can do better writing it directly in JavaScript when working with IndexedDb, but I care more about the speed of which I'm able to code and the readability of my code.



I've added a lot more logic as well that I'm not showing here. I'm adding repository methods, better versioning for auto updating the version, and there's a lot of things I've added as well that I didn't write out here. I'm going to document everything on GitHub, so I'm not going to waste my time writing it here.



I wanted to build my own custom version for a few reasons. But majorly, I need a very custom solution. I have a very unique software in which I have very weird unique needs. Which also means I need to build really fun code haha. But this code is meant to be great with Blazor WebAssembly and I'm going to open source the code while hopefully finding others who can help me make it better.



Some ideas I have are auto syncing when online with my API. I want to implement this in a way as well that it works similarly to cache memory methods when wrapping API calls so that when a call is made (usually done on server-side web applications), if it recognizes your offline with no internet, it can properly store logic and send the requests later.



Whether you're using this for a game, business use cases, safety implementations, offline mode capabilities, I really plan to use and abuse the capabilities of Indexed Db in my Blazor WebAssembly application.



But honestly, I also built this in a way that if someone used my code, they could likely make similar LINQ like methods in JavaScript as well. But I was pretty excited about getting a good version of this to work and wanted to share. Maybe nobody cares and I'm a lonely nerd, but if someone does care, if you have any suggestions as to what I should add, what would be cool to see, or if you're just interested in contributing to the project when I open source it this weekend, let me know!