Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 8.4.0 (#6307) #6309

Merged
merged 5 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/nuget.org.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ jobs:
- name: dotnet pack Butil
run: dotnet pack src/Butil/Bit.Butil/Bit.Butil.csproj --output . --configuration Release

- name: dotnet build Besql
run: dotnet build src/Besql/Bit.Besql/Bit.Besql.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false
- name: dotnet pack Besql
run: dotnet pack src/Besql/Bit.Besql/Bit.Besql.csproj --output . --configuration Release

- name: dotnet build CodeAnalyzers
run: dotnet build src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false

- name: dotnet pack CodeAnalyzer
run: dotnet pack src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj --output . --configuration Release

Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/prerelease.nuget.org.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ jobs:
- name: dotnet pack Butil
run: dotnet pack src/Butil/Bit.Butil/Bit.Butil.csproj --output . --configuration Release

- name: dotnet build Besql
run: dotnet build src/Besql/Bit.Besql/Bit.Besql.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false
- name: dotnet pack Besql
run: dotnet pack src/Besql/Bit.Besql/Bit.Besql.csproj --output . --configuration Release

- name: dotnet build CodeAnalyzers
run: dotnet build src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false

- name: dotnet pack CodeAnalyzer
run: dotnet pack src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj --output . --configuration Release

- name: build SourceGenerators nupkg file
run: dotnet build src/SourceGenerators/Bit.SourceGenerators/Bit.SourceGenerators.csproj --configuration Release

Expand Down
25 changes: 25 additions & 0 deletions src/Besql/Bit.Besql.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34321.82
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bit.Besql", "Bit.Besql\Bit.Besql.csproj", "{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4474E62-EBBF-4C5C-B256-EDA41E47EDC3}
EndGlobalSection
EndGlobal
131 changes: 131 additions & 0 deletions src/Besql/Bit.Besql/BesqlDbContextFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;

namespace Bit.Besql;

public class BesqlDbContextFactory<TContext> : DbContextFactory<TContext>
where TContext : DbContext
{
private static readonly IDictionary<Type, string> FileNames = new Dictionary<Type, string>();

private readonly IBesqlStorage cache;
private Task<int>? startupTask = null;
private int lastStatus = -2;

public BesqlDbContextFactory(
IBesqlStorage cache,
IServiceProvider serviceProvider,
DbContextOptions<TContext> options,
IDbContextFactorySource<TContext> factorySource)
: base(serviceProvider, options, factorySource)
{
this.cache = cache;
startupTask = RestoreAsync();
}

private static string Filename => FileNames[typeof(TContext)];

private static string BackupFile => $"{BesqlDbContextFactory<TContext>.Filename}_bak";

public static void Reset() => FileNames.Clear();

public static string? GetFilenameForType() =>
FileNames.ContainsKey(typeof(TContext)) ? FileNames[typeof(TContext)] : null;

public override async Task<TContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
{
await CheckForStartupTaskAsync();

var ctx = await base.CreateDbContextAsync(cancellationToken);

ctx.SavedChanges += SyncDbToCacheAsync;

return ctx;
}

private async Task DoSwap(string source, string target)
{
await using var src = new SqliteConnection($"Data Source={source}");
await using var tgt = new SqliteConnection($"Data Source={target}");

await src.OpenAsync();
await tgt.OpenAsync();

src.BackupDatabase(tgt);

await tgt.CloseAsync();
await src.CloseAsync();
}

private async Task<string> GetFilename()
{
await using var ctx = await base.CreateDbContextAsync();
var filename = "filenotfound.db";
var type = ctx.GetType();
if (FileNames.TryGetValue(type, out var value))
{
return value;
}

var cs = ctx.Database.GetConnectionString();

if (cs != null)
{
var file = cs.Split(';').Select(s => s.Split('='))
.Select(split => new
{
key = split[0].ToLowerInvariant(),
value = split[1],
})
.Where(kv => kv.key.Contains("data source") ||
kv.key.Contains("datasource") ||
kv.key.Contains("filename"))
.Select(kv => kv.value)
.FirstOrDefault();
if (file != null)
{
filename = file;
}
}

FileNames.Add(type, filename);
return filename;
}

private async Task CheckForStartupTaskAsync()
{
if (startupTask != null)
{
lastStatus = await startupTask;
startupTask?.Dispose();
startupTask = null;
}
}

private async void SyncDbToCacheAsync(object sender, SavedChangesEventArgs e)
{
var ctx = (TContext)sender;
await ctx.Database.CloseConnectionAsync();
await CheckForStartupTaskAsync();
if (e.EntitiesSavedCount > 0)
{
// unique to avoid conflicts. Is deleted after caching.
var backupName = $"{BesqlDbContextFactory<TContext>.BackupFile}-{Guid.NewGuid().ToString().Split('-')[0]}";
await DoSwap(BesqlDbContextFactory<TContext>.Filename, backupName);
lastStatus = await cache.SyncDb(backupName);
}
}

private async Task<int> RestoreAsync()
{
var filename = $"{await GetFilename()}_bak";
lastStatus = await cache.SyncDb(filename);
if (lastStatus == 0)
{
await DoSwap(filename, FileNames[typeof(TContext)]);
}

return lastStatus;
}
}
32 changes: 32 additions & 0 deletions src/Besql/Bit.Besql/Bit.Besql.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<Import Project="../../Bit.Build.props" />

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>


<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
29 changes: 29 additions & 0 deletions src/Besql/Bit.Besql/BrowserCacheBesqlStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.JSInterop;

namespace Bit.Besql;

public sealed class BrowserCacheBesqlStorage : IAsyncDisposable, IBesqlStorage
{
private readonly Lazy<Task<IJSObjectReference>> moduleTask;

public BrowserCacheBesqlStorage(IJSRuntime jsRuntime)
{
moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
"import", "./_content/Bit.Besql/browserCache.js").AsTask()!);
}

