For Blazor WebAssembly I came up with the idea of using SQLite. This question mentions it is not possible. Is it possible to use SQLite in Blazor WebAssembly and if so, how?
As of .NET 6, you can use include native dependencies in Blazor WebAssembly, one example in fact being SQLite. See example code here: https://github.com/SteveSandersonMS/BlazeOrbital/blob/6b5f7892afbdc96871c974eb2d30454df4febb2c/BlazeOrbital/ManufacturingHub/Properties/NativeMethods.cs#L6
Starting .Net 6, it is now possible to use SQLite with Blazor Web Assembly .
Here are the steps,
Add reference to following Nuget packages.
a. Microsoft.EntityFrameworkCore.Sqlite
b. SQLitePCLRaw.bundle_e_sqlite3 - Currently in preview as of posting this answer. This package is to avoid NativeFileReference of e_sqlite3.o.
Add the following in .csproj to avoid unwanted warning from popping out.
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EmccExtraLDFlags>-s WARN_ON_UNDEFINED_SYMBOLS=0</EmccExtraLDFlags>
Add the following code in Program.cs. This is required to avoid runtime exception - Could not find method 'AddYears' on type 'System.DateOnly'
public partial class Program
{
/// <summary>
/// FIXME: This is required for EF Core 6.0 as it is not compatible with trimming.
/// </summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
private static Type _keepDateOnly = typeof(DateOnly);
}
I'm using sqlite inmemory store. Here is the Code Example.
Database Model:
public class Name
{
public int Id { get; set; }
public string FullName { get; set; }
}
Database Context:
public class TestDbCOntext : DbContext
{
public DbSet<Name> Names { get; set; } = default!;
public TestDbCOntext(DbContextOptions<TestDbCOntext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Name>().ToTable("Names");
modelBuilder.Entity<Name>().HasIndex(x => x.FullName);
modelBuilder.Entity<Name>().Property(x => x.FullName).UseCollation("nocase");
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.LogTo(Console.WriteLine, LogLevel.Warning)
.EnableDetailedErrors()
.EnableSensitiveDataLogging(true);
}
}
Page/Component:
<button #onclick="RunEfCore">Run Ef Core</button>
#code {
private async Task RunEfCore()
{
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connection = new SqliteConnection(connectionStringBuilder.ToString());
var options = new DbContextOptionsBuilder<TestDbCOntext>()
.UseSqlite(connection)
.Options;
using var db = new TestDbCOntext(options);
db.Database.OpenConnection();
await db.Database.EnsureCreatedAsync();
var nextId = db.Names!.Count() + 1;
db.Names.Add(new Name { Id = nextId, FullName = "Abdul Rahman" });
await db.SaveChangesAsync();
Console.WriteLine();
await foreach (var name in db.Names.AsAsyncEnumerable())
{
Console.WriteLine(name.FullName);
}
db.Database.CloseConnection();
}
}
For persistance, you can make use of IndexedDB from browser and sync to save in your server.
Sample Working Demo can found in my Github Repo - BlazorWasmEfCore
Live Demo
Refer the github issue for complete history.
Steve Sanderson Video Reference
Screenshot:
Points to consider:
The below details are taken from following stackoverflow answer.
A good rule of programming is KISS - Keep it Simple. So if the requirement of your app is satisfied by Linq to Objects, then complicating it with SQLite would seem to be the wrong thing to do.
However, in the video Steve S. does come up with requirement parameters that lend themselves to using SQLite - the application needs to process:
lots of data
in the client
offline
quickly
The use of SQLite here is not for persisting beyond client app memory.
So the answer to your question on the advantage of a Blazor app using SQLite is simply:
If an app needs the functionality of an in-memory relational database but is trying to avoid using one, it will either be reinventing the wheel in its implementation, or missing necessary functionality.
If an app doesn't need the functionality of an in-memory database but is using one, it will be introducing unnecessary complexity (cost).
Your Blazor WebAssembly C# code still runs in the sandbox of the browser, that means it is not allowed to open files on the local drive.
Blazor WebAssembly has the same access to the machine as any regular website.
Even if someone was to port SQLite to WebAssembly you would not be able to open a database file.
For storage on the client computer you are limited to Local Storage, it is limited to 5 MB (might be different per browser brand) and can only contain strings. But it is not a reliable option as the data will be removed when the users clears the cache, browser history etc..
The only option you have is storing data on the server.
Related
I have the (almost) worst of multi tenancy. I'm building a asp.net core website that I'm porting a bunch of pokey little intranet sites to. Each subsite will be an asp.net Area. I have an IdentityContext for the Identity stuff. I have multiple copies of vendor databases, each of those with multiple tenants. The ApplicationUserclass has an OrgCode property that I want to use to switch the db context.
I can see myself needing something that maps User.OrgCode and Area to a Connection string
There are many partial examples of this on Stack Overflow. I am very confused after an afternoons reading. The core of it seams to be:
remove DI dbcontext ref from the constructor args.
Instantiate the dbcontext in the controller constructor.
Use dbcontext as before.
Am I on the right track?
Any coherent examples?
Edit 2020/07/09
This has unfortunately become more pressing.
The Identity database is tenant agnostic. Every user in Identity has an OrgCode identifier. (Custom user property).
Each server has multi tenancy built in through the use of 'cost centers'. The server has a collection of databases named the same on every server.
core vendor database
custom database where we store our extensions
logs database for our job output
There are also small application specific databases that already use an Org Code to identify a user
Server A - 1 Org Code
Server B - 4 Org Codes
Server C - 3 Org Codes engaged in project, 50+ not yet (mostly small)
Server D - No Org Codes engaged as of now. 80+ on server. (soon)
It is not possible to consolidate all the organisations onto one server. There are legal and technical ramifications. Each server has hundreds of remote transponders reporting to them that would need updating. The data these supply is what our custom jobs work with.
The dream is to continue to use DI in each page, passing in the contexts as required. The context would then be smart enough to pick the correct underlying connection details based on the OrgCode of the username.
I hesitate to use the word proxy because it seems heavily loaded in this space.
Hell, even using a switch statement would be fine if I knew where to put it
Desired effect User from Org XYZ loads page that requires Vendor database, they get the one from the server that XYZ maps to.
Edit 2020/07/13
To tidy up referenceing, I've switched the OrgCode and Server to Enums. The context inheritance is as follows
DbContext
CustLogsContext
public virtual ServerEnum Server
{
get
{
return ServerEnum.None;
}
}
DbSet (etc)
CustLogsServerAContext
public override ServerEnum Server
{
get
{
return ServerEnum.ServerA;
}
}
CustLogsServerBContext (etc)
CustLogsServerCContext (etc)
CustLogsServerDContext (etc)
VendorContext
VendorServerAContext
VendorServerBContext (etc)
VendorServerCContext (etc)
VendorServerDContext (etc)
I've also created a static class OrgToServerMapping that contains a dictionary mapping OrgCodes to Servers. Currently hardcoded, will change eventually to load from config, and add a reload method.
Currently thinking I need a class that collects the contexts Would have a Dictionary<serverEnum, dbcontext> and be registered as a service. Pretty sure I'd need a version of the object for each inherited dbcontext, unless someone knows ome polymorphic trick I can use
I work on a similar system with thousands of databases, but with LinqToSql instead of EF (I know...). Hopefully the general ideas translate. There are connection pool fragmentation issues that you have to contend with if you end up with many databases, but for just your four databases you won't have to worry about that.
I like these two approaches - they both assume that you can set up the current ApplicationUser to be injected via DI.
Approach #1: In Startup, configure the DI that returns the data context to get the current user, then use that user to build the correct data context. Something like this:
// In Startup.ConfigureServices
services.AddScoped<ApplicationUser>((serviceProvider) =>
{
// something to return the active user however you're normally doing it.
});
services.AddTransient<CustLogsContext>((serviceProvider) =>
{
ApplicationUser currentUser = serviceProvider.GetRequiredService<ApplicationUser>();
// Use your OrgToServerMapping to create a data context
// with the correct connection
return CreateDataContextFromOrganization(currentUser.OrgCode);
});
Approach #2: Rather than injecting the CustLogsContext directly, inject a service that depends on the active user that is responsible for building the data context:
// In Startup.ConfigureServices
services.AddScoped<ApplicationUser>((serviceProvider) =>
{
// something to return the active user however you're normally doing it.
});
services.AddTransient<CustLogsContextWrapper>();
// In its own file somewhere
public class CustLogsContextWrapper
{
private ApplicationUser currentUser;
public CustLogsContextWrapper(ApplicationUser currentUser)
{
this.currentUser = currentUser;
}
public CustLogsContext GetContext()
{
// use your OrgToServerMapping to create a data context with the correct connection;
return CreateDataContextFromOrganization(user.OrgCode);
}
}
Personally I prefer the latter approach, because it avoids a call to a service locator in Startup, and I like encapsulating away the details of how the data context is created. But if I already had a bunch of code that gets the data context directly with DI, the first one would be fine.
I have created a multitenancy implementation as follows (which could scale endlessly in theorie). Create a multitenancy database (say tenantdb). Easy. But the trick is to store connectionstring details for each tenant (your target databases). Along side your user orgCode etc.
I can see myself needing something that maps User.OrgCode and Area to a Connection string
So the way to map it in code is to feed your dbcontext whith your target tenant connectionstring, which you get from your tenantdb. So you would need anohter dbcontext for you tenantdb. So first call your tenantdb get the correct tenant connectionstring by filtering with your user orgcode. And then use it to create a new target dbcontext.
The dream is to continue to use DI in each page, passing in the contexts as required. The context would then be smart enough to pick the correct underlying connection details based on the OrgCode of the username.
I have this working with DI.
I created UI elements for crud operations for this tenantdb, so I can update delete add connection string details and other needed data. The Password is encrypted on save and decrypted on the get just before passing to your target dbcontext.
So I have two connection strings in my config file. One for the tenantdb and one for a default target db. Which can be an empty/dummy one, as you probably encounter application startup errors thrown by your DI code if you don't have one, as it will most likely auto search for a connectionstring.
I also have switch code. This is where a user can switch to anohter tenant. So here the user can choose from all the tenants it has rights to (yes rights are stored in tenantdb). And this would again trigger the code steps described above.
Cheers.
Took this Razor Pages tutorial as my starting point.
This way you can have very lousily coupled target databases. The only overlap could be the User ID. (or even some token from Azure,Google,AWS etc)
Startup.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddDbContext<TenantContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TenantContext")));
//your dummy (empty) target context.
services.AddDbContext<TargetContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TargetContext")));
}
IndexModel (Tenant pages).
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.TenantContext _context;
private ContosoUniversity.Data.TargetContext _targetContext;
public IndexModel(ContosoUniversity.Data.TenantContext context, ContosoUniversity.Data.TargetContext targetContext)
{
_context = context;
//set as default targetcontext -> dummy/empty one.
_targetContext = targetContext;
}
public TenantContext Context => _context;
public TargetContext TargetContext { get => _targetContext; set => _targetContext = value; }
public async Task OnGetAsync()
{
//get data from default target.
var student1 = _targetContext.Students.First();
//or
//switch tenant
//lets say you login and have the users ID as guid.
//then return list of tenants for this user from tenantusers.
var ut = await _context.TenantUser.FindAsync("9245fe4a-d402-451c-b9ed-9c1a04247482");
//now get the tenant(s) for this user.
var SelectedTentant = await _context.Tenants.FindAsync(ut.TenantID);
DbContextOptionsBuilder<TargetContext> Builder = new DbContextOptionsBuilder<TargetContext>();
Builder.UseSqlServer(SelectedTentant.ConnectionString);
_targetContext = new TargetContext(Builder.Options);
//now get data from the switched to database.
var student2 = _targetContext.Students.First();
}
}
Tenant.
public class Tenant
{
public int TenantID { get; set; }
public string Name { get; set; }
//probably could slice up the connenctiing string into props.
public string ConnectionString { get; set; }
public ICollection<TenantUser> TenantUsers { get; set; }
}
TenantUser.
public class TenantUser
{
[Key]
public Guid UserID { get; set; }
public string TenantID { get; set; }
}
Default connstrings.
{ "AllowedHosts": "*",
"ConnectionStrings": {
"TenantContext": "Server=(localdb)\mssqllocaldb;Database=TenantContext;Trusted_Connection=True;MultipleActiveResultSets=true",
"TargetContext": "Server=(localdb)\mssqllocaldb;Database=TargetContext;Trusted_Connection=True;MultipleActiveResultSets=true"
}
I have an API written in Asp Net Core 2.1 (upgraded from 2.0) which build an entity from a DTO sent by a mobile app.
The DTO have a field "mobileId" (Guid format) to prevent the mobile to send the same object when it goes online (after connectivity issues for example).
But this solution does not seem to be efficient as presented below :
There are 4 lines whereas I actually wanted only 1 line :S I don't understand how it occurred because I specified in the Startup:
services.AddScoped<DbContext>(s =>
{
// code emitted for brevity
});
The code of the API itself is centralized in a Handler because our API follow a little piece of CQRS pattern and the "Dispatcher" is registered via Autofac :
public class DispatcherModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Dispatcher>()
.As<IDispatcher>()
.InstancePerDependency();
}
}
The same applies for IUnitOfWork which use our scoped DbContext internally
At the beginning I check that an entity with the same 'mobileId' is not already present in database :
if (dto.MobileId != null)
{
Report existingMobileId = await UnitOfWork.Repository<Report>()
.GetFirstOrDefaultAsync(qr => qr.MobileId == dto.MobileId);
if (existingMobileId != null)
{
return new Result<object>(new Error(400, $"Report with '{dto.MobileId}' already exists in DB."));
}
}
What do you think I'm doing wrong ? Or maybe I should add something else ?
Thank you for your help guyz :)
Technical environment :
- ASP.NET Core 2.1
- Entity Framework Core
- Azure SQL Database
AddScoped means that new instance of service is created for each request, so, if you have several simultaneous requests, you may have some kind of race condition on repository level when each checks presence of a row before writing it to db. I would recommend putting this responsibility on the database level applying Unique constraint on mobileId column.
I am testing my service layer which contains some calls to repositories by using the SQLite database provider. My test class has been written according to the following introduction:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite
For the entity which is written to the database in this test case, the Guid is generated on database:
entity.Property(e => e.Guid).HasDefaultValueSql("newid()");
Now when trying to add a new entity (without explicitely setting the Guid), I get following exception:
SQLite Error 1: 'unknown function: newid()'.
Is there a way around this issue?
It seems like this function is simply not supported. As the database I am working with is quite old I am afraid to find more places like this which may not work.
My hope was to get better unit tests with SQLite than using the InMemoryDatabase provider which does not really suits for testing "relational database functionalities". But if this problem cannot be solved, I am stuck and probably need to stick to integration tests (at least for the data access part of my services)
You can create a custom function to do this in c# if you reference Microsoft.Data.SqLite
For example:
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connection = new SqliteConnection(connectionStringBuilder.ToString());
connection.CreateFunction("newid", () => Guid.NewGuid());
See here for a comparison between System.Data.SQLite and Microsoft.Data.Sqlite
I guess you would have this issue for MySql uuid() or any other DB because there is a direct dependency to MSSQL with newid(). My first suggestion would be to generate the GUID on the application side and pass it to the DB but you have probably already thought of that and cannot. As a workaround you can create a extension method like the following:
public static class Extensions
{
private static readonly Dictionary<Type, string> NewIdDictionary = new Dictionary<Type, string>
{
{ typeof(SqlServerOptionsExtension), "newid()" }
};
public static PropertyBuilder<TProperty> HasDefaultValueForSql<TProperty>(this PropertyBuilder<TProperty> propertyBuilder,
DbContextOptions contextOptions)
{
var result = contextOptions.Extensions.Select(extension =>
{
if (!(extension is RelationalOptionsExtension item)) return string.Empty;
return NewIdDictionary.TryGetValue(item.GetType(), out var sql) ? sql : string.Empty;
}).SingleOrDefault(s => !string.IsNullOrEmpty(s));
return propertyBuilder.HasDefaultValueSql(result);
}
}
Although there isn't an equvalient for SQLite it seems to work when passing null to .HasDefaultValueSql. And then you can add other extension types if needed.
Bowing to my Visual Studios request, I started my latest project using Entity Framework Core (1.0.1)
So writing my database models as I always have using the 'virtual' specifier to enable lazy loading for a List. Though when loading the parent table it appears that the child list never loads.
Parent Model
public class Events
{
[Key]
public int EventID { get; set; }
public string EventName { get; set; }
public virtual List<EventInclusions> EventInclusions { get; set; }
}
Child Model
public class EventInclusions
{
[Key]
public int EventIncSubID { get; set; }
public string InclusionName { get; set; }
public string InclusionDesc { get; set; }
public Boolean InclusionActive { get; set; }
}
Adding new records to these tables seems to work as I am used to where I can nest the EventInclusions records as a List inside the Events record.
Though when I query this table
_context.Events.Where(e => e.EventName == "Test")
The Issue
EventInclusions will return a null value regardless of the data behind the scenes.
After reading a bit I am getting the feeling this is a change between EF6 which I normally use and EF Core
I could use some help in either making a blanket Lazy Loading on statement or figuring out the new format for specifying Lazy Loading.
Caz
Lazy loading is now available on EF Core 2.1 and here is link to the relevant docs:
https://learn.microsoft.com/en-us/ef/core/querying/related-data#lazy-loading
So it appears that EF Core does not currently support lazy loading. Its coming but may be a while off.
For now if anyone else comes across this problem and is struggling. Below is a demo of using Eager loading which is what for now you have to use.
Say before you had a person object and that object contained a List of Hats in another table.
Rather than writing
var person = _context.Person.Where(p=> p.id == id).ToList();
person.Hats.Where(h=> h.id == hat).ToList();
You need to write
var person = _context.Person.Include(p=> p.Hats).Where(p=> p.id == id).ToList();
And then person.Hats.Where(h=> h.id == hat).ToList(); will work
If you have multiple Lists - Chain the Includes
var person = _context.Person.Include(p=> p.Hats).Include(p=> p.Tickets)
.Include(p=> p.Smiles).Where(p=> p.id == id).ToList();
I kinda get why this method is safer, that your not loading huge data sets that could slow things down. But I hope they get Lazy loading back soon!!!
Caz
you can instaling this package for enable lazy loading in EF Core 2.1.
Microsoft.EntityFrameworkCore.Proxies
and then set this config in your ef dbContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer("myConnectionString");
"Notice" this package works only on EF Core 2.1 and above.
For EF Core 2.1 and above,
Install:
dotnet add package Microsoft.EntityFrameworkCore.Proxies --version 2.2.4
Then Update your Startup.cs file as indicated below.
using Microsoft.EntityFrameworkCore.Proxies;
services.AddEntityFrameworkProxies();
services.AddDbContext<BlogDbContext>(options =>
{
options.UseSqlite(Configuration.GetSection("ConnectionStrings")["DefaultConnection"]);
options.UseLazyLoadingProxies(true);
});
There's a pre-release version that just came out, regardless it's supposed to be available in full release soon.
A couple of caveats:
All your data properties that are more than simple types (ie: any other classes/tables) need to be public virtuals (default scaffolding they're not).
This line gets tucked into OnConfiguring on your data context:
optionsBuilder.UseLazyLoadingProxies();
It's (currently) pre-release so may the force be with you.
LazyLoading is not yet supported by EF Core, but there is a non-official library that enables LazyLoading: https://github.com/darxis/EntityFramework.LazyLoading. You can use it until it is officially supported.
It supports EF Core v1.1.1. It is available as a nuget package: https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.LazyLoading/
Disclaimer: I am the owner of this repo and invite you to try it out, report issues and/or contribute.
Lazy load is planned to be in EF core 2.1 - you can read more on why it is a must-have feature - here.
I installed MongoDB.Driver 2.0 from nuget published 4/2/2015. I also installed MondgoDB via chocolatey version 2.4.7. I created a test app in VS2013, as follows:
var client = new MongoClient();
var database = client.GetDatabase("foo");
var foo = database.GetCollection<BsonDocument>("bar");
From what I have read in the docs, this should be sufficient to connect to the server and create the database. When I check the mongodb via robomongo, I dont see that the database "foo" has been created.
I tried running mongodb via windows server and via command line (admin mode), with no results. I have disabled my firewall just in case that was an issue; still nothing. I have to say that as my first foray into MongoDB I would have expected this to just work.
What am I missing?
... and create the database
there is no such operation in mongodb, databases are created when you attempt to insert data into one
What am I missing?
You're not asking the driver to actually do anything. All operations are lazy. To have the driver connect and insert a document, thereby creating both the database and the collection, do something like this:
var foo = database.GetCollection<Customer>("customer");
foo.InsertOneAsync(new Customer { Name = "John Doe" }).Wait();
where Customer is a class, e.g.
public class Customer {
public ObjectId Id { get; set; }
public string Name { get; set; }
}
Of course, you can also work with BsonDocuments, but that seems unnecessarily cumbersome.