GaEpd.AppLibrary
7.0.0
Prefix Reserved
dotnet add package GaEpd.AppLibrary --version 7.0.0
NuGet\Install-Package GaEpd.AppLibrary -Version 7.0.0
<PackageReference Include="GaEpd.AppLibrary" Version="7.0.0" />
<PackageVersion Include="GaEpd.AppLibrary" Version="7.0.0" />
<PackageReference Include="GaEpd.AppLibrary" />
paket add GaEpd.AppLibrary --version 7.0.0
#r "nuget: GaEpd.AppLibrary, 7.0.0"
#:package GaEpd.AppLibrary@7.0.0
#addin nuget:?package=GaEpd.AppLibrary&version=7.0.0
#tool nuget:?package=GaEpd.AppLibrary&version=7.0.0
Georgia EPD-IT App Library
This repo contains a library created by Georgia EPD-IT to provide common classes and tools for our web applications.
(Much of this work was inspired by the ABP Framework.)
How to install
To install, search for "GaEpd.AppLibrary" in the NuGet package manager or run the following command:
dotnet add package GaEpd.AppLibrary
What's included
Domain entities
The following interfaces and abstract implementations of domain entities are provided for domain-driven design:
- The basic
IEntity<TKey>
interface defines an entity with a primary key of the given type. - The special case
IEntity
interface defines an entity with aGUID
primary key. IAuditableEntity<TUserKey>
adds created/updated properties and methods for basic data auditing.ISoftDelete
andISoftDelete<TUserKey>
add properties for "soft deleting" an entity rather than actually- deleting it.
INamedEntity
adds a "Name" string property.IActiveEntity
adds an "Active" boolean property.
There are also abstract classes based on the above interfaces from which you should derive your domain
entities: Entity<TKey>
, AuditableEntity<TKey, TUserKey>
, SoftDeleteEntity<TKey, TUserKey>
,
AuditableSoftDeleteEntity<TKey, TUserKey>
, and StandardNamedEntity
.
The StandardNamedEntity
class derives from AuditableEntity<Guid>
, INamedEntity
, and IActiveEntity
, and includes
methods for enforcing the length of the Name
. Maximum and minimum length for the name can be set by overriding the
default properties.
Example usage:
public class MyEntity : StandardNamedEntity
{
public override int MinNameLength => 2;
public override int MaxNameLength => 50;
public MyEntity() { }
public MyEntity(Guid id, string name) : base(id, name) { }
}
ValueObject
An abstract ValueObject record can help add value objects to your domain entities.
A value object is a compound of properties, such as an address or
date range, that are comparable based solely on their values rather than their references. The properties of a value
object are typically stored with its parent class, not as a separate record with its own ID. Value objects should be
treated as immutable. When deriving from ValueObject
, you must override the GetEqualityComponents()
method to
define which properties to use to determine equality.
Example usage:
[Owned]
public record Address : ValueObject
{
public string Street { get; init; } = string.Empty;
public string? Street2 { get; init; }
public string City { get; init; } = string.Empty;
public string State { get; init; } = string.Empty;
[DataType(DataType.PostalCode)]
public string PostalCode { get; init; } = string.Empty;
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Street;
yield return Street2 ?? string.Empty;
yield return City;
yield return State;
yield return PostalCode;
}
}
Note: The [Owned]
attribute is an Entity Framework attribute defining this as a value object owned by the parent
class. See Owned Entity Types for more info on how
this is implemented in EF Core.
Data Repositories
Common data repository interfaces define basic entity CRUD operations.
- The
IReadRepository
interface defines various read-only operations: Get, Find, Count, Exists, GetList, and GetPagedList. (The Get and Find methods both return an entity, but Get enables the Entity Framework change tracker while Find disables it. Also, if an entity is not found, then Get throws anEntityNotFoundException
while Find returns null. Read more about Tracking vs. No-Tracking Queries.) - The
IWriteRepository
interface defines various write operations: Insert, Update, and Delete. IRepository
combines the above read and write interfaces.INamedEntityRepository
derives fromIRepository
and adds some methods useful for entities expected to have unique names (i.e., entities implementingINamedEntity
).IReadRepositoryWithMapping
adds overloads to the Find, GetList, and GetPagedList methods that use query projection to return data transfer objects (DTOs) rather than complete entities. Use of these methods enables Entity Framework to create much more efficient SQL queries.IRepositoryWithMapping
andINamedEntityRepositoryWithMapping
similarly include the query projection overloads.
Repository Implementations
There are two sets of abstract repository classes that implement the IRepository
, IRepositoryWithMapping
,
INamedEntityRepository
, and INamedEntityRepositoryWithMapping
interfaces. Client applications can derive from either
or both of these implementations.
- One set (in the
LocalRepository
namespace) uses in-memory data (convenient for use during development when the overhead of a database is undesirable). - The other set (in the
EFRepository
namespace) requires an Entity Framework database context (DbContext
).
Example usage:
public interface IMyRepository : INamedEntityRepository<MyEntity> { }
public sealed class MyInMemoryRepository
: LocalRepository.NamedEntityRepository<MyEntity>(MyEntitySeedData), IMyRepository;
public sealed class MyEfRepository(AppDbContext context)
: EFRepository.NamedEntityRepository<MyEntity, AppDbContext>(context), IMyRepository;
Then you can use the desired implementation based on the app environment, for example:
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSingleton<IMyRepository, MyInMemoryRepository>();
}
else
{
builder.Services.AddScoped<IMyRepository, MyEfRepository>();
}
Predicate builder
Code from C# in a Nutshell is included to enable creating
filter expressions that can be combined. The library comes with the commonly used filters WithId(id)
for all entities
and ExcludedDeleted()
for "soft delete" entities.
Example usage:
public static Expression<Func<MyEntity, bool>> IsActive(this Expression<Func<MyEntity, bool>> predicate) =>
predicate.And(e => e.IsActive);
public static Expression<Func<MyEntity, bool>> ActiveAndNotDeletedPredicate() =>
PredicateBuilder.True<MyEntity>().IsActive().ExcludeDeleted();
Pagination classes
IPaginatedRequest
and IPaginatedResult<T>
define how to request and receive paginated (and sorted) search results.
The System.Linq.Dynamic.Core package is included.
List Item record
A ListItem<TKey>
record type defines a key-value pair with fields for ID of type TKey
and string
Name.
The ToSelectList()
extension method takes a ListItem
enumerable and returns an MVC SelectList
which can be used to
create an HTML <select>
element.
Enum extensions
GetDisplayName()
and GetDescription()
return the DisplayAttribute.Name
and DescriptionAttribute
values of an
enum, respectively.
Guard clauses
The GuardClauses package is included by reference.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- AutoMapper (>= 14.0.0 && < 15.0.0)
- GaEpd.GuardClauses (>= 2.1.0)
- Microsoft.EntityFrameworkCore (>= 8.0.15)
- System.Linq.Dynamic.Core (>= 1.6.5)
-
net9.0
- AutoMapper (>= 14.0.0 && < 15.0.0)
- GaEpd.GuardClauses (>= 2.1.0)
- Microsoft.EntityFrameworkCore (>= 9.0.4)
- System.Linq.Dynamic.Core (>= 1.6.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
7.0.0 | 163 | 8/7/2025 |
6.2.0-beta.2 | 34 | 8/1/2025 |
6.2.0-beta.1 | 90 | 7/28/2025 |
6.1.0 | 271 | 6/5/2025 |
6.0.0 | 341 | 3/31/2025 |
5.6.1 | 482 | 3/24/2025 |
5.6.0 | 463 | 3/24/2025 |
5.5.0 | 657 | 1/28/2025 |
5.4.0 | 406 | 1/9/2025 |
5.3.1 | 603 | 9/11/2024 |
5.3.0 | 754 | 9/10/2024 |
5.2.1 | 1,189 | 4/30/2024 |
5.2.0 | 141 | 4/29/2024 |
5.1.0 | 1,069 | 1/3/2024 |
5.0.1 | 137 | 1/2/2024 |
5.0.0 | 150 | 1/2/2024 |
4.1.0 | 599 | 11/9/2023 |
4.0.0 | 230 | 10/25/2023 |
3.5.1 | 380 | 9/20/2023 |
3.5.0 | 186 | 9/19/2023 |
3.4.0 | 153 | 9/18/2023 |
3.3.0 | 375 | 8/11/2023 |
3.2.0 | 643 | 5/22/2023 |
3.1.0 | 469 | 4/25/2023 |
3.0.0 | 773 | 3/27/2023 |
2.0.0 | 335 | 3/7/2023 |
1.1.0 | 722 | 12/22/2022 |
1.0.1 | 480 | 10/14/2022 |
1.0.0 | 908 | 10/6/2022 |
0.4.0-beta | 266 | 9/29/2022 |
0.3.0-beta | 249 | 9/23/2022 |
# Changelog
## [7.0.0] - 2026-08-07
(All changes since version [6.1.0](#610---2025-06-05))
- Added three new repository interfaces: `IReadRepositoryWithMapping`, `IRepositoryWithMapping`, and
`INamedEntityRepositoryWithMapping`. These new repositories inherit the existing repositories and add new methods
enabling [query projection using AutoMapper](https://docs.automapper.io/en/stable/Queryable-Extensions.html). The new
methods take a destination DTO as a type parameter, enabling Entity Framework to create much more efficient SQL
queries.
- **Breaking change:** No-tracking queries now use identity resolution if that is the default tracking behavior set for
the DB context (i.e., `QueryTrackingBehavior.NoTrackingWithIdentityResolution`).
- **Breaking change:** The `PaginatedRequest` class now requires a non-null, non-empty sorting parameter. (Reliable
pagination requires a defined ordering, and the class now enforces that.)
- **Breaking change:** The `OrderByIf` extension method now returns an `IQueryable` rather than an `IOrderedQueryable`,
meaning it cannot be chained with the `ThenBy` method. (The `ordering` parameter already accommodates ordering by
multiple columns, e.g., `source.OrderByIf("Name, Id")`.)
## [6.2.0-beta.2] - 2026-08-01
- **Breaking change:** No-tracking queries now use identity resolution if that is the default tracking behavior set for
the DB context (i.e., `QueryTrackingBehavior.NoTrackingWithIdentityResolution`).
- **Breaking change from v6.2.0-beta.1:** The overloads introducing DTO query projection have been moved to separate
repositories, allowing client applications to opt in if desired. The new repositories use the "WithMapping" suffix,
e.g., `IReadRepositoryWithMapping`.
## [6.2.0-beta.1] - 2026-07-28
- Added overloads to the `Find`, `GetList`, and `GetPagedList` repository methods to enable [query projection using
AutoMapper](https://docs.automapper.io/en/stable/Queryable-Extensions.html). The new overloads take a destination DTO
as a type parameter, enabling Entity Framework to create much more efficient SQL queries.
- **Breaking change:** The `PaginatedRequest` class now requires a non-null, non-empty sorting parameter. (Reliable
pagination requires a defined ordering, and the class now enforces that.)
- **Breaking change:** The `OrderByIf` extension method now returns an `IQueryable` rather than an `IOrderedQueryable`,
meaning it cannot be chained with the `ThenBy` method. (The `ordering` parameter already accommodates ordering by
multiple columns.)
## [6.1.0] - 2025-06-05
- **Breaking change:** Increased the number of overloads in the `IReadRepository` interface by using fewer optional
parameters. (This avoids requiring calls to the read repository methods to be rewritten if updating from a pre-6.0
version, so for some this is an *unbreaking change*. If you have mocked any `IReadRepository` in your unit tests,
though, you might have to update which methods are actually being called.)
## [6.0.0] - 2025-03-31
- **Breaking change:** Reduced the number of overloads in the `IReadRepository` interface by using more optional
parameters. (This may require rewriting calls to the read repository methods.)
- Added optional `includeProperties` parameters to the `GetListAsync()` repository methods.
## [5.6.1] - 2025-03-24
- Added an overload to the `FetchApiDataAsync` method.
## [5.6.0] - 2025-03-24
- Added two utility methods to help with retrieving data from an API:
- `UriCombine()` combines a base URI and path while correctly handling path separators.
- `FetchApiDataAsync<T>()` fetches JSON data from an API endpoint and serializes it to a target type.
## [5.5.0] - 2025-01-28
- Added support for .NET 9.
## [5.4.0] - 2025-01-09
- Added `GetListAsync` overloads that allow you to specify the ordering of the returned list.
- Added `GetPagedListAsync` overloads that allow you to specify what navigation properties to include (when using the
Entity Framework repository).
- The parent type of `EntityNotFoundException` has been changed to `KeyNotFoundException` (instead of `Exception`).
## [5.3.1] - 2024-09-11
- Fixed the Entity Framework repository's `GetAsync` method to enable change tracking for the returned entity.
## [5.3.0] - 2024-09-10
- Added `GetAsync` and `FindAsync` overloads that allow you to specify what navigation properties to include (when using
the Entity Framework repository).
## [5.2.1] - 2024-04-30
- Added a `GetOrderedListAsync` overload method with predicate matching.
## [5.2.0] - 2024-04-30
- Added a string `Truncate` function.
- Added a `GetOrderedListAsync` method to the Named Entity Repository.
## [5.1.0] - 2024-01-03
- Updated the included GuardClauses library to v2.0.0.
## [5.0.1] - 2024-01-02
- Updated changelog for v5.0.0 release.
## [5.0.0] - 2024-01-02
- Upgraded to .NET 8.0.
### Added
- Added entity and repository interfaces that default to using a GUID primary key and updated the abstract classes to
use these new interfaces.
### Changed
- **Breaking changes:**
- Uses of `EntityNotFoundException` will need to be updated to provide the class type. For example,
`EntityNotFoundException(typeof(MyEntity), id)` should be replaced with `EntityNotFoundException<MyEntity>(id)`.
- References to `IEntity<Guid>` may need to be replaced with `IEntity`.
## [4.1.0] - 2023-11-09
- Implement IAsyncDisposable in repositories.
## [4.0.0] - 2023-10-25
- Move GuardClauses to a separate NuGet package.
## [3.5.1] - 2023-09-20
- Derived EF repositories can now specify the DbContext type.
## [3.5.0] - 2023-09-19
- Added an abstract StandardNameEntity along with INamedEntity and IActiveEntity interfaces.
- Added INamedEntityRepository and INamedEntityManager interfaces and implementations.
## [3.4.0] - 2023-09-18
- Included abstract implementations of BaseRepository.
## [3.3.0] - 2023-08-11
- Added a "ConcatWithSeparator" string extension.
- Added "PreviousPageNumber" and "NextPageNumber" properties to the IPaginatedResult interface.
- Made some possible performance improvements to the Enum extensions.
Breaking change: The Enum extensions no longer work with nullable Enum values.
## [3.2.0] - 2023-05-22
- Added a "SetNotDeleted" (undelete) method to the ISoftDelete interface.
## [3.1.0] - 2023-04-25
- Added a "SaveChanges" method to the "write" repository.
## [3.0.0] - 2023-04-25
- Upgraded the library to .NET 7.
## [2.0.0] - 2023-03-07
- Moved the write repository operations to a separate interface.
- Added "Exists" methods to the read repository interface.
- Renamed the user ID properties on auditable entities.
## [1.1.0] - 2023-03-07
- Added predicate builder and common entity filters.
- Added enum extensions.
- Added the System.Linq.Dynamic.Core package.
## [1.0.1] - 2022-10-14
- Added a Readme file to the package.
## [1.0.0] - 2022-10-06
_Initial release._
[7.0.0]: https://github.com/gaepdit/app-library/releases/tag/v7.0.0
[6.2.0-beta.2]: https://github.com/gaepdit/app-library/releases/tag/v6.2.0-beta.2
[6.2.0-beta.1]: https://github.com/gaepdit/app-library/releases/tag/v6.2.0-beta.1
[6.1.0]: https://github.com/gaepdit/app-library/releases/tag/v6.1.0
[6.0.0]: https://github.com/gaepdit/app-library/releases/tag/v6.0.0
[5.6.1]: https://github.com/gaepdit/app-library/releases/tag/v5.6.1
[5.6.0]: https://github.com/gaepdit/app-library/releases/tag/v5.6.0
[5.5.0]: https://github.com/gaepdit/app-library/releases/tag/v5.5.0
[5.4.0]: https://github.com/gaepdit/app-library/releases/tag/v5.4.0
[5.3.1]: https://github.com/gaepdit/app-library/releases/tag/v5.3.1
[5.3.0]: https://github.com/gaepdit/app-library/releases/tag/v5.3.0
[5.2.1]: https://github.com/gaepdit/app-library/releases/tag/v5.2.1
[5.2.0]: https://github.com/gaepdit/app-library/releases/tag/v5.2.0
[5.1.0]: https://github.com/gaepdit/app-library/releases/tag/l%2Fv5.1.0
[5.0.1]: https://github.com/gaepdit/app-library/releases/tag/al%2Fv5.0.1
[5.0.0]: https://github.com/gaepdit/app-library/releases/tag/al%2Fv5.0.0
[4.1.0]: https://github.com/gaepdit/app-library/releases/tag/al%2Fv4.1.0
[4.0.0]: https://github.com/gaepdit/app-library/releases/tag/al%2Fv4.0.0
[3.5.1]: https://github.com/gaepdit/app-library/releases/tag/v3.5.1
[3.5.0]: https://github.com/gaepdit/app-library/releases/tag/v3.5.0
[3.4.0]: https://github.com/gaepdit/app-library/releases/tag/v3.4.0
[3.3.0]: https://github.com/gaepdit/app-library/releases/tag/v3.3.0
[3.2.0]: https://github.com/gaepdit/app-library/releases/tag/v3.2.0
[3.1.0]: https://github.com/gaepdit/app-library/releases/tag/v3.1.0
[3.0.0]: https://github.com/gaepdit/app-library/releases/tag/v3.0.0
[2.0.0]: https://github.com/gaepdit/app-library/releases/tag/v2.0.0
[1.1.0]: https://github.com/gaepdit/app-library/releases/tag/v1.1.0
[1.0.1]: https://github.com/gaepdit/app-library/releases/tag/v1.0.1
[1.0.0]: https://github.com/gaepdit/app-library/releases/tag/v1.0.0