Skip to content
Open
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
1 change: 1 addition & 0 deletions Homeworks/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,47 @@ public interface IRepository<T> where T : BaseEntity
{
Task<IReadOnlyCollection<T>> GetAll(bool withIncludes = false, CancellationToken ct = default);

/// <returns>The entity tracked by the current DbContext if found; otherwise, <see langword="null"/>.</returns>
Task<T?> GetById(Guid id, bool withIncludes = false, CancellationToken ct = default);

Task<IReadOnlyCollection<T>> GetByRangeId(
IEnumerable<Guid> ids,
bool withIncludes = false,
/// <returns>
/// Collection of entities matching the specified ids.
/// Entities are tracked by the current DbContext.
/// </returns>
Task<IReadOnlyCollection<T>> GetByRangeId(IEnumerable<Guid> ids, bool withIncludes = false,
CancellationToken ct = default);

Task<IReadOnlyCollection<T>> GetWhere(
Expression<Func<T, bool>> predicate,
bool withIncludes = false,
/// <returns>
/// Collection of entities matching the specified predicate.
/// Entities are tracked by the current DbContext.
/// </returns>
Task<IReadOnlyCollection<T>> GetWhere(Expression<Func<T, bool>> predicate, bool withIncludes = false,
CancellationToken ct = default);

Task Add(T entity, CancellationToken ct);
/// <summary>
/// Adds a new entity to the current DbContext. Use IUnitOfWork to SaveChangesAsync
/// </summary>
/// <param name="entity">
/// A new entity with tracked references to existing related entities.
/// </param>
void Add(T entity);

/// <exception cref="EntityNotFoundException"/>
Task Update(T entity, CancellationToken ct);
/// <summary>
/// Updates a detached entity by applying its values to the existing entity in the DbContext and saving changes.
/// </summary>
/// <exception cref="EntityNotFoundException">Thrown if the entity with the given Id is not found in the database.</exception>
Task UpdateDetached(T entity, CancellationToken ct);

/// <exception cref="EntityNotFoundException"/>
Task Delete(Guid id, CancellationToken ct);

/// <summary>
/// Adds new entities to the current DbContext. Use IUnitOfWork to SaveChangesAsync.
/// </summary>
/// <param name="entities">
/// New entities with tracked references to existing related entities.
/// </param>
void AddRange(IEnumerable<T> entities);

Task<bool> IsNotEmptyAsync(CancellationToken ct);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace PromoCodeFactory.Core.Abstractions.Repositories;

public interface IUnitOfWork
{
/// <summary>
/// Persists changes for all modified entities currently tracked by the DbContext.
/// </summary>
Task SaveChangesAsync(CancellationToken ct);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace PromoCodeFactory.Core.Domain;

public abstract class BaseEntity
{
public Guid Id { get; set; }
public Guid Id { get; set; } = Guid.NewGuid();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

[*.cs]
csharp_style_namespace_declarations = block_scoped:silent
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace PromoCodeFactory.DataAccess;

public static class DependencyInjection
{
public static void AddInMemoryDataAccess(this IServiceCollection services)
/* public static void AddInMemoryDataAccess(this IServiceCollection services)
{
services.AddSingleton<IRepository<Employee>>(_ =>
new InMemoryRepository<Employee>(SeedData.Employees));
Expand All @@ -23,18 +23,28 @@ public static void AddInMemoryDataAccess(this IServiceCollection services)
new InMemoryRepository<PromoCode>(SeedData.PromoCodes));
services.AddSingleton<IRepository<CustomerPromoCode>>(_ =>
new InMemoryRepository<CustomerPromoCode>(SeedData.CustomerPromoCodes));
}
}*/

public static void AddEfDataAccess(this IServiceCollection services)
public static void AddEfDataAccess(this IServiceCollection services, string contentRootPath)
{
var dbPath = Path.Combine(contentRootPath, "PromoCodeFactory.sqlite");
Console.WriteLine($"dbPath[{dbPath}]");

services.AddDbContext<PromoCodeFactoryDbContext>(builder =>
builder.UseSqlite("Filename=PromoCodeFactory.sqlite"));
builder.UseSqlite(
$"Data Source={dbPath}",
// EfRepository<T> auto Include => therefore EF warns about multiple collection include
// SplitQuery => fixes execution strategy for that auto Include
opt => opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
)
);

services.AddScoped<IUnitOfWork, EfUnitOfWork>();
services.AddScoped<IRepository<Employee>, EmployeeEfRepository>();
services.AddScoped<IRepository<Role>, EfRepository<Role>>();
services.AddScoped<IRepository<Customer>, CustomerEfRepository>();
services.AddScoped<IRepository<PromoCode>, PromoCodeEfRepository>();
services.AddScoped<IRepository<Preference>, EfRepository<Preference>>();
services.AddScoped<IRepository<Preference>, PreferenceEfRepository>();
services.AddScoped<IRepository<CustomerPromoCode>, EfRepository<CustomerPromoCode>>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ public static class HostExtensions
public static async Task SeedDatabase(this IHost host, CancellationToken ct = default)
{
using var scope = host.Services.CreateScope();
var logger = scope.ServiceProvider
.GetRequiredService<ILoggerFactory>()
.CreateLogger("SeedDatabase");
IServiceProvider sp = scope.ServiceProvider;

var logger = sp.GetRequiredService<ILoggerFactory>().CreateLogger("SeedDatabase");
logger.LogInformation("Starting database seed...");
var unitOfWork = sp.GetRequiredService<IUnitOfWork>();

await SeedEntity(scope.ServiceProvider, SeedData.Roles, ct);
await SeedEntity(scope.ServiceProvider, SeedData.Preferences, ct);
await SeedEntity(scope.ServiceProvider, SeedData.Employees, ct);
await SeedEntity(scope.ServiceProvider, SeedData.Customers, ct);
await SeedEntity(scope.ServiceProvider, SeedData.PromoCodes, ct);
await SeedEntity(scope.ServiceProvider, SeedData.CustomerPromoCodes, ct);
await SeedEntity(sp, SeedData.Roles, unitOfWork, ct);
await SeedEntity(sp, SeedData.Preferences, unitOfWork, ct);
await SeedEntity(sp, SeedData.Employees, unitOfWork, ct);
await SeedEntity(sp, SeedData.Customers, unitOfWork, ct);
await SeedEntity(sp, SeedData.PromoCodes, unitOfWork, ct);
await SeedEntity(sp, SeedData.CustomerPromoCodes, unitOfWork, ct);

logger.LogInformation("Database seed completed.");
}
Expand All @@ -52,18 +52,22 @@ private static IHost MigrateDatabase<TDbContext>(this IHost host) where TDbConte
return host;
}

public static async Task SeedEntity<T>(
IServiceProvider serviceProvider,
IReadOnlyCollection<T> entities,
CancellationToken ct)
where T : BaseEntity
public static async Task SeedEntity<T>(IServiceProvider serviceProvider, IReadOnlyCollection<T> entities,
IUnitOfWork unitOfWork, CancellationToken ct) where T : BaseEntity
{
var repository = serviceProvider.GetRequiredService<IRepository<T>>();

if ((await repository.GetAll()).Count > 0)
return;
var ids = entities.Select(x => x.Id);
var existing = await repository.GetByRangeId(ids, ct: ct);

var existingIds = existing.Select(x => x.Id).ToHashSet();

foreach (var entity in entities)
await repository.Add(entity, ct);
{
if (!existingIds.Contains(entity.Id))
repository.Add(entity);
}

await unitOfWork.SaveChangesAsync(ct);
}
}
Loading