public async ValueTask DisposeAsync()
{
if (moduleTask.IsValueCreated)
{
var module = await moduleTask.Value;
await module.DisposeAsync();
}
}

public async Task<int> SyncDb(string filename)
{
var module = await moduleTask.Value;
return await module.InvokeAsync<int>("synchronizeDbWithCache", filename);
}
}
6 changes: 6 additions & 0 deletions src/Besql/Bit.Besql/IBesqlStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Bit.Besql;

public interface IBesqlStorage
{
Task<int> SyncDb(string filename);
}
43 changes: 43 additions & 0 deletions src/Besql/Bit.Besql/IServiceCollectionBesqlExtentions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Bit.Besql;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection;

public static class IServiceCollectionBesqlExtentions
{
public static IServiceCollection AddBesqlDbContextFactory<TContext>(
this IServiceCollection services,
Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction)
where TContext : DbContext
{
if (OperatingSystem.IsBrowser())
{
services.TryAddScoped<IBesqlStorage, BrowserCacheBesqlStorage>();
services.AddDbContextFactory<TContext, BesqlDbContextFactory<TContext>>(
optionsAction ?? ((s, p) => { }), ServiceLifetime.Scoped);
}
else
{
services.AddDbContextFactory<TContext>(
optionsAction ?? ((s, p) => { }), ServiceLifetime.Scoped);
}

return services;
}

public static IServiceCollection AddBesqlDbContextFactory<TContext>(
this IServiceCollection services,
Action<DbContextOptionsBuilder>? optionsAction)
where TContext : DbContext
{
return services.AddBesqlDbContextFactory<TContext>((s, p) => optionsAction?.Invoke(p));
}

public static IServiceCollection AddBesqlDbContextFactory<TContext>(
this IServiceCollection services)
where TContext : DbContext
{
return services.AddBesqlDbContextFactory<TContext>(options => { });
}
}
63 changes: 63 additions & 0 deletions src/Besql/Bit.Besql/wwwroot/browserCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export async function synchronizeDbWithCache(file) {

window.sqlitedb = window.sqlitedb || {
init: false,
cache: await caches.open('Bit-Besql')
};

const db = window.sqlitedb;

const backupPath = `/${file}`;
const cachePath = `/data/cache/${file.substring(0, file.indexOf('_bak'))}`;

if (!db.init) {

db.init = true;

const resp = await db.cache.match(cachePath);

if (resp && resp.ok) {

const res = await resp.arrayBuffer();

if (res) {
console.log(`Restoring ${res.byteLength} bytes.`);
window.Module.FS.writeFile(backupPath, new Uint8Array(res));
return 0;
}
}
return -1;
}

if (window.Module.FS.analyzePath(backupPath).exists) {

const waitFlush = new Promise((done, _) => {
setTimeout(done, 10);
});

await waitFlush;

const data = window.Module.FS.readFile(backupPath);

const blob = new Blob([data], {
type: 'application/octet-stream',
ok: true,
status: 200
});

const headers = new Headers({
'content-length': blob.size
});

const response = new Response(blob, {
headers
});

await db.cache.put(cachePath, response);

window.Module.FS.unlink(backupPath);

return 1;
}
return -1;
}
17 changes: 17 additions & 0 deletions src/Besql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## bit entity framework core sqlite (bit Besql)

How to use `Bit.Besql`:

The usage of `Bit.Besql` is exactly the same as the regular usage of `Microsoft.EntityFrameworkCore.Sqlite` with [IDbContextFactory](https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-ef-core?view=aspnetcore-8.0#new-dbcontext-instances).

To get start, simply install `Bit.Besql` and use `services.AddBesqlDbContextFactory` instead of `services.AddDbContextFactory`.

Note: Don't use `IDbContextFactory` in `OnInitialized` because it relies on `IJSRuntime`. Use `OnAfterRender` instead.

In order to download sqlite db file from browser cache storage in blazor WebAssembly run the followings in browser console:
```js
const cache = await caches.open('Bit-Besql');
const resp = await cache.match('/data/cache/Boilerplate-ClientDb.db');
const blob = await resp.blob();
URL.createObjectURL(blob);
```
Loading
Loading