This library provides a set of Entity Framework Core abstractions for the unit of work pattern and other commonly used features.
Install-Package Motorbay.EntityFrameworkCore.Abstractionspublic class MyEntity
: IUniqueEntity<long>
{
public long Id { get; set; }
public string Name { get; set; }
}You can extend the RepositoryErrorDescriptor class to add custom error codes or override the existing errors.
public class MyRepositoryErrorDescriptor
: RepositoryErrorDescriptor
{
public RepositoryError EntityWithNameNotFound<TKey, TEntity>(string name)
where TKey : IEquatable<TKey>
where TEntity : class, IUniqueEntity<TKey>
{
return new RepositoryError(
nameof(EntityWithNameNotFound),
$"Entity of type '{typeof(TEntity).Name}' with name '{name}' was not found."
);
}
// Add more custom error codes here...
}This can be passed into the constructor of the DatabaseRepository<TKey, TEntity, TErrorDescriptor> or ReadOnlyDatabaseRepository<TKey, TEntity, TErrorDescriptor> class or registered as a singleton in the DI container.
You can create a repository by inheriting from DatabaseRepository<TEntity, TKey> or ReadOnlyDatabaseRepository<TEntity, TKey> or manually implementing IRepository<TEntity, TKey> or IReadOnlyRepository<TEntity, TKey>.
public class MyEntityRepository
: DatabaseRepository<MyEntity, long, MyRepositoryErrorDescriptor>, IMyEntityRepository
{
public MyEntityRepository(MyDbContext context, MyRepositoryErrorDescriptor errorDescriptor)
: base(context, errorDescriptor)
{
}
public async Task<RepositoryResult<MyEntity>> GetByNameAsync(string name, bool isTracked, CancellationToken cancellationToken = default)
{
MyEntity? entity = await GetQuerable(isTracked).FirstOrDefaultAsync(x => x.Name == name, cancellationToken);
return entity == null
? RepositoryResult<MyEntity>.Failure(ErrorDescriptor.EntityWithNameNotFound<long, MyEntity>(name))
: RepositoryResult<MyEntity>.Success(entity);
}
public Task<RepositoryResult<MyEntity>> GetByNameAsync(string name, CancellationToken cancellationToken = default)
{
return GetByNameAsync(name, isTracked: false, cancellationToken); // Default to no tracking
}
// Add more custom methods here...
}
public interface IMyEntityRepository
: IRepository<MyEntity, long>
{
// This is the recommended way to add custom methods to your repository
Task<RepositoryResult<MyEntity>> GetByNameAsync(string name, bool isTracked, CancellationToken cancellationToken = default);
Task<RepositoryResult<MyEntity>> GetByNameAsync(string name, CancellationToken cancellationToken = default);
// Add more custom methods here...
}Add this to your Program.cs file.
builder.Services.AddDbContext<MyDbContext>(options =>
{
// Configure your database here
});
builder.Services.AddScoped<IMyEntityRepository, MyEntityRepository>();
// OPTIONAL: Register the custom error descriptor
builder.Services.AddSingleton<MyRepositoryErrorDescriptor>();public class MyTimestampedEntity
: ITimestampedEntity
{
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
}public class MyDbContext
: TimestampedDbContext
{
public DbSet<MyTimestampedEntity> MyTimestampedEntities { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyTimestampedEntity>(entity =>
{
entity.Property(x => x.CreatedAt).IsRequired();
entity.Property(x => x.UpdatedAt).IsRequired();
});
}
protected override void UpdateTimestamps(DateTimeOffset date)
{
// OPTIONAL: Override the default update behavior.
}
}