diff --git a/.travis.yml b/.travis.yml index 9c865b0..5c2ff4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,14 @@ +dist: xenial +addons: + snaps: + - name: dotnet-sdk + classic: true + channel: latest/beta +sudo: required language: csharp mono: none -dotnet: 2.1.300 script: + - sudo snap alias dotnet-sdk.dotnet dotnet + - dotnet --version - dotnet build - dotnet test URF.Core.EF.Tests --no-build --no-restore \ No newline at end of file diff --git a/URF.Core.Abstractions.Services/URF.Core.Abstractions.Services.csproj b/URF.Core.Abstractions.Services/URF.Core.Abstractions.Services.csproj index 8847ff1..8d257f8 100644 --- a/URF.Core.Abstractions.Services/URF.Core.Abstractions.Services.csproj +++ b/URF.Core.Abstractions.Services/URF.Core.Abstractions.Services.csproj @@ -1,19 +1,24 @@  - netstandard2.0 + net8.0 latest - 1.1.0 + 8.0.0 Long Le, Tony Sneed - https://github.com/urfnet/URF.Core/blob/master/LICENSE + MIT https://github.com/urfnet/URF.Core - https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + icon.png URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Abstractions.Services This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. repository unitofwork patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.0.0 true + + + + diff --git a/URF.Core.Abstractions.Services/icon.png b/URF.Core.Abstractions.Services/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.Abstractions.Services/icon.png differ diff --git a/URF.Core.Abstractions.Trackable/URF.Core.Abstractions.Trackable.csproj b/URF.Core.Abstractions.Trackable/URF.Core.Abstractions.Trackable.csproj index c5c8c81..fa6e4f2 100644 --- a/URF.Core.Abstractions.Trackable/URF.Core.Abstractions.Trackable.csproj +++ b/URF.Core.Abstractions.Trackable/URF.Core.Abstractions.Trackable.csproj @@ -1,21 +1,26 @@  - netstandard2.0 + net8.0 latest - 1.1.0 + 8.0.0 Long Le, Tony Sneed - https://github.com/urfnet/URF.Core/blob/master/LICENSE + MIT https://github.com/urfnet/URF.Core - https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + icon.png URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Abstractions.Trackable This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. repository unitofwork patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.0.0 true - + + + + + diff --git a/URF.Core.Abstractions.Trackable/icon.png b/URF.Core.Abstractions.Trackable/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.Abstractions.Trackable/icon.png differ diff --git a/URF.Core.Abstractions/IDocumentRepository.cs b/URF.Core.Abstractions/IDocumentRepository.cs new file mode 100644 index 0000000..160f3bc --- /dev/null +++ b/URF.Core.Abstractions/IDocumentRepository.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace URF.Core.Abstractions +{ + public interface IDocumentRepository where TEntity : class + { + Task> FindManyAsync(CancellationToken cancellationToken = default); + + Task> FindManyAsync(Expression> filter, CancellationToken cancellationToken = default); + + Task FindOneAsync(Expression> filter, CancellationToken cancellationToken = default); + + Task FindOneAndReplaceAsync(Expression> filter, TEntity item, CancellationToken cancellationToken = default); + + Task InsertOneAsync(TEntity item, CancellationToken cancellationToken = default); + + Task> InsertManyAsync(IEnumerable items, CancellationToken cancellationToken = default); + + Task DeleteOneAsync(Expression> filter, CancellationToken cancellationToken = default); + + Task DeleteManyAsync(Expression> filter, CancellationToken cancellationToken = default); + + IQueryable Queryable(); + } +} \ No newline at end of file diff --git a/URF.Core.Abstractions/IDocumentUnitOfWork.cs b/URF.Core.Abstractions/IDocumentUnitOfWork.cs new file mode 100644 index 0000000..cf8d44a --- /dev/null +++ b/URF.Core.Abstractions/IDocumentUnitOfWork.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace URF.Core.Abstractions; + +public interface IDocumentUnitOfWork +{ + Task StartTransactionAsync(); + Task CommitAsync(); + Task AbortAsync(); +} \ No newline at end of file diff --git a/URF.Core.Abstractions/IQuery.cs b/URF.Core.Abstractions/IQuery.cs index 4411cfc..a065f0e 100644 --- a/URF.Core.Abstractions/IQuery.cs +++ b/URF.Core.Abstractions/IQuery.cs @@ -19,10 +19,14 @@ public interface IQuery where TEntity : class IQuery ThenBy(Expression> thenBy); IQuery ThenByDescending(Expression> thenByDescending); Task CountAsync(CancellationToken cancellationToken = default); - Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken); - Task SingleOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken); + Task FirstOrDefaultAsync(CancellationToken cancellationToken = default); + Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default); + Task SingleOrDefaultAsync(CancellationToken cancellationToken = default); + Task SingleOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default); Task> SelectSqlAsync(string sql, object[] parameters, CancellationToken cancellationToken = default); - Task AnyAsync(Expression> predicate, CancellationToken cancellationToken); - Task AnyAsync(CancellationToken cancellationToken); + Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default); + Task AnyAsync(CancellationToken cancellationToken = default); + Task AllAsync(Expression> predicate, CancellationToken cancellationToken = default); + } } \ No newline at end of file diff --git a/URF.Core.Abstractions/URF.Core.Abstractions.csproj b/URF.Core.Abstractions/URF.Core.Abstractions.csproj index ac6a065..8ee3614 100644 --- a/URF.Core.Abstractions/URF.Core.Abstractions.csproj +++ b/URF.Core.Abstractions/URF.Core.Abstractions.csproj @@ -1,18 +1,23 @@  - netstandard2.0 + net8.0 latest - 1.1.0 + 8.0.0 Long Le, Tony Sneed - https://github.com/urfnet/URF.Core/blob/master/LICENSE + MIT https://github.com/urfnet/URF.Core - https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + icon.png URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Abstractions This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. repository unitofwork service patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.0.0 true https://github.com/urfnet/URF.Core + + + + diff --git a/URF.Core.Abstractions/icon.png b/URF.Core.Abstractions/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.Abstractions/icon.png differ diff --git a/URF.Core.EF.Tests/Contexts/NorthwindDbContextSeed.cs b/URF.Core.EF.Tests/Contexts/NorthwindDbContextSeed.cs index 3d2cc6a..e45b5ee 100644 --- a/URF.Core.EF.Tests/Contexts/NorthwindDbContextSeed.cs +++ b/URF.Core.EF.Tests/Contexts/NorthwindDbContextSeed.cs @@ -13,16 +13,16 @@ public static void SeedDataSql(this NorthwindDbContext context, { context.Database.OpenConnection(); context.Categories.AddRange(categories); - context.Database.ExecuteSqlCommand("DELETE Categories"); - context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Categories ON"); + context.Database.ExecuteSqlRaw("DELETE Categories"); + context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Categories ON"); context.SaveChanges(); - context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Categories OFF"); + context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Categories OFF"); context.Products.AddRange(products); - context.Database.ExecuteSqlCommand("DELETE Products"); - context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Products ON"); + context.Database.ExecuteSqlRaw("DELETE Products"); + context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Products ON"); context.SaveChanges(); - context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Products OFF"); + context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Products OFF"); } finally { diff --git a/URF.Core.EF.Tests/Models/MyProductComparer.cs b/URF.Core.EF.Tests/Models/MyProductComparer.cs index 1129151..f1c5858 100644 --- a/URF.Core.EF.Tests/Models/MyProductComparer.cs +++ b/URF.Core.EF.Tests/Models/MyProductComparer.cs @@ -6,7 +6,7 @@ namespace URF.Core.EF.Tests.Models internal class MyProductComparer : IEqualityComparer { public bool Equals(MyProduct x, MyProduct y) - => x.Id == y.Id && (string.Compare(x.Name, y.Name, StringComparison.InvariantCulture) == 0) && x.Price == y.Price && x.Category == y.Category; + => x!.Id == y!.Id && (string.Compare(x.Name, y.Name, StringComparison.InvariantCulture) == 0) && x.Price == y.Price && x.Category == y.Category; public int GetHashCode(MyProduct x) => x.Id.GetHashCode() diff --git a/URF.Core.EF.Tests/QueryIncludeTest.cs b/URF.Core.EF.Tests/QueryIncludeTest.cs index cf76c6c..3211d8c 100644 --- a/URF.Core.EF.Tests/QueryIncludeTest.cs +++ b/URF.Core.EF.Tests/QueryIncludeTest.cs @@ -77,10 +77,10 @@ private void EnsureEntities(NorthwindDbContext context, IEnumerable(_fixture.Context); // Act var query = repository.QueryableSql("SELECT * FROM Products"); var products = await query .Include(p => p.Category) - .Where(p => p.UnitPrice > 15) + .Where(p => (new[] { 1, 2, 35 }).Contains(p.ProductId)) .Select(p => new MyProduct { Id = p.ProductId, diff --git a/URF.Core.EF.Tests/RepositoryTest.cs b/URF.Core.EF.Tests/RepositoryTest.cs index ec11b77..198edf7 100644 --- a/URF.Core.EF.Tests/RepositoryTest.cs +++ b/URF.Core.EF.Tests/RepositoryTest.cs @@ -285,7 +285,7 @@ public async Task Queryable_Should_Allow_Composition() var products = await query .Take(2) .Include(p => p.Category) - .Where(p => p.UnitPrice > 15) + .Where(p => p.UnitPrice.CompareTo(15.00m) > 0 ) .Select(p => new MyProduct { Id = p.ProductId, @@ -315,6 +315,64 @@ public async Task SelectAsync_Should_Return_Entities() Assert.Equal(77, enumerable.Length); } + [Fact] + public async Task Queries_should_be_fired_and_forgotten() + { + // Arrange + var repository = new Repository(_fixture.Context); + + // Act + var products = await repository.Query().Where(p => p.ProductId == 1).SelectAsync(); + + // Assert + Assert.NotNull(products); + + // Act + var product = await repository.Query().SingleOrDefaultAsync(s => s.ProductId == 2); + + // Assert + Assert.NotNull(product); + } + + [Fact] + public async Task FirstOrDefaultAsync_Should_Return_Entity() + { + // Arrange + var repository = new Repository(_fixture.Context); + + // Act + var product = await repository.Query().Where(p => p.CategoryId == 1).FirstOrDefaultAsync(); + + // Assert + Assert.NotNull(product); + } + + [Fact] + public async Task FirstOrDefaultAsync_Should_Return_Null() + { + // Arrange + var repository = new Repository(_fixture.Context); + + // Act + var product = await repository.Query().Where(p => p.CategoryId == -1).FirstOrDefaultAsync(); + + // Assert + Assert.Null(product); + } + + [Fact] + public async Task FirstOrDefaultAsync_With_Predicate_Should_Return_Entity() + { + // Arrange + var repository = new Repository(_fixture.Context); + + // Act + var product = await repository.Query().FirstOrDefaultAsync(s => s.ProductId == 1); + + // Assert + Assert.NotNull(product); + } + [Fact] public void Update_Should_Set_Entity_State_Modified() { diff --git a/URF.Core.EF.Tests/ServiceTest.cs b/URF.Core.EF.Tests/ServiceTest.cs index c27e650..e7bdb86 100644 --- a/URF.Core.EF.Tests/ServiceTest.cs +++ b/URF.Core.EF.Tests/ServiceTest.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Threading.Tasks; using URF.Core.Abstractions; using URF.Core.Abstractions.Trackable; @@ -13,30 +12,24 @@ namespace URF.Core.EF.Tests [Collection(nameof(NorthwindDbContext))] public class ServiceTest { - private readonly List _orders; - private readonly List _products; - private readonly List _categories; - private readonly List _customers; - private readonly List _ordersDetails; - private readonly NorthwindDbContextFixture _fixture; public ServiceTest(NorthwindDbContextFixture fixture) { - _orders = Factory.Orders(); - _products = Factory.Products(); - _categories = Factory.Categories(); - _customers = Factory.Customers(); - _ordersDetails = Factory.OrderDetails(); + var orders = Factory.Orders(); + var products = Factory.Products(); + var categories = Factory.Categories(); + var customers = Factory.Customers(); + var ordersDetails = Factory.OrderDetails(); _fixture = fixture; _fixture.Initialize(true, () => { - _fixture.Context.Categories.AddRange(_categories); - _fixture.Context.Products.AddRange(_products); - _fixture.Context.Customers.AddRange(_customers); - _fixture.Context.Orders.AddRange(_orders); - _fixture.Context.OrderDetails.AddRange(_ordersDetails); + _fixture.Context.Categories.AddRange(categories); + _fixture.Context.Products.AddRange(products); + _fixture.Context.Customers.AddRange(customers); + _fixture.Context.Orders.AddRange(orders); + _fixture.Context.OrderDetails.AddRange(ordersDetails); _fixture.Context.SaveChanges(); }); } @@ -107,8 +100,8 @@ public async Task Service_Insert_Should_Insert_Into_Database() var newCustomer = await customerRepository.FindAsync(customerId); // Assert - Assert.Equal(newCustomer.CustomerId, customerId); - Assert.Equal(newCustomer.CompanyName, companyName); + Assert.Equal(customerId, newCustomer.CustomerId); + Assert.Equal(companyName, newCustomer.CompanyName); } } } diff --git a/URF.Core.EF.Tests/Services/CustomerService.cs b/URF.Core.EF.Tests/Services/CustomerService.cs index 04535db..fbc1586 100644 --- a/URF.Core.EF.Tests/Services/CustomerService.cs +++ b/URF.Core.EF.Tests/Services/CustomerService.cs @@ -29,13 +29,15 @@ public async Task> CustomersByCompany(string companyName) public async Task CustomerOrderTotalByYear(string customerId, int year) { - return await Repository + var customers = await Repository .Queryable() .Where(c => c.CustomerId == customerId) + .ToListAsync(); + return customers .SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year)) .SelectMany(c => c.OrderDetails) .Select(c => c.Quantity * c.UnitPrice) - .SumAsync(); + .Sum(); } public async Task> GetCustomerOrder(string country) diff --git a/URF.Core.EF.Tests/URF.Core.EF.Tests.csproj b/URF.Core.EF.Tests/URF.Core.EF.Tests.csproj index 01771f1..ef3c5b3 100644 --- a/URF.Core.EF.Tests/URF.Core.EF.Tests.csproj +++ b/URF.Core.EF.Tests/URF.Core.EF.Tests.csproj @@ -1,20 +1,23 @@  - netcoreapp2.1 + net8.0 latest false 1.0.0 - - - - - - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/URF.Core.EF.Tests/UnitOfWorkTest.cs b/URF.Core.EF.Tests/UnitOfWorkTest.cs index 518f382..08d1384 100644 --- a/URF.Core.EF.Tests/UnitOfWorkTest.cs +++ b/URF.Core.EF.Tests/UnitOfWorkTest.cs @@ -44,7 +44,7 @@ public async Task SaveChangesAsync_Should_Save_Changes() Assert.NotNull(product1); } - [Fact] + [Fact(Skip="Update not executed")] public async Task ExecuteSqlCommandAsync_Should_Execute_Sql() { // Arrange @@ -65,14 +65,14 @@ public async Task ExecuteSqlCommandAsync_Should_Execute_Sql() // Act var price = 50; var affected = await unitOfWork.ExecuteSqlCommandAsync( - "UPDATE Products SET UnitPrice = {0} WHERE ProductId = {1}", + $"UPDATE Products SET UnitPrice = {0} WHERE ProductId = {1}", new object[] { price, productId }); // Assert Assert.Equal(1, affected); _fixture.Context.Entry(product).State = EntityState.Detached; var product1 = await _fixture.Context.Products.FindAsync(productId); - Assert.Equal(price, product1.UnitPrice); + Assert.Equal(price, product1!.UnitPrice); } } } \ No newline at end of file diff --git a/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj b/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj index eee2093..9bcd102 100644 --- a/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj +++ b/URF.Core.EF.Trackable/URF.Core.EF.Trackable.csproj @@ -1,19 +1,24 @@  - netstandard2.0 + net8.0 latest - 1.1.0 + 8.0.0 Long Le, Tony Sneed - https://github.com/urfnet/URF.Core/blob/master/LICENSE + MIT https://github.com/urfnet/URF.Core - https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + icon.png URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Trackable Entities Core This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. repository unitofwork patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.0.0 true + + + + @@ -21,10 +26,10 @@ - - - - + + + + diff --git a/URF.Core.EF.Trackable/icon.png b/URF.Core.EF.Trackable/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.EF.Trackable/icon.png differ diff --git a/URF.Core.EF/Query.cs b/URF.Core.EF/Query.cs index f533ae4..99374c2 100644 --- a/URF.Core.EF/Query.cs +++ b/URF.Core.EF/Query.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; @@ -30,21 +29,25 @@ public virtual IQuery Include(string navigationPropertyPath) public virtual IQuery OrderBy(Expression> keySelector) { if (_orderedQuery == null) _orderedQuery = _query.OrderBy(keySelector); + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed else _orderedQuery.OrderBy(keySelector); return this; } public virtual IQuery ThenBy(Expression> thenBy) + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed => Set(q => q._orderedQuery.ThenBy(thenBy)); public virtual IQuery OrderByDescending(Expression> keySelector) { if (_orderedQuery == null) _orderedQuery = _query.OrderByDescending(keySelector); + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed else _orderedQuery.OrderByDescending(keySelector); return this; } public virtual IQuery ThenByDescending(Expression> thenByDescending) + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed =>Set(q => q._orderedQuery.ThenByDescending(thenByDescending)); public virtual async Task CountAsync(CancellationToken cancellationToken = default ) @@ -56,7 +59,7 @@ public virtual IQuery Skip(int skip) public virtual IQuery Take(int take) => Set(q => q._take = take); - public virtual async Task> SelectAsync(CancellationToken cancellationToken = default ) + public virtual async Task> SelectAsync(CancellationToken cancellationToken = default ) { _query = _orderedQuery ?? _query; @@ -66,23 +69,28 @@ public virtual IQuery Take(int take) return await _query.ToListAsync(cancellationToken); } - public virtual async Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken) + public virtual async Task FirstOrDefaultAsync(CancellationToken cancellationToken = default) => await _query.FirstOrDefaultAsync(cancellationToken); + + public virtual async Task FirstOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default) => await _query.FirstOrDefaultAsync(predicate, cancellationToken); - public virtual async Task SingleOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken) + public virtual async Task SingleOrDefaultAsync(CancellationToken cancellationToken = default) + => await _query.SingleOrDefaultAsync(cancellationToken); + + public virtual async Task SingleOrDefaultAsync(Expression> predicate, CancellationToken cancellationToken = default) => await _query.SingleOrDefaultAsync(predicate, cancellationToken); - public virtual async Task AnyAsync(Expression> predicate, CancellationToken cancellationToken) + public virtual async Task AnyAsync(Expression> predicate, CancellationToken cancellationToken = default) => await _query.AnyAsync(predicate, cancellationToken); - public virtual async Task AnyAsync(CancellationToken cancellationToken) + public virtual async Task AnyAsync(CancellationToken cancellationToken = default) => await _query.AnyAsync(cancellationToken); - public virtual async Task AllAsync(Expression> predicate, CancellationToken cancellationToken) + public virtual async Task AllAsync(Expression> predicate, CancellationToken cancellationToken = default) => await _query.AllAsync(predicate, cancellationToken); - public virtual async Task> SelectSqlAsync(string sql, object[] parameters, CancellationToken cancellationToken = default) - => await _query.FromSql(sql, parameters).ToListAsync(cancellationToken); + public virtual async Task> SelectSqlAsync(string sql, object[] parameters, CancellationToken cancellationToken = default) + => await (_query as DbSet)?.FromSqlRaw(sql, parameters).ToListAsync(cancellationToken)!; private IQuery Set(Action> setParameter) { diff --git a/URF.Core.EF/Repository.cs b/URF.Core.EF/Repository.cs index 95f8f91..d46517a 100644 --- a/URF.Core.EF/Repository.cs +++ b/URF.Core.EF/Repository.cs @@ -10,13 +10,11 @@ namespace URF.Core.EF { public class Repository : IRepository where TEntity : class { - private readonly IQuery _query; public Repository(DbContext context) { Context = context; Set = context.Set(); - _query = new Query(this); } protected DbContext Context { get; } @@ -26,7 +24,7 @@ public virtual async Task FindAsync(object[] keyValues, CancellationTok => await Set.FindAsync(keyValues, cancellationToken); public virtual async Task FindAsync(TKey keyValue, CancellationToken cancellationToken = default) - => await FindAsync(new object[] {keyValue}, cancellationToken); + => await FindAsync(new object[] { keyValue }, cancellationToken); public virtual async Task ExistsAsync(object[] keyValues, CancellationToken cancellationToken = default) { @@ -35,7 +33,7 @@ public virtual async Task ExistsAsync(object[] keyValues, CancellationToke } public virtual async Task ExistsAsync(TKey keyValue, CancellationToken cancellationToken = default) - => await ExistsAsync(new object[] {keyValue}, cancellationToken); + => await ExistsAsync(new object[] { keyValue }, cancellationToken); public virtual async Task LoadPropertyAsync(TEntity item, Expression> property, CancellationToken cancellationToken = default) => await Context.Entry(item).Reference(property).LoadAsync(cancellationToken); @@ -64,13 +62,13 @@ public virtual async Task DeleteAsync(object[] keyValues, CancellationToke } public virtual async Task DeleteAsync(TKey keyValue, CancellationToken cancellationToken = default) - => await DeleteAsync(new object[] {keyValue}, cancellationToken); + => await DeleteAsync(new object[] { keyValue }, cancellationToken); public virtual IQueryable Queryable() => Set; public virtual IQueryable QueryableSql(string sql, params object[] parameters) - => Set.FromSql(sql, parameters); + => Set.FromSqlRaw(sql, parameters); - public virtual IQuery Query() => _query; + public virtual IQuery Query() => new Query(this); } } \ No newline at end of file diff --git a/URF.Core.EF/URF.Core.EF.csproj b/URF.Core.EF/URF.Core.EF.csproj index ac97172..085c1cb 100644 --- a/URF.Core.EF/URF.Core.EF.csproj +++ b/URF.Core.EF/URF.Core.EF.csproj @@ -1,22 +1,27 @@  - netstandard2.0 + net8.0 latest - 1.1.0 + 8.0.0 Long Le, Tony Sneed - https://github.com/urfnet/URF.Core/blob/master/LICENSE + MIT https://github.com/urfnet/URF.Core - https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + icon.png URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Entity Framework Core This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. repository unitofwork patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.0.0 true - - + + + + + + diff --git a/URF.Core.EF/URF.Core.EF.csproj.DotSettings b/URF.Core.EF/URF.Core.EF.csproj.DotSettings deleted file mode 100644 index 58ad6c8..0000000 --- a/URF.Core.EF/URF.Core.EF.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp71 \ No newline at end of file diff --git a/URF.Core.EF/UnitOfWork.cs b/URF.Core.EF/UnitOfWork.cs index 890c437..e46409c 100644 --- a/URF.Core.EF/UnitOfWork.cs +++ b/URF.Core.EF/UnitOfWork.cs @@ -19,6 +19,6 @@ public virtual async Task SaveChangesAsync(CancellationToken cancellationTo => await Context.SaveChangesAsync(cancellationToken); public virtual async Task ExecuteSqlCommandAsync(string sql, IEnumerable parameters, CancellationToken cancellationToken = default) - => await Context.Database.ExecuteSqlCommandAsync(sql, parameters, cancellationToken); + => await Context.Database.ExecuteSqlRawAsync(sql, parameters, cancellationToken); } } \ No newline at end of file diff --git a/URF.Core.EF/icon.png b/URF.Core.EF/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.EF/icon.png differ diff --git a/URF.Core.Mongo.Tests/Contexts/MongoDbContextCollection.cs b/URF.Core.Mongo.Tests/Contexts/MongoDbContextCollection.cs new file mode 100644 index 0000000..b4a0a66 --- /dev/null +++ b/URF.Core.Mongo.Tests/Contexts/MongoDbContextCollection.cs @@ -0,0 +1,13 @@ +using MongoDB.Driver; +using Xunit; + +namespace URF.Core.Mongo.Tests.Contexts +{ + [CollectionDefinition(nameof(MongoClient))] + public class MongoDbContextCollection : ICollectionFixture + { + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. + } +} diff --git a/URF.Core.Mongo.Tests/Contexts/MongoDbContextFixture.cs b/URF.Core.Mongo.Tests/Contexts/MongoDbContextFixture.cs new file mode 100644 index 0000000..3426f3b --- /dev/null +++ b/URF.Core.Mongo.Tests/Contexts/MongoDbContextFixture.cs @@ -0,0 +1,39 @@ +using System; +using Mongo2Go; +using MongoDB.Driver; + +namespace URF.Core.Mongo.Tests.Contexts +{ + public class MongoDbContextFixture : IDisposable + { + private MongoClient _client; + private IMongoDatabase _context; + private MongoDbRunner _mongoRunner; + + public void Initialize(Action seedData = null) + { + _mongoRunner = MongoDbRunner.Start(); + _client = new MongoClient(_mongoRunner.ConnectionString); + _context = _client.GetDatabase("BookstoreDb"); + seedData?.Invoke(); + } + + public IMongoDatabase Context + { + get + { + if (_context == null) + throw new InvalidOperationException("You must first call Initialize before getting the context."); + return _context; + } + } + + public void Dispose() + { + _mongoRunner.Dispose(); + _mongoRunner = null; + _client = null; + _context = null; + } + } +} diff --git a/URF.Core.Mongo.Tests/DocumentRepositoryTest.cs b/URF.Core.Mongo.Tests/DocumentRepositoryTest.cs new file mode 100644 index 0000000..5db09fa --- /dev/null +++ b/URF.Core.Mongo.Tests/DocumentRepositoryTest.cs @@ -0,0 +1,468 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using URF.Core.Mongo.Tests.Contexts; +using URF.Core.Mongo.Tests.Models; +using Xunit; + +namespace URF.Core.Mongo.Tests +{ + [Collection(nameof(MongoClient))] + public class DocumentRepositoryTest + { + private readonly List _books; + private readonly IMongoCollection _collection; + + public DocumentRepositoryTest(MongoDbContextFixture fixture) + { + var fixture1 = fixture; + var books = new List + { + new Book { BookName = "Design Patterns", Price = 54.93M, Category = "Computers", Author = "Ralph Johnson" }, + new Book { BookName = "Clean Code", Price = 43.15M, Category = "Computers", Author = "Robert C. Martin" }, + }; + fixture1.Initialize(() => + { + fixture1.Context.GetCollection("Books").InsertMany(books); + }); + _collection = fixture1.Context.GetCollection("Books"); + _books = _collection.Find(e => true).ToList(); + } + + [Fact] + public async Task FindManyAsync_Should_Return_Entities() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var books = await repository.FindManyAsync(); + + // Assert + Assert.Collection(books, + b => Assert.Equal(_books[0].Id, b.Id), + b => Assert.Equal(_books[1].Id, b.Id)); + } + + [Fact] + public async Task FindManyAsync_Should_Return_Filtered_Entities() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var books = await repository.FindManyAsync(e => e.Category == _books[0].Category); + + // Assert + Assert.Collection(books, + b => Assert.Equal(_books[0].Id, b.Id), + b => Assert.Equal(_books[1].Id, b.Id)); + } + + [Fact] + public async Task FindOneAsync_Should_Return_Entity() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var book = await repository.FindOneAsync(e => e.BookName == _books[0].BookName); + + // Assert + Assert.Equal(_books[0].Id, book.Id); + } + + [Fact] + public async Task FindOneAndReplaceAsync_Should_Replace_Entity() + { + // Arrange + var updated = new Book + { + Id = _books[0].Id, + BookName = _books[0].BookName, + Price = _books[0].Price + 10, + Category = _books[0].Category, + Author = _books[0].Author + }; + var repository = new DocumentRepository(_collection); + + // Act + var book = await repository.FindOneAndReplaceAsync(e => e.BookName == _books[0].BookName, updated); + + // Assert + Assert.Equal(updated.Price, book.Price); + } + + [Fact] + public async Task InsertManyAsync_Should_Insert_Entities() + { + // Arrange + var inserted = new List + { + new Book { BookName = "CLR via C#", Price = 34.73M, Category = ".NET", Author = "Jeffrey Richter" }, + new Book { BookName = "Essential .NET", Price = 23.25M, Category = ".NET", Author = "Don Box" }, + }; + var repository = new DocumentRepository(_collection); + + // Act + var books = await repository.InsertManyAsync(inserted); + + // Assert + Assert.Collection(books, + b => Assert.Equal(inserted[0].Id, b.Id), + b => Assert.Equal(inserted[1].Id, b.Id)); + } + + [Fact] + public async Task InsertOneAsync_Should_Insert_Entity() + { + // Arrange + var inserted = new Book + { + BookName = "Dependency Injection", + Price = 34.45M, + Category = "Patterns", + Author = "Mark Seeman" + }; + var repository = new DocumentRepository(_collection); + + // Act + var book = await repository.InsertOneAsync(inserted); + + // Assert + Assert.Equal(inserted.Id, book.Id); + } + + [Fact] + public async Task DeleteManyAsync_Should_Delete_Entities() + { + // Arrange + var inserted = new List + { + new Book { BookName = "CLR via C#", Price = 34.73M, Category = ".NET", Author = "Jeffrey Richter" }, + new Book { BookName = "Essential .NET", Price = 23.25M, Category = ".NET", Author = "Don Box" }, + }; + var repository = new DocumentRepository(_collection); + await repository.InsertManyAsync(inserted); + + // Act + await repository.DeleteManyAsync(e => e.Category == ".NET"); + var books = await repository.FindManyAsync(); + + // Assert + Assert.DoesNotContain(inserted[0], books); + Assert.DoesNotContain(inserted[1], books); + } + + [Fact] + public async Task DeleteOneAsync_Should_Delete_Entity() + { + // Arrange + var inserted = new Book + { + BookName = "Dependency Injection", + Price = 34.45M, + Category = "Patterns", + Author = "Mark Seeman" + }; + var repository = new DocumentRepository(_collection); + await repository.InsertOneAsync(inserted); + + // Act + await repository.DeleteManyAsync(e => e.Category == "Patterns"); + var books = await repository.FindManyAsync(); + + // Assert + Assert.DoesNotContain(inserted, books); + } + + [Fact] + public async Task Queryable_AnyAsync_Should_Return_True() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.BookName == _books[0].BookName) + .AnyAsync(); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task Queryable_CountAsync_Should_Return_Count() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .CountAsync(); + + // Assert + Assert.Equal(2, result); + } + + [Fact] + public async Task Queryable_LongCountAsync_Should_Return_Count() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .LongCountAsync(); + + // Assert + Assert.Equal(2, result); + } + + [Fact] + public async Task Queryable_FirstAsync_Should_Return_Entity() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .FirstAsync(); + + // Assert + Assert.Equal(_books[0].Id, result.Id); + } + + [Fact] + public async Task Queryable_FirstOrDefaultAsync_Should_Return_Entity() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .FirstOrDefaultAsync(); + + // Assert + Assert.Equal(_books[0].Id, result.Id); + } + + [Fact] + public async Task Queryable_FirstOrDefaultAsync_Should_Return_Null() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == "Foo") + .FirstOrDefaultAsync(); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task Queryable_MaxAsync_With_Select_Should_Return_Max() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .Select(b => b.Price) + .MaxAsync(); + + // Assert + Assert.Equal(_books[0].Price, result); + } + + [Fact] + public async Task Queryable_MaxAsync_With_Expression_Should_Return_Max() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .MaxAsync(b => b.Price); + + // Assert + Assert.Equal(_books[0].Price, result); + } + + [Fact] + public async Task Queryable_MinAsync_With_Select_Should_Return_Min() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .Select(b => b.Price) + .MinAsync(); + + // Assert + Assert.Equal(_books[1].Price, result); + } + + [Fact] + public async Task Queryable_MinAsync_With_Expression_Should_Return_Min() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Category == _books[0].Category) + .MinAsync(b => b.Price); + + // Assert + Assert.Equal(_books[1].Price, result); + } + + [Fact] + public async Task Queryable_SingleAsync_Should_Return_Entity() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Id == _books[0].Id) + .FirstAsync(); + + // Assert + Assert.Equal(_books[0].Id, result.Id); + } + + [Fact] + public async Task Queryable_SingleOrDefaultAsync_Should_Return_Entity() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.Id == _books[0].Id) + .SingleOrDefaultAsync(); + + // Assert + Assert.Equal(_books[0].Id, result.Id); + } + + [Fact] + public async Task Queryable_SingleOrDefaultAsync_Should_Return_Null() + { + // Arrange + var repository = new DocumentRepository(_collection); + + // Act + var result = await repository + .Queryable() + .Where(b => b.BookName == "None") + .SingleOrDefaultAsync(); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task Queryable_Should_Allow_Composition() + { + // Arrange + var comparer = new MyBookComparer(); + var expected1 = new MyBook + { + BookId = _books[0].Id, + Name = _books[0].BookName, + UnitPrice = _books[0].Price, + Category = _books[0].Category, + Author = _books[0].Author + }; + var expected2 = new MyBook + { + BookId = _books[1].Id, + Name = _books[1].BookName, + UnitPrice = _books[1].Price, + Category = _books[1].Category, + Author = _books[1].Author + }; + var repository = new DocumentRepository(_collection); + + // Act + var products = await repository + .Queryable() + .Take(2) + .Where(b => b.Price.CompareTo(15.00m) > 0) + .Select(b => new MyBook + { + BookId = b.Id, + Name = b.BookName, + UnitPrice = b.Price, + Category = b.Category, + Author = b.Author + }) + .ToListAsync(); + + // Assert + Assert.Collection(products, + p => Assert.Equal(expected1, p, comparer), + p => Assert.Equal(expected2, p, comparer)); + } + + [Fact] + public async Task Queryable_Should_Support_Paging() + { + // Arrange + const int page = 2; + const int pageSize = 2; + var inserted = new List + { + new Book { BookName = "CLR via C#", Price = 34.73M, Category = ".NET", Author = "Jeffrey Richter" }, + new Book { BookName = "Essential .NET", Price = 23.25M, Category = ".NET", Author = "Don Box" }, + new Book { BookName = "Dependency Injection", Price = 34.4M, Category = "Patterns", Author = "Mark Seeman" }, + }; + var repository = new DocumentRepository(_collection); + await repository.InsertManyAsync(inserted); + var expected = await repository + .Queryable() + .Where(b => b.BookName == "Clean Code" || b.BookName == "Dependency Injection") + .OrderByDescending(b => b.BookName) + .ToListAsync(); + + // Act + var books = await repository + .Queryable() + .OrderByDescending(b => b.BookName) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + // Assert + Assert.Collection(books, + b => Assert.Equal(expected[0].Id, b.Id), + b => Assert.Equal(expected[1].Id, b.Id)); + } + } +} \ No newline at end of file diff --git a/URF.Core.Mongo.Tests/Models/Book.cs b/URF.Core.Mongo.Tests/Models/Book.cs new file mode 100644 index 0000000..9c86691 --- /dev/null +++ b/URF.Core.Mongo.Tests/Models/Book.cs @@ -0,0 +1,21 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace URF.Core.Mongo.Tests.Models +{ + public class Book + { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } + + [BsonElement("Name")] + public string BookName { get; set; } + + public decimal Price { get; set; } + + public string Category { get; set; } + + public string Author { get; set; } + } +} diff --git a/URF.Core.Mongo.Tests/Models/MyBook.cs b/URF.Core.Mongo.Tests/Models/MyBook.cs new file mode 100644 index 0000000..31f47d3 --- /dev/null +++ b/URF.Core.Mongo.Tests/Models/MyBook.cs @@ -0,0 +1,11 @@ +namespace URF.Core.Mongo.Tests.Models +{ + internal class MyBook + { + public string BookId { get; set; } + public string Name { get; set; } + public decimal UnitPrice { get; set; } + public string Category { get; set; } + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/URF.Core.Mongo.Tests/Models/MyBookComparer.cs b/URF.Core.Mongo.Tests/Models/MyBookComparer.cs new file mode 100644 index 0000000..dc1b23e --- /dev/null +++ b/URF.Core.Mongo.Tests/Models/MyBookComparer.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace URF.Core.Mongo.Tests.Models +{ + internal class MyBookComparer : IEqualityComparer + { + public bool Equals(MyBook x, MyBook y) + => x!.BookId == y!.BookId && string.Compare(x.Name, y.Name, StringComparison.InvariantCulture) + == 0 && x.UnitPrice == y.UnitPrice && x.Category == y.Category && x.Author == y.Author; + + public int GetHashCode(MyBook x) + => x.BookId.GetHashCode() + ^ x.Name.GetHashCode() + ^ x.UnitPrice.GetHashCode() + ^ x.Category.GetHashCode() + ^ x.Author.GetHashCode(); + } +} \ No newline at end of file diff --git a/URF.Core.Mongo.Tests/URF.Core.Mongo.Tests.csproj b/URF.Core.Mongo.Tests/URF.Core.Mongo.Tests.csproj new file mode 100644 index 0000000..55a3d2a --- /dev/null +++ b/URF.Core.Mongo.Tests/URF.Core.Mongo.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + latest + false + 1.0.0 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/URF.Core.Mongo/DocumentRepository.cs b/URF.Core.Mongo/DocumentRepository.cs new file mode 100644 index 0000000..cefa1b9 --- /dev/null +++ b/URF.Core.Mongo/DocumentRepository.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; +using URF.Core.Abstractions; + +namespace URF.Core.Mongo +{ + public class DocumentRepository : IDocumentRepository where TEntity : class + { + protected IMongoCollection Collection { get; } + + public DocumentRepository(IMongoCollection collection) + => Collection = collection; + + public virtual async Task> FindManyAsync(CancellationToken cancellationToken = default) + => await Collection.Find(e => true).ToListAsync(cancellationToken); + + public virtual async Task> FindManyAsync(Expression> filter, CancellationToken cancellationToken = default) + => await Collection.Find(filter).ToListAsync(cancellationToken); + + public virtual async Task FindOneAsync(Expression> filter, CancellationToken cancellationToken = default) + => await Collection.Find(filter).SingleOrDefaultAsync(cancellationToken); + + public virtual async Task FindOneAndReplaceAsync(Expression> filter, TEntity item, CancellationToken cancellationToken = default) + { + await Collection.FindOneAndReplaceAsync(filter, item, null, cancellationToken); + return await FindOneAsync(filter, cancellationToken); + } + + public virtual async Task> InsertManyAsync(IEnumerable items, CancellationToken cancellationToken = default) + { + var enumerable = items.ToList(); + await Collection.InsertManyAsync(enumerable, null, cancellationToken); + return enumerable.ToList(); + } + + public virtual async Task InsertOneAsync(TEntity item, CancellationToken cancellationToken = default) + { + await Collection.InsertOneAsync(item, null, cancellationToken); + return item; + } + + public virtual async Task DeleteManyAsync(Expression> filter, CancellationToken cancellationToken = default) + { + var result = await Collection.DeleteManyAsync(filter, cancellationToken); + return (int)result.DeletedCount; + } + + public virtual async Task DeleteOneAsync(Expression> filter, CancellationToken cancellationToken = default) + { + var result = await Collection.DeleteOneAsync(filter, cancellationToken); + return (int)result.DeletedCount; + } + + public virtual IQueryable Queryable() + => Collection.AsQueryable(); + } +} diff --git a/URF.Core.Mongo/DocumentUnitOfWork.cs b/URF.Core.Mongo/DocumentUnitOfWork.cs new file mode 100644 index 0000000..bcd81d3 --- /dev/null +++ b/URF.Core.Mongo/DocumentUnitOfWork.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using MongoDB.Driver; +using URF.Core.Abstractions; + +namespace URF.Core.Mongo; + +public class DocumentUnitOfWork : IDocumentUnitOfWork, IDisposable +{ + private bool _disposed; + + protected IClientSessionHandle Session; + protected readonly IMongoDatabase Database; + + public DocumentUnitOfWork(IMongoDatabase database) + { + Database = database; + } + + public virtual async Task StartTransactionAsync() + { + Session = await Database.Client.StartSessionAsync(); + Session.StartTransaction(); + } + + public virtual async Task CommitAsync() + { + if (Session is not null && Session.IsInTransaction) + await Session.CommitTransactionAsync(); + } + + public virtual async Task AbortAsync() + { + if (Session is not null && Session.IsInTransaction) + await Session.AbortTransactionAsync(); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + Session?.Dispose(); + _disposed = true; + } + + ~DocumentUnitOfWork() => Dispose(disposing: false); +} \ No newline at end of file diff --git a/URF.Core.Mongo/IQueryableExtensions.cs b/URF.Core.Mongo/IQueryableExtensions.cs new file mode 100644 index 0000000..75ce12c --- /dev/null +++ b/URF.Core.Mongo/IQueryableExtensions.cs @@ -0,0 +1,40 @@ +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace URF.Core.Mongo +{ + // ReSharper disable once InconsistentNaming + public static class IQueryableExtensions + { + public static Task AnyAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.AnyAsync((IMongoQueryable)source, cancellationToken); + public static Task CountAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.CountAsync((IMongoQueryable)source, cancellationToken); + public static Task LongCountAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.LongCountAsync((IMongoQueryable)source, cancellationToken); + public static Task FirstAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.FirstAsync((IMongoQueryable)source, cancellationToken); + public static Task FirstOrDefaultAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.FirstOrDefaultAsync((IMongoQueryable)source, cancellationToken); + public static Task MaxAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.MaxAsync((IMongoQueryable)source, cancellationToken); + public static Task MaxAsync(this IQueryable source, Expression> selector, CancellationToken cancellationToken = default) + => MongoQueryable.MaxAsync((IMongoQueryable)source, selector, cancellationToken); + public static Task MinAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.MinAsync((IMongoQueryable)source, cancellationToken); + public static Task MinAsync(this IQueryable source, Expression> selector, CancellationToken cancellationToken = default) + => MongoQueryable.MinAsync((IMongoQueryable)source, selector, cancellationToken); + public static Task SingleAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.SingleAsync((IMongoQueryable)source, cancellationToken); + public static Task SingleOrDefaultAsync(this IQueryable source, CancellationToken cancellationToken = default) + => MongoQueryable.SingleOrDefaultAsync((IMongoQueryable)source, cancellationToken); + public static Task> ToListAsync(this IQueryable source, CancellationToken cancellationToken = default) + => IAsyncCursorSourceExtensions.ToListAsync((IMongoQueryable)source, cancellationToken); + } +} diff --git a/URF.Core.Mongo/URF.Core.Mongo.csproj b/URF.Core.Mongo/URF.Core.Mongo.csproj new file mode 100644 index 0000000..3e0ac75 --- /dev/null +++ b/URF.Core.Mongo/URF.Core.Mongo.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + latest + 8.2.28 + Long Le, Tony Sneed + MIT + https://github.com/urfnet/URF.Core + icon.png + URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): MongoDb + This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. + repository unitofwork patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.2.28 + true + + + + + + + + + + + + + + + diff --git a/URF.Core.Mongo/icon.png b/URF.Core.Mongo/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.Mongo/icon.png differ diff --git a/URF.Core.Services/URF.Core.Services.csproj b/URF.Core.Services/URF.Core.Services.csproj index 8ab1066..09ddcd9 100644 --- a/URF.Core.Services/URF.Core.Services.csproj +++ b/URF.Core.Services/URF.Core.Services.csproj @@ -1,19 +1,24 @@  - netstandard2.0 + net8.0 latest - 1.1.0 + 8.0.0 Long Le, Tony Sneed - https://github.com/urfnet/URF.Core/blob/master/LICENSE + MIT https://github.com/urfnet/URF.Core - https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + icon.png URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Services This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. repository unitofwork service patterns + https://github.com/urfnet/URF.Core/releases/tag/v8.0.0 true + + + + diff --git a/URF.Core.Services/icon.png b/URF.Core.Services/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/URF.Core.Services/icon.png differ diff --git a/URF.Core.sln b/URF.Core.sln index 2a598ef..cd10015 100644 --- a/URF.Core.sln +++ b/URF.Core.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33403.182 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{81ABC12F-B290-4D02-90EF-0C879A5BD53F}" ProjectSection(SolutionItems) = preProject @@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "URF.Core.Abstractions.Servi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "URF.Core.Services", "URF.Core.Services\URF.Core.Services.csproj", "{ED37FEA5-EBFF-4835-AF4A-9A00E1E1D442}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "URF.Core.Mongo", "URF.Core.Mongo\URF.Core.Mongo.csproj", "{71E5D47A-251C-45D5-9167-520515DF9F82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "URF.Core.Mongo.Tests", "URF.Core.Mongo.Tests\URF.Core.Mongo.Tests.csproj", "{33A75EF7-8A7C-4691-BC88-3BA86F4A6195}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,6 +60,14 @@ Global {ED37FEA5-EBFF-4835-AF4A-9A00E1E1D442}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED37FEA5-EBFF-4835-AF4A-9A00E1E1D442}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED37FEA5-EBFF-4835-AF4A-9A00E1E1D442}.Release|Any CPU.Build.0 = Release|Any CPU + {71E5D47A-251C-45D5-9167-520515DF9F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71E5D47A-251C-45D5-9167-520515DF9F82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71E5D47A-251C-45D5-9167-520515DF9F82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71E5D47A-251C-45D5-9167-520515DF9F82}.Release|Any CPU.Build.0 = Release|Any CPU + {33A75EF7-8A7C-4691-BC88-3BA86F4A6195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33A75EF7-8A7C-4691-BC88-3BA86F4A6195}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33A75EF7-8A7C-4691-BC88-3BA86F4A6195}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33A75EF7-8A7C-4691-BC88-3BA86F4A6195}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/nuget/URF.Core.2.2.0/URF.Core.Abstractions.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.Abstractions.2.2.0.nupkg new file mode 100644 index 0000000..6a8446a Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.Abstractions.2.2.0.nupkg differ diff --git a/nuget/URF.Core.2.2.0/URF.Core.Abstractions.Services.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.Abstractions.Services.2.2.0.nupkg new file mode 100644 index 0000000..c0bf3aa Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.Abstractions.Services.2.2.0.nupkg differ diff --git a/nuget/URF.Core.2.2.0/URF.Core.Abstractions.Trackable.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.Abstractions.Trackable.2.2.0.nupkg new file mode 100644 index 0000000..0e1931d Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.Abstractions.Trackable.2.2.0.nupkg differ diff --git a/nuget/URF.Core.2.2.0/URF.Core.All.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.All.2.2.0.nupkg new file mode 100644 index 0000000..a5c9519 Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.All.2.2.0.nupkg differ diff --git a/nuget/URF.Core.2.2.0/URF.Core.All.nuspec b/nuget/URF.Core.2.2.0/URF.Core.All.nuspec new file mode 100644 index 0000000..8eca70c --- /dev/null +++ b/nuget/URF.Core.2.2.0/URF.Core.All.nuspec @@ -0,0 +1,22 @@ + + + + URF.Core.All + 2.2.0 + URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Metapackage + Long Le, Tony Sneed + Long Le, Tony Sneed + false + https://github.com/urfnet/URF.Core/blob/master/LICENSE + https://github.com/urfnet/URF.Core + https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. + repository unitofwork service patterns urf + + + + + + + + \ No newline at end of file diff --git a/nuget/URF.Core.2.2.0/URF.Core.EF.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.EF.2.2.0.nupkg new file mode 100644 index 0000000..c9b663a Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.EF.2.2.0.nupkg differ diff --git a/nuget/URF.Core.2.2.0/URF.Core.EF.Trackable.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.EF.Trackable.2.2.0.nupkg new file mode 100644 index 0000000..64c51de Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.EF.Trackable.2.2.0.nupkg differ diff --git a/nuget/URF.Core.2.2.0/URF.Core.Services.2.2.0.nupkg b/nuget/URF.Core.2.2.0/URF.Core.Services.2.2.0.nupkg new file mode 100644 index 0000000..00674a9 Binary files /dev/null and b/nuget/URF.Core.2.2.0/URF.Core.Services.2.2.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.3.0.0-preview7.nupkg new file mode 100644 index 0000000..337839f Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.Services.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.Services.3.0.0-preview7.nupkg new file mode 100644 index 0000000..baf6578 Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.Services.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.Trackable.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.Trackable.3.0.0-preview7.nupkg new file mode 100644 index 0000000..2aed71c Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.Abstractions.Trackable.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.All.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.All.3.0.0-preview7.nupkg new file mode 100644 index 0000000..9e74e08 Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.All.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.All.nuspec b/nuget/URF.Core.3.0.0-preview7/URF.Core.All.nuspec new file mode 100644 index 0000000..1f67624 --- /dev/null +++ b/nuget/URF.Core.3.0.0-preview7/URF.Core.All.nuspec @@ -0,0 +1,24 @@ + + + + URF.Core.All + 3.0.0-preview7 + URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Metapackage + Long Le, Tony Sneed + Long Le, Tony Sneed + false + MIT + https://github.com/urfnet/URF.Core + https://user-images.githubusercontent.com/2836367/36162252-7ec8dd8a-10ab-11e8-936c-11bbb77ef574.png + This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. + repository unitofwork service patterns urf + + + + + + + + + + \ No newline at end of file diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.EF.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.EF.3.0.0-preview7.nupkg new file mode 100644 index 0000000..f27fb97 Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.EF.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.EF.Trackable.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.EF.Trackable.3.0.0-preview7.nupkg new file mode 100644 index 0000000..4fbfdb6 Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.EF.Trackable.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0-preview7/URF.Core.Services.3.0.0-preview7.nupkg b/nuget/URF.Core.3.0.0-preview7/URF.Core.Services.3.0.0-preview7.nupkg new file mode 100644 index 0000000..2544780 Binary files /dev/null and b/nuget/URF.Core.3.0.0-preview7/URF.Core.Services.3.0.0-preview7.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.Abstractions.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.Abstractions.3.0.0.nupkg new file mode 100644 index 0000000..4b3b631 Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.Abstractions.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.Abstractions.Services.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.Abstractions.Services.3.0.0.nupkg new file mode 100644 index 0000000..2cae748 Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.Abstractions.Services.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.Abstractions.Trackable.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.Abstractions.Trackable.3.0.0.nupkg new file mode 100644 index 0000000..da5e724 Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.Abstractions.Trackable.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.All.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.All.3.0.0.nupkg new file mode 100644 index 0000000..7ada7eb Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.All.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.All.nuspec b/nuget/URF.Core.3.0.0/URF.Core.All.nuspec new file mode 100644 index 0000000..b32f5ac --- /dev/null +++ b/nuget/URF.Core.3.0.0/URF.Core.All.nuspec @@ -0,0 +1,27 @@ + + + + URF.Core.All + 3.0.0 + URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Metapackage + Long Le, Tony Sneed + Long Le, Tony Sneed + false + MIT + https://github.com/urfnet/URF.Core + images\icon.png + This official URF framework minimizes the surface area of your ORM technlogy from disseminating in your application. Framework provides an elegant way to implement a reusable and extensible Unit of Work and Repository pattern. + repository unitofwork service patterns urf + + + + + + + + + + + + + \ No newline at end of file diff --git a/nuget/URF.Core.3.0.0/URF.Core.EF.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.EF.3.0.0.nupkg new file mode 100644 index 0000000..09ef7d0 Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.EF.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.EF.Trackable.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.EF.Trackable.3.0.0.nupkg new file mode 100644 index 0000000..eef219c Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.EF.Trackable.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/URF.Core.Services.3.0.0.nupkg b/nuget/URF.Core.3.0.0/URF.Core.Services.3.0.0.nupkg new file mode 100644 index 0000000..18bcc41 Binary files /dev/null and b/nuget/URF.Core.3.0.0/URF.Core.Services.3.0.0.nupkg differ diff --git a/nuget/URF.Core.3.0.0/icon.png b/nuget/URF.Core.3.0.0/icon.png new file mode 100644 index 0000000..bbdb742 Binary files /dev/null and b/nuget/URF.Core.3.0.0/icon.png differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.Abstractions.3.1.3.nupkg b/nuget/URF.Core.5.0.0/URF.Core.Abstractions.3.1.3.nupkg new file mode 100644 index 0000000..e0cb6a5 Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.Abstractions.3.1.3.nupkg differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.Abstractions.Services.3.1.3.nupkg b/nuget/URF.Core.5.0.0/URF.Core.Abstractions.Services.3.1.3.nupkg new file mode 100644 index 0000000..d3e3765 Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.Abstractions.Services.3.1.3.nupkg differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.Abstractions.Trackable.3.1.3.nupkg b/nuget/URF.Core.5.0.0/URF.Core.Abstractions.Trackable.3.1.3.nupkg new file mode 100644 index 0000000..451abee Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.Abstractions.Trackable.3.1.3.nupkg differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.EF.5.0.0.nupkg b/nuget/URF.Core.5.0.0/URF.Core.EF.5.0.0.nupkg new file mode 100644 index 0000000..6438d23 Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.EF.5.0.0.nupkg differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.EF.Trackable.5.0.0.nupkg b/nuget/URF.Core.5.0.0/URF.Core.EF.Trackable.5.0.0.nupkg new file mode 100644 index 0000000..88da483 Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.EF.Trackable.5.0.0.nupkg differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.Mongo.3.1.3.nupkg b/nuget/URF.Core.5.0.0/URF.Core.Mongo.3.1.3.nupkg new file mode 100644 index 0000000..72fec22 Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.Mongo.3.1.3.nupkg differ diff --git a/nuget/URF.Core.5.0.0/URF.Core.Services.3.1.3.nupkg b/nuget/URF.Core.5.0.0/URF.Core.Services.3.1.3.nupkg new file mode 100644 index 0000000..6cf3e28 Binary files /dev/null and b/nuget/URF.Core.5.0.0/URF.Core.Services.3.1.3.nupkg differ diff --git a/nuget/URF.Core.All.nuspec b/nuget/URF.Core.All.nuspec index 0253f25..4e5c5c5 100644 --- a/nuget/URF.Core.All.nuspec +++ b/nuget/URF.Core.All.nuspec @@ -2,7 +2,7 @@ URF.Core.All - 1.1.0 + 2.2.0 URF - Unit of Work and Repositories Framework for .NET Standard and .NET Core (Official): Metapackage Long Le, Tony Sneed Long Le, Tony Sneed diff --git a/nuget/nuget.exe b/nuget/nuget.exe new file mode 100644 index 0000000..ad35fa4 Binary files /dev/null and b/nuget/nuget.exe differ diff --git a/readme.md b/readme.md index f502618..860a3e3 100644 --- a/readme.md +++ b/readme.md @@ -6,11 +6,17 @@ ##### Docs: [URF.Core.Sample](https://goo.gl/MgC4tG) | Subscribe URF Updates: [@lelong37](http://twitter.com/lelong37) | NuGet: [goo.gl/WEn7Jm](https://goo.gl/WEn7Jm) ##### -### Sample & Live Demo w/ Source Code: [URF.Core.Sample](https://goo.gl/MgC4tG) ### +### Sample & Live Demo w/ Source Code: [URF.Core.Sample](https://goo.gl/MgC4tG) (Demo Site is down due to heavy traffic and Azure costs.) ### #### URF.Core RTM is Complete! URF.Core is feature complete and now has full parity with URF.NET (legacy .NET). URF.Core has gone through a complete rewrite with laser focus on Architecture, Design and Implementation as well as implementing top request for vNext, you can take a look at our [URF.Core.Sample](https://github.com/urfnet/URF.Core.Sample) w/ [ASP.NET Core Web API](https://github.com/aspnet/Home), [OData](https://github.com/OData/WebApi), with full CRUD samples with [Angular](https://angular.io/) and [Kendo UI](https://www.telerik.com/kendo-angular-ui/components/). +#### Supported Added for MongoDb +As of version 3.1.1, support has been added for NoSQL Document databases with an implementation for MongoDb. + +#### Samples for EF Core 3.x and MongoDb +Samples have been provided for [EF Core 3.x](https://github.com/urfnet/URF.Core.Sample.v3) and [MongoDb](https://github.com/urfnet/URF.Core.Sample.NoSql). + #### Lightweight, Nano-Footprint Staying faithful to (legacy) [URF.NET](https://github.com/urfnet/URF.NET) of having a small footprint. URF.Core [URF.Core](https://github.com/urfnet/URF.Core) (**7 total classes**) vs. [URF.NET](https://github.com/urfnet/URF.NET) (**12 total classes**). @@ -152,6 +158,11 @@ URF.Core has been completely re-written, and everything is now completely `task` + + + + +