Skip to content

Commit

Permalink
feat: Add DbContextFactory for improved scalability in HouseScoutContext
Browse files Browse the repository at this point in the history
  • Loading branch information
xforman2 committed Sep 7, 2024
1 parent e0625fc commit 38d817f
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 107 deletions.
67 changes: 37 additions & 30 deletions DiscordBot/Filters/DataFilter.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
using SharedDependencies.Model;
using Microsoft.EntityFrameworkCore;
using SharedDependencies.Services;

namespace DiscordBot.Filters;

public class DataFilter
namespace DiscordBot.Filters
{
private HouseScoutContext _context;

public DataFilter(HouseScoutContext context)
{
_context = context;
}

public List<Estate> SurfacePriceFilter(
int priceMin,
int priceMax,
int surfaceMin,
int surfaceMax,
bool isNewUser
)
public class DataFilter
{
var query = _context.Estates.AsQueryable();
private readonly IDbContextFactory<HouseScoutContext> _contextFactory;

// we want to process only new data if user is not new,
// and all data if user is new
if (!isNewUser)
public DataFilter(IDbContextFactory<HouseScoutContext> contextFactory)
{
query = query.Where(e => e.IsNew);
_contextFactory = contextFactory;
}
query = query.Where(e =>
e.Price >= priceMin
&& e.Price <= priceMax
&& e.Surface >= surfaceMin
&& e.Surface <= surfaceMax
);

return query.ToList();
public List<Estate> SurfacePriceFilter(
int priceMin,
int priceMax,
int surfaceMin,
int surfaceMax,
bool isNewUser
)
{
using (var context = _contextFactory.CreateDbContext())
{
var query = context.Estates.AsQueryable();

// Process only new data if the user is not new,
// and all data if the user is new
if (!isNewUser)
{
query = query.Where(e => e.IsNew);
}

query = query.Where(e =>
e.Price >= priceMin
&& e.Price <= priceMax
&& e.Surface >= surfaceMin
&& e.Surface <= surfaceMax
);

return query.ToList();
}
}
}
}
}
97 changes: 50 additions & 47 deletions DiscordBot/Modules/RegisterModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,24 @@
using DiscordBot.Filters;
using Microsoft.EntityFrameworkCore;
using SharedDependencies.Model;
using SharedDependencies.Services;

namespace DiscordBot.Modules
{
public class RegisterModule : InteractionModuleBase<SocketInteractionContext>
{
private readonly DataFilter _filter;
private static int MinPrice { get; set; }
private static int MaxPrice { get; set; }
private static int MinSurface { get; set; }
private static int MaxSurface { get; set; }
private static OfferType OfferType { get; set; }
private static EstateType EstateType { get; set; }

private readonly HouseScoutContext _context;
private readonly IDbContextFactory<HouseScoutContext> _contextFactory;

public RegisterModule(DataFilter filter, HouseScoutContext context)
public RegisterModule(IDbContextFactory<HouseScoutContext> contextFactory)
{
_filter = filter;
_context = context;
_contextFactory = contextFactory;
}

public class EstateModal : IModal
Expand All @@ -33,12 +32,7 @@ public class EstateModal : IModal
public string MinPriceInput { get; set; }

[InputLabel("Max Price")]
[ModalTextInput(
"max_price",
placeholder: "99999999",
maxLength: 20,
initValue: "20000"
)]
[ModalTextInput("max_price", placeholder: "99999999", maxLength: 20, initValue: "20000")]
public string MaxPriceInput { get; set; }

[InputLabel("Min Surface Area (m ^ 2)")]
Expand All @@ -53,14 +47,17 @@ public class EstateModal : IModal
[SlashCommand("register", "Register your preferences to receive notifications")]
public async Task Register()
{
var user = _context.Users.FirstOrDefault(u => (ulong)u.UserId == Context.User.Id);
if (user is null)
await using (var context = await _contextFactory.CreateDbContextAsync())
{
await RespondWithModalAsync<EstateModal>("registerModal");
}
else
{
await RespondAsync("User already registered");
var user = context.Users.FirstOrDefault(u => (ulong)u.UserId == Context.User.Id);
if (user is null)
{
await RespondWithModalAsync<EstateModal>("registerModal");
}
else
{
await RespondAsync("User already registered");
}
}
}

Expand All @@ -80,7 +77,7 @@ public async Task HandleEstateDetailsModalSubmit(EstateModal modal)

var builder = new ComponentBuilder().WithSelectMenu(estateSelectMenu);

await RespondAsync("Please select a estate type:", components: builder.Build());
await RespondAsync("Please select an estate type:", components: builder.Build());
}

[ComponentInteraction("estateType")]
Expand All @@ -96,50 +93,56 @@ public async Task HandleEstateTypeSelect(string[] selectedValues)

var builder = new ComponentBuilder().WithSelectMenu(offerSelectMenu);

await RespondAsync("Please select a offer type:", components: builder.Build());
await RespondAsync("Please select an offer type:", components: builder.Build());
}

