diff --git a/src/Nadeko.Bot.Common/DbService.cs b/src/Nadeko.Bot.Common/DbService.cs
index b829db2b6..c139c8f0c 100644
--- a/src/Nadeko.Bot.Common/DbService.cs
+++ b/src/Nadeko.Bot.Common/DbService.cs
@@ -6,73 +6,13 @@ using NadekoBot.Services.Database;
namespace NadekoBot.Services;
-public class DbService
+public abstract class DbService
{
- private readonly IBotCredsProvider _creds;
+ ///
+ /// Call this to apply all migrations
+ ///
+ public abstract Task SetupAsync();
- // these are props because creds can change at runtime
- private string DbType => _creds.GetCreds().Db.Type.ToLowerInvariant().Trim();
- private string ConnString => _creds.GetCreds().Db.ConnectionString;
-
- public DbService(IBotCredsProvider creds)
- {
- LinqToDBForEFTools.Initialize();
- Configuration.Linq.DisableQueryCache = true;
-
- _creds = creds;
- }
-
- public async Task SetupAsync()
- {
- var dbType = DbType;
- var connString = ConnString;
-
- await using var context = CreateRawDbContext(dbType, connString);
-
- // make sure sqlite db is in wal journal mode
- if (context is SqliteContext)
- {
- await context.Database.ExecuteSqlRawAsync("PRAGMA journal_mode=WAL");
- }
-
- await context.Database.MigrateAsync();
- }
-
- private static NadekoContext CreateRawDbContext(string dbType, string connString)
- {
- switch (dbType)
- {
- case "postgresql":
- case "postgres":
- case "pgsql":
- return new PostgreSqlContext(connString);
- case "mysql":
- return new MysqlContext(connString);
- case "sqlite":
- return new SqliteContext(connString);
- default:
- throw new NotSupportedException($"The database provide type of '{dbType}' is not supported.");
- }
- }
-
- private NadekoContext GetDbContextInternal()
- {
- var dbType = DbType;
- var connString = ConnString;
-
- var context = CreateRawDbContext(dbType, connString);
- if (context is SqliteContext)
- {
- var conn = context.Database.GetDbConnection();
- conn.Open();
- using var com = conn.CreateCommand();
- com.CommandText = "PRAGMA synchronous=OFF";
- com.ExecuteNonQuery();
- }
-
- return context;
- }
-
- public NadekoContext GetDbContext()
- => GetDbContextInternal();
+ public abstract DbContext CreateRawDbContext(string dbType, string connString);
+ public abstract DbContext GetDbContext();
}
\ No newline at end of file
diff --git a/src/Nadeko.Bot.Common/Services/Currency/CurrencyService.cs b/src/Nadeko.Bot.Common/Services/Currency/CurrencyService.cs
index 6b1bd4975..fd2c697ca 100644
--- a/src/Nadeko.Bot.Common/Services/Currency/CurrencyService.cs
+++ b/src/Nadeko.Bot.Common/Services/Currency/CurrencyService.cs
@@ -1,5 +1,7 @@
#nullable disable
using LinqToDB;
+using LinqToDB.EntityFrameworkCore;
+using NadekoBot.Db.Models;
using NadekoBot.Services.Currency;
namespace NadekoBot.Services;
@@ -52,7 +54,8 @@ public sealed class CurrencyService : ICurrencyService, INService
if (type == CurrencyType.Default)
{
await using var ctx = _db.GetDbContext();
- await ctx.DiscordUser
+ await ctx
+ .GetTable()
.Where(x => userIds.Contains(x.UserId))
.UpdateAsync(du => new()
{
diff --git a/src/Nadeko.Bot.Common/Services/Currency/DefaultWallet.cs b/src/Nadeko.Bot.Common/Services/Currency/DefaultWallet.cs
index 1bcc33121..93df5b96e 100644
--- a/src/Nadeko.Bot.Common/Services/Currency/DefaultWallet.cs
+++ b/src/Nadeko.Bot.Common/Services/Currency/DefaultWallet.cs
@@ -1,5 +1,6 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
+using NadekoBot.Db.Models;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Currency;
@@ -19,11 +20,11 @@ public class DefaultWallet : IWallet
{
await using var ctx = _db.GetDbContext();
var userId = UserId;
- return await ctx.DiscordUser
- .ToLinqToDBTable()
- .Where(x => x.UserId == userId)
- .Select(x => x.CurrencyAmount)
- .FirstOrDefaultAsync();
+ return await ctx
+ .GetTable()
+ .Where(x => x.UserId == userId)
+ .Select(x => x.CurrencyAmount)
+ .FirstOrDefaultAsync();
}
public async Task Take(long amount, TxData? txData)
@@ -34,12 +35,13 @@ public class DefaultWallet : IWallet
await using var ctx = _db.GetDbContext();
var userId = UserId;
- var changed = await ctx.DiscordUser
- .Where(x => x.UserId == userId && x.CurrencyAmount >= amount)
- .UpdateAsync(x => new()
- {
- CurrencyAmount = x.CurrencyAmount - amount
- });
+ var changed = await ctx
+ .GetTable()
+ .Where(x => x.UserId == userId && x.CurrencyAmount >= amount)
+ .UpdateAsync(x => new()
+ {
+ CurrencyAmount = x.CurrencyAmount - amount
+ });
if (changed == 0)
return false;
@@ -70,25 +72,26 @@ public class DefaultWallet : IWallet
await using var ctx = _db.GetDbContext();
var userId = UserId;
-
+
await using (var tran = await ctx.Database.BeginTransactionAsync())
{
- var changed = await ctx.DiscordUser
- .Where(x => x.UserId == userId)
- .UpdateAsync(x => new()
- {
- CurrencyAmount = x.CurrencyAmount + amount
- });
+ var changed = await ctx
+ .GetTable()
+ .Where(x => x.UserId == userId)
+ .UpdateAsync(x => new()
+ {
+ CurrencyAmount = x.CurrencyAmount + amount
+ });
if (changed == 0)
{
- await ctx.DiscordUser
- .ToLinqToDBTable()
- .Value(x => x.UserId, userId)
- .Value(x => x.Username, "Unknown")
- .Value(x => x.Discriminator, "????")
- .Value(x => x.CurrencyAmount, amount)
- .InsertAsync();
+ await ctx
+ .GetTable()
+ .Value(x => x.UserId, userId)
+ .Value(x => x.Username, "Unknown")
+ .Value(x => x.Discriminator, "????")
+ .Value(x => x.CurrencyAmount, amount)
+ .InsertAsync();
}
await tran.CommitAsync();
diff --git a/src/Nadeko.Bot.Common/Services/Impl/BlacklistService.cs b/src/Nadeko.Bot.Common/Services/Impl/BlacklistService.cs
index 2ffd393de..3f77f5fe0 100644
--- a/src/Nadeko.Bot.Common/Services/Impl/BlacklistService.cs
+++ b/src/Nadeko.Bot.Common/Services/Impl/BlacklistService.cs
@@ -1,7 +1,9 @@
#nullable disable
-using Microsoft.EntityFrameworkCore;
+using LinqToDB;
+using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db;
+using NadekoBot.Db.Models;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Permissions.Services;
@@ -73,38 +75,36 @@ public sealed class BlacklistService : IExecOnMessage
public void Reload(bool publish = true)
{
using var uow = _db.GetDbContext();
- var toPublish = uow.Blacklist.AsNoTracking().ToArray();
+ var toPublish = uow.GetTable().ToArray();
blacklist = toPublish;
if (publish)
_pubSub.Pub(_blPubKey, toPublish);
}
- public void Blacklist(BlacklistType type, ulong id)
+ public async Task Blacklist(BlacklistType type, ulong id)
{
if (_creds.OwnerIds.Contains(id))
return;
- using var uow = _db.GetDbContext();
- var item = new BlacklistEntry
- {
- ItemId = id,
- Type = type
- };
- uow.Blacklist.Add(item);
- uow.SaveChanges();
+ await using var uow = _db.GetDbContext();
+
+ await uow
+ .GetTable()
+ .InsertAsync(() => new()
+ {
+ ItemId = id,
+ Type = type,
+ });
Reload();
}
- public void UnBlacklist(BlacklistType type, ulong id)
+ public async Task UnBlacklist(BlacklistType type, ulong id)
{
- using var uow = _db.GetDbContext();
- var toRemove = uow.Blacklist.FirstOrDefault(bi => bi.ItemId == id && bi.Type == type);
-
- if (toRemove is not null)
- uow.Blacklist.Remove(toRemove);
-
- uow.SaveChanges();
+ await using var uow = _db.GetDbContext();
+ await uow.GetTable()
+ .Where(bi => bi.ItemId == id && bi.Type == type)
+ .DeleteAsync();
Reload();
}
@@ -113,16 +113,21 @@ public sealed class BlacklistService : IExecOnMessage
{
using (var uow = _db.GetDbContext())
{
- var bc = uow.Blacklist;
- //blacklist the users
+ var bc = uow.Set();
bc.AddRange(toBlacklist.Select(x => new BlacklistEntry
{
ItemId = x,
Type = BlacklistType.User
}));
- //clear their currencies
- uow.DiscordUser.RemoveFromMany(toBlacklist);
+ // todo check if blacklist works and removes currency
+ uow.GetTable()
+ .UpdateAsync(x => toBlacklist.Contains(x.UserId),
+ _ => new()
+ {
+ CurrencyAmount = 0
+ });
+
uow.SaveChanges();
}
diff --git a/src/Nadeko.Bot.Common/Services/Impl/DiscordPermOverrideService.cs b/src/Nadeko.Bot.Common/Services/Impl/DiscordPermOverrideService.cs
index a513768b6..72c8f7223 100644
--- a/src/Nadeko.Bot.Common/Services/Impl/DiscordPermOverrideService.cs
+++ b/src/Nadeko.Bot.Common/Services/Impl/DiscordPermOverrideService.cs
@@ -18,10 +18,11 @@ public class DiscordPermOverrideService : INService, IExecPreCommand, IDiscordPe
_db = db;
_services = services;
using var uow = _db.GetDbContext();
- _overrides = uow.DiscordPermOverrides.AsNoTracking()
- .AsEnumerable()
- .ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
- .ToConcurrent();
+ _overrides = uow.Set()
+ .AsNoTracking()
+ .AsEnumerable()
+ .ToDictionary(o => (o.GuildId ?? 0, o.Command), o => o)
+ .ToConcurrent();
}
public bool TryGetOverrides(ulong guildId, string commandName, out Nadeko.Bot.Db.GuildPerm? perm)
@@ -52,18 +53,18 @@ public class DiscordPermOverrideService : INService, IExecPreCommand, IDiscordPe
commandName = commandName.ToLowerInvariant();
await using var uow = _db.GetDbContext();
var over = await uow.Set()
- .AsQueryable()
- .FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
+ .AsQueryable()
+ .FirstOrDefaultAsync(x => x.GuildId == guildId && commandName == x.Command);
if (over is null)
{
uow.Set()
- .Add(over = new()
- {
- Command = commandName,
- Perm = (Nadeko.Bot.Db.GuildPerm)perm,
- GuildId = guildId
- });
+ .Add(over = new()
+ {
+ Command = commandName,
+ Perm = (Nadeko.Bot.Db.GuildPerm)perm,
+ GuildId = guildId
+ });
}
else
over.Perm = (Nadeko.Bot.Db.GuildPerm)perm;
@@ -77,10 +78,10 @@ public class DiscordPermOverrideService : INService, IExecPreCommand, IDiscordPe
{
await using var uow = _db.GetDbContext();
var overrides = await uow.Set()
- .AsQueryable()
- .AsNoTracking()
- .Where(x => x.GuildId == guildId)
- .ToListAsync();
+ .AsQueryable()
+ .AsNoTracking()
+ .Where(x => x.GuildId == guildId)
+ .ToListAsync();
uow.RemoveRange(overrides);
await uow.SaveChangesAsync();
@@ -95,9 +96,9 @@ public class DiscordPermOverrideService : INService, IExecPreCommand, IDiscordPe
await using var uow = _db.GetDbContext();
var over = await uow.Set()
- .AsQueryable()
- .AsNoTracking()
- .FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
+ .AsQueryable()
+ .AsNoTracking()
+ .FirstOrDefaultAsync(x => x.GuildId == guildId && x.Command == commandName);
if (over is null)
return;
@@ -112,10 +113,10 @@ public class DiscordPermOverrideService : INService, IExecPreCommand, IDiscordPe
{
await using var uow = _db.GetDbContext();
return await uow.Set()
- .AsQueryable()
- .AsNoTracking()
- .Where(x => x.GuildId == guildId)
- .ToListAsync();
+ .AsQueryable()
+ .AsNoTracking()
+ .Where(x => x.GuildId == guildId)
+ .ToListAsync();
}
public async Task ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command)
diff --git a/src/Nadeko.Bot.Db/Extensions/DiscordUserExtensions.cs b/src/Nadeko.Bot.Db/Extensions/DiscordUserExtensions.cs
index cb2ecc98c..3f15e189a 100644
--- a/src/Nadeko.Bot.Db/Extensions/DiscordUserExtensions.cs
+++ b/src/Nadeko.Bot.Db/Extensions/DiscordUserExtensions.cs
@@ -42,7 +42,7 @@ public static class DiscordUserExtensions
});
public static Task EnsureUserCreatedAsync(
- this NadekoBaseContext ctx,
+ this DbContext ctx,
ulong userId)
=> ctx.GetTable()
.InsertOrUpdateAsync(
@@ -66,7 +66,7 @@ public static class DiscordUserExtensions
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
public static DiscordUser GetOrCreateUser(
- this NadekoBaseContext ctx,
+ this DbContext ctx,
ulong userId,
string username,
string discrim,
diff --git a/src/Nadeko.Bot.Db/Extensions/GuildConfigExtensions.cs b/src/Nadeko.Bot.Db/Extensions/GuildConfigExtensions.cs
index df55da37d..dea053769 100644
--- a/src/Nadeko.Bot.Db/Extensions/GuildConfigExtensions.cs
+++ b/src/Nadeko.Bot.Db/Extensions/GuildConfigExtensions.cs
@@ -65,7 +65,7 @@ public static class GuildConfigExtensions
/// Use to manipulate the set however you want. Pass null to include everything
/// Config for the guild
public static GuildConfig GuildConfigsForId(
- this NadekoBaseContext ctx,
+ this DbContext ctx,
ulong guildId,
Func, IQueryable> includes)
{
@@ -193,7 +193,7 @@ public static class GuildConfigExtensions
conf.CleverbotEnabled = cleverbotEnabled;
}
- public static XpSettings XpSettingsFor(this NadekoBaseContext ctx, ulong guildId)
+ public static XpSettings XpSettingsFor(this DbContext ctx, ulong guildId)
{
var gc = ctx.GuildConfigsForId(guildId,
set => set.Include(x => x.XpSettings)
diff --git a/src/Nadeko.Bot.Db/Extensions/UserXpExtensions.cs b/src/Nadeko.Bot.Db/Extensions/UserXpExtensions.cs
index 5308f83d2..3214a03b0 100644
--- a/src/Nadeko.Bot.Db/Extensions/UserXpExtensions.cs
+++ b/src/Nadeko.Bot.Db/Extensions/UserXpExtensions.cs
@@ -9,9 +9,9 @@ namespace NadekoBot.Db;
public static class UserXpExtensions
{
- public static UserXpStats GetOrCreateUserXpStats(this NadekoContext ctx, ulong guildId, ulong userId)
+ public static UserXpStats GetOrCreateUserXpStats(this DbContext ctx, ulong guildId, ulong userId)
{
- var usr = ctx.UserXpStats.FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId);
+ var usr = ctx.Set().FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId);
if (usr is null)
{
diff --git a/src/Nadeko.Bot.Db/Extensions/WaifuExtensions.cs b/src/Nadeko.Bot.Db/Extensions/WaifuExtensions.cs
index a5487f3a8..0abb578a4 100644
--- a/src/Nadeko.Bot.Db/Extensions/WaifuExtensions.cs
+++ b/src/Nadeko.Bot.Db/Extensions/WaifuExtensions.cs
@@ -75,24 +75,24 @@ public static class WaifuExtensions
.Select(x => x.Waifu.UserId)
.FirstOrDefault();
- public static async Task GetWaifuInfoAsync(this NadekoContext ctx, ulong userId)
+ public static async Task GetWaifuInfoAsync(this DbContext ctx, ulong userId)
{
- await ctx.WaifuInfo
+ await ctx.Set()
.ToLinqToDBTable()
.InsertOrUpdateAsync(() => new()
{
AffinityId = null,
ClaimerId = null,
Price = 1,
- WaifuId = ctx.DiscordUser.Where(x => x.UserId == userId).Select(x => x.Id).First()
+ WaifuId = ctx.Set().Where(x => x.UserId == userId).Select(x => x.Id).First()
},
_ => new(),
() => new()
{
- WaifuId = ctx.DiscordUser.Where(x => x.UserId == userId).Select(x => x.Id).First()
+ WaifuId = ctx.Set().Where(x => x.UserId == userId).Select(x => x.Id).First()
});
- var toReturn = ctx.WaifuInfo.AsQueryable()
+ var toReturn = ctx.Set().AsQueryable()
.Where(w => w.WaifuId
== ctx.Set()
.AsQueryable()
@@ -120,7 +120,7 @@ public static class WaifuExtensions
.Where(u => u.Id == w.AffinityId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
- ClaimCount = ctx.WaifuInfo.AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
+ ClaimCount = ctx.Set().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
ClaimerName =
ctx.Set()
.AsQueryable()
diff --git a/src/Nadeko.Bot.Modules.Utility/_Common/Db/CommandAlias.cs b/src/Nadeko.Bot.Db/Models/CommandAlias.cs
similarity index 100%
rename from src/Nadeko.Bot.Modules.Utility/_Common/Db/CommandAlias.cs
rename to src/Nadeko.Bot.Db/Models/CommandAlias.cs
diff --git a/src/Nadeko.Bot.Db/NadekoBaseContext.cs b/src/Nadeko.Bot.Db/NadekoBaseContext.cs
deleted file mode 100644
index 04c3b484a..000000000
--- a/src/Nadeko.Bot.Db/NadekoBaseContext.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace NadekoBot.Db;
-
-public abstract class NadekoBaseContext : DbContext
-{
-}
\ No newline at end of file
diff --git a/src/Nadeko.Bot.Db/Extensions/PollExtensions.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs
similarity index 76%
rename from src/Nadeko.Bot.Db/Extensions/PollExtensions.cs
rename to src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs
index 01be1e3d6..40f7abedc 100644
--- a/src/Nadeko.Bot.Db/Extensions/PollExtensions.cs
+++ b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs
@@ -12,9 +12,9 @@ public static class PollExtensions
.Include(x => x.Votes)
.ToArray();
- public static void RemovePoll(this NadekoContext ctx, int id)
+ public static void RemovePoll(this DbContext ctx, int id)
{
- var p = ctx.Poll.Include(x => x.Answers).Include(x => x.Votes).FirstOrDefault(x => x.Id == id);
+ var p = ctx.Set().Include(x => x.Answers).Include(x => x.Votes).FirstOrDefault(x => x.Id == id);
if (p is null)
return;
@@ -31,6 +31,6 @@ public static class PollExtensions
p.Answers.Clear();
}
- ctx.Poll.Remove(p);
+ ctx.Set().Remove(p);
}
}
\ No newline at end of file
diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs
index 730a981be..618efd487 100644
--- a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs
+++ b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs
@@ -52,7 +52,7 @@ public class PollRunner
finally { _locker.Release(); }
await using var uow = _db.GetDbContext();
- var trackedPoll = uow.Poll.FirstOrDefault(x => x.Id == Poll.Id);
+ var trackedPoll = uow.Set().FirstOrDefault(x => x.Id == Poll.Id);
trackedPoll.Votes.Add(voteObj);
uow.SaveChanges();
return true;
diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs
index 1563710fc..efae39526 100644
--- a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs
+++ b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs
@@ -24,15 +24,15 @@ public class PollService : IExecOnMessage
_eb = eb;
using var uow = db.GetDbContext();
- ActivePolls = uow.Poll.GetAllPolls()
- .ToDictionary(x => x.GuildId,
- x =>
- {
- var pr = new PollRunner(db, x);
- pr.OnVoted += Pr_OnVoted;
- return pr;
- })
- .ToConcurrent();
+ ActivePolls = uow.Set().GetAllPolls()
+ .ToDictionary(x => x.GuildId,
+ x =>
+ {
+ var pr = new PollRunner(db, x);
+ pr.OnVoted += Pr_OnVoted;
+ return pr;
+ })
+ .ToConcurrent();
}
public Poll CreatePoll(ulong guildId, ulong channelId, string input)
@@ -44,10 +44,10 @@ public class PollService : IExecOnMessage
return null;
var col = new IndexedCollection(data.Skip(1)
- .Select(x => new PollAnswer
- {
- Text = x
- }));
+ .Select(x => new PollAnswer
+ {
+ Text = x
+ }));
return new()
{
@@ -66,7 +66,7 @@ public class PollService : IExecOnMessage
{
using (var uow = _db.GetDbContext())
{
- uow.Poll.Add(p);
+ uow.Set().Add(p);
uow.SaveChanges();
}
@@ -98,8 +98,13 @@ public class PollService : IExecOnMessage
var toDelete = await msg.Channel.SendConfirmAsync(_eb,
_strs.GetText(strs.poll_voted(Format.Bold(usr.ToString())), usr.GuildId));
toDelete.DeleteAfter(5);
- try { await msg.DeleteAsync(); }
- catch { }
+ try
+ {
+ await msg.DeleteAsync();
+ }
+ catch
+ {
+ }
}
public async Task ExecOnMessageAsync(IGuild guild, IUserMessage msg)
diff --git a/src/Nadeko.Bot.Modules.Permisssions/Blacklist/BlacklistCommands.cs b/src/Nadeko.Bot.Modules.Permisssions/Blacklist/BlacklistCommands.cs
index e51041a3e..025753b91 100644
--- a/src/Nadeko.Bot.Modules.Permisssions/Blacklist/BlacklistCommands.cs
+++ b/src/Nadeko.Bot.Modules.Permisssions/Blacklist/BlacklistCommands.cs
@@ -124,9 +124,9 @@ public partial class Permissions
private async Task Blacklist(AddRemove action, ulong id, BlacklistType type)
{
if (action == AddRemove.Add)
- _service.Blacklist(type, id);
+ await _service.Blacklist(type, id);
else
- _service.UnBlacklist(type, id);
+ await _service.UnBlacklist(type, id);
if (action == AddRemove.Add)
{
diff --git a/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj b/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj
index 919f7ceab..590ef6b35 100644
--- a/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj
+++ b/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj
@@ -24,4 +24,8 @@
+
+
+
+
diff --git a/src/NadekoBot/Db/NadekoDbService.cs b/src/NadekoBot/Db/NadekoDbService.cs
new file mode 100644
index 000000000..0671eff07
--- /dev/null
+++ b/src/NadekoBot/Db/NadekoDbService.cs
@@ -0,0 +1,76 @@
+using LinqToDB.Common;
+using LinqToDB.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+
+namespace NadekoBot.Services.Database;
+
+public sealed class NadekoDbService : DbService
+{
+ private readonly IBotCredsProvider _creds;
+
+ // these are props because creds can change at runtime
+ private string DbType => _creds.GetCreds().Db.Type.ToLowerInvariant().Trim();
+ private string ConnString => _creds.GetCreds().Db.ConnectionString;
+
+ public NadekoDbService(IBotCredsProvider creds)
+ {
+ LinqToDBForEFTools.Initialize();
+ Configuration.Linq.DisableQueryCache = true;
+
+ _creds = creds;
+ }
+
+ public override async Task SetupAsync()
+ {
+ var dbType = DbType;
+ var connString = ConnString;
+
+ await using var context = CreateRawDbContext(dbType, connString);
+
+ // make sure sqlite db is in wal journal mode
+ if (context is SqliteContext)
+ {
+ await context.Database.ExecuteSqlRawAsync("PRAGMA journal_mode=WAL");
+ }
+
+ await context.Database.MigrateAsync();
+ }
+
+ public override NadekoContext CreateRawDbContext(string dbType, string connString)
+ {
+ switch (dbType)
+ {
+ case "postgresql":
+ case "postgres":
+ case "pgsql":
+ return new PostgreSqlContext(connString);
+ case "mysql":
+ return new MysqlContext(connString);
+ case "sqlite":
+ return new SqliteContext(connString);
+ default:
+ throw new NotSupportedException($"The database provide type of '{dbType}' is not supported.");
+ }
+ }
+
+ private NadekoContext GetDbContextInternal()
+ {
+ var dbType = DbType;
+ var connString = ConnString;
+
+ var context = CreateRawDbContext(dbType, connString);
+ if (context is SqliteContext)
+ {
+ var conn = context.Database.GetDbConnection();
+ conn.Open();
+ using var com = conn.CreateCommand();
+ com.CommandText = "PRAGMA synchronous=OFF";
+ com.ExecuteNonQuery();
+ }
+
+ return context;
+ }
+
+ public override NadekoContext GetDbContext()
+ => GetDbContextInternal();
+}
\ No newline at end of file