[ComponentInteraction("offerType")]
public async Task HandleOfferTypeSelect(string[] selectedValues)
{
OfferType = selectedValues[0] == "sale" ? OfferType.SALE : OfferType.RENT;
_context.Users.Add(
new User(
//Postgres allows only this: identity column type must be smallint, integer, or bigint
(long)Context.User.Id,
MinPrice,
MaxPrice,
MinSurface,
MaxSurface,
EstateType,
OfferType,
true
)
);
await _context.SaveChangesAsync();

await using (var context = await _contextFactory.CreateDbContextAsync())
{
context.Users.Add(
new User(
(long)Context.User.Id,
MinPrice,
MaxPrice,
MinSurface,
MaxSurface,
EstateType,
OfferType,
true
)
);
await context.SaveChangesAsync();
}

await RespondAsync(
$"You have been registered with the following preferences:\n"
+ $"**Offer Type:** {OfferType}\n"
+ $"**Price Range:** {MinPrice} - {MaxPrice}\n"
+ $"**Surface Area Range:** {MinSurface} - {MaxSurface}\n"
+ $"**Estate Type:** {EstateType}"
+ $"**Offer Type:** {OfferType}\n"
+ $"**Price Range:** {MinPrice} - {MaxPrice}\n"
+ $"**Surface Area Range:** {MinSurface} - {MaxSurface}\n"
+ $"**Estate Type:** {EstateType}"
);
}

[SlashCommand("unregister", "Unregister your notifications")]
public async Task Unregister()
{
var user = _context.Users.AsNoTracking().FirstOrDefault(u => (ulong)u.UserId == Context.User.Id);
if (user is null)
{
await RespondAsync("User is not registered yet");
}
else
await using (var context = await _contextFactory.CreateDbContextAsync())
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
await RespondAsync("User unregistered");
var user = context.Users.FirstOrDefault(u => (ulong)u.UserId == Context.User.Id);
if (user is null)
{
await RespondAsync("User is not registered yet");
}
else
{
context.Users.Remove(user);
await context.SaveChangesAsync();
await RespondAsync("User unregistered");
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion DiscordBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ private static IHostBuilder CreateHostBuilder() =>
options.UseNpgsql(
configuration.GetConnectionString("DefaultConnection")
)
)
).AddDbContextFactory<HouseScoutContext>(options =>
options.UseNpgsql(
configuration.GetConnectionString("DefaultConnection")))
.AddSingleton<CommandService>()
.AddScoped<DataFilter>()
.AddSingleton<RabbitMQService>()
Expand Down
56 changes: 27 additions & 29 deletions DiscordBot/Services/DiscordMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,52 @@ namespace DiscordBot.Services
public class DiscordMessageHandler : IMessageHandler
{
private readonly DiscordSocketClient _client;
private readonly HouseScoutContext _context;
private readonly IDbContextFactory<HouseScoutContext> _contextFactory;
private readonly DataFilter _dataFilter;

public DiscordMessageHandler(
DiscordSocketClient client,
HouseScoutContext context,
DataFilter dataFilter
)
DataFilter dataFilter, IDbContextFactory<HouseScoutContext> contextFactory)
{
_client = client;
_context = context;
_dataFilter = dataFilter;
_contextFactory = contextFactory;
}

public async Task HandleMessageAsync()
{
var users = await _context.Users.AsNoTracking().ToListAsync();
foreach (var dbUser in users)
await using (var context = await _contextFactory.CreateDbContextAsync())
{
var user = await _client.GetUserAsync((ulong)dbUser.UserId);
if (user != null)
var users = await context.Users.ToListAsync();
foreach (var dbUser in users)
{
var dmChannel = await user.CreateDMChannelAsync();
var estateData = _dataFilter.SurfacePriceFilter(
dbUser.MinPrice,
dbUser.MaxPrice,
dbUser.MinSurface,
dbUser.MaxSurface,
dbUser.IsNew
);
// We need to update the new flag for all user that have it as true to false.
// Because of the AsNoTracking() we need to update it like this
if (dbUser.IsNew)
var user = await _client.GetUserAsync((ulong)dbUser.UserId);
if (user != null)
{
dbUser.IsNew = false;
_context.Users.Attach(dbUser);
_context.Entry(dbUser).Property(u => u.IsNew).IsModified = true;
}
var dmChannel = await user.CreateDMChannelAsync();
var estateData = _dataFilter.SurfacePriceFilter(
dbUser.MinPrice,
dbUser.MaxPrice,
dbUser.MinSurface,
dbUser.MaxSurface,
dbUser.IsNew
);

if (dbUser.IsNew)
{
dbUser.IsNew = false;
}

var messages = PrepareMessages(estateData);
var messages = PrepareMessages(estateData);

foreach (var message in messages)
{
await dmChannel.SendMessageAsync(message);
foreach (var message in messages)
{
await dmChannel.SendMessageAsync(message);
}
}
}
await context.SaveChangesAsync();
}
await _context.SaveChangesAsync();
}

private List<string> PrepareMessages(List<Estate> estateData)
Expand Down

0 comments on commit 38d817f

Please sign in to comment.