├── RepositoryFramework.Interfaces ├── .tfignore ├── pack.ps1 ├── ICreate.cs ├── IUpdate.cs ├── IDelete.cs ├── IFind.cs ├── IGetById.cs ├── ICreateAsync.cs ├── IDeleteMany.cs ├── IDeleteAsync.cs ├── IUpdateAsync.cs ├── ICreateMany.cs ├── IFindAsync.cs ├── IGetByIdAsync.cs ├── SortOrder.cs ├── IDeleteManyAsync.cs ├── ICreateManyAsync.cs ├── IFindFilterAsync.cs ├── IFindFilter.cs ├── IFindWhere.cs ├── IFindWhereAsync.cs ├── IRepository.cs ├── Properties │ └── AssemblyInfo.cs ├── IUnitOfWorkRepositoryAsync.cs ├── IFindSql.cs ├── IFindSqlAsync.cs ├── IUnitOfWorkRepository.cs ├── IQueryableRepository.cs ├── IParameterizedRepository.cs ├── IExpandableRepository.cs ├── RepositoryFramework.Interfaces.csproj ├── IBlobRepository.cs ├── IPageableRepository.cs ├── TaskExtensions.cs ├── ISortableRepository.cs ├── BlobInfo.cs ├── IQueryableExtensions.cs └── GenericRepositoryBase.cs ├── RepositoryFramework.EntityFramework ├── .tfignore ├── pack.ps1 ├── ModelBuilderExtensions.cs ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.EntityFramework.csproj └── IEntityFrameworkRepository.cs ├── RepositoryFramework.EntityFramework.Test ├── .tfignore ├── Models │ ├── Part.cs │ ├── Order.cs │ ├── OrderDetail.cs │ ├── Product.cs │ └── Category.cs ├── TestLoggerProvider.cs ├── Properties │ └── AssemblyInfo.cs ├── TestLogger.cs ├── SQLiteContext.cs ├── RepositoryFramework.EntityFramework.Test.csproj ├── GenericRepositoryBaseTest.cs └── EFCoreTest.cs ├── RepositoryFramework.AWS.S3.Test ├── appSettings.json ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.AWS.S3.Test.csproj └── AWSS3RepositoryTest.cs ├── RepositoryFramework.Api ├── Properties │ ├── pack.ps1 │ └── AssemblyInfo.cs ├── pack.ps1 ├── AuthenticationType.cs ├── IApiRepository.cs ├── RepositoryFramework.Api.csproj ├── ApiException.cs └── ApiConfiguration.cs ├── stylecop.json ├── RepositoryFramework.AWS.S3 ├── pack.ps1 ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.AWS.S3.csproj └── AWSS3Repository.cs ├── RepositoryFramework.Dapper ├── pack.ps1 ├── Properties │ └── AssemblyInfo.cs ├── IStoredProcedureDapperRepository.cs ├── RepositoryFramework.Dapper.csproj ├── IDapperRepository.cs └── StoredProcedureDapperRepository.cs ├── RepositoryFramework.MongoDB ├── pack.ps1 ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.MongoDB.csproj └── IMongoDBRepository.cs ├── RepositoryFramework.Azure.Blob ├── pack.ps1 ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.Azure.Blob.csproj └── AzureBlobRepository.cs ├── RepositoryFramework.Dapper.Test ├── Filters │ ├── IdFilter.cs │ ├── ProductFilter.cs │ └── CategoryFilter.cs ├── Models │ ├── Part.cs │ ├── Product.cs │ └── Category.cs ├── Properties │ └── AssemblyInfo.cs ├── ProductRepository.cs ├── RepositoryFramework.Dapper.Test.csproj ├── CategoryRepository.cs ├── CategoryRepositoryTest.cs └── StoredProcedureRepositoryTest.cs ├── RepositoryFramework.Api.Test ├── Post.cs ├── Comment.cs ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.Api.Test.csproj └── ApiRepositoryTest.cs ├── RepositoryFramework.MongoDB.Test ├── MongoDBFixture.cs ├── Properties │ └── AssemblyInfo.cs ├── TestDocument.cs ├── RepositoryFramework.MongoDB.Test.csproj └── Mongo2GoTest.cs ├── RepositoryFramework.Azure.Blob.Test ├── Properties │ └── AssemblyInfo.cs ├── RepositoryFramework.Azure.Blob.Test.csproj └── AzureBlobRepositoryTest.cs ├── pack.ps1 ├── LICENSE ├── RULES.ruleset ├── .gitattributes ├── .gitignore └── RepositoryFramework.sln /RepositoryFramework.Interfaces/.tfignore: -------------------------------------------------------------------------------- 1 | # 2 | 3 | \project.lock.json 4 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework/.tfignore: -------------------------------------------------------------------------------- 1 | # 2 | 3 | \project.lock.json 4 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/.tfignore: -------------------------------------------------------------------------------- 1 | # 2 | 3 | \project.lock.json 4 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3.Test/appSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWS": { 3 | "Profile": "local-test-profile", 4 | "Region": "eu-central-1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/Properties/pack.ps1: -------------------------------------------------------------------------------- 1 | Remove-Item "project.lock.json" 2 | & "dotnet" restore --no-cache 3 | & "dotnet" pack -c Release -o \Packages 4 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "documentationRules": { 4 | "documentInterfaces": true, 5 | "documentInternalElements": false 6 | } 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /RepositoryFramework.Api/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del E:\Packages\RepositoryFramework.Api.* 5 | dotnet pack -c Release -o \Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del E:\Packages\RepositoryFramework.AWS.S3.* 5 | dotnet pack -c Release -o \Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del E:\Packages\RepositoryFramework.Dapper.* 5 | dotnet pack -c Release -o \Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del E:\Packages\RepositoryFramework.MongoDB.* 5 | dotnet pack -c Release -o \Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del E:\Packages\RepositoryFramework.Azure.Blob.* 5 | dotnet pack -c Release -o \Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del E:\Packages\RepositoryFramework.Interfaces.* 5 | dotnet pack -c Release -o \Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Filters/IdFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Test.Filters 4 | { 5 | public class IdFilter 6 | { 7 | public int Id { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework/pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | Remove-Item "project.lock.json" 3 | dotnet restore --no-cache 4 | del C:\Export\Packages\RepositoryFramework.EntityFramework.* 5 | dotnet pack -c Release -o C:\Export\Packages 6 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Filters/ProductFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Test.Filters 4 | { 5 | public class ProductFilter 6 | { 7 | public string Name { get; set; } 8 | public int? CategoryId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Filters/CategoryFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Test.Filters 4 | { 5 | public class CategoryFilter 6 | { 7 | public string Name { get; set; } 8 | public string Description { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /RepositoryFramework.Api.Test/Post.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Api.Test 2 | { 3 | public class Post 4 | { 5 | public int UserId { get; set; } 6 | 7 | public int Id { get; set; } 8 | 9 | public string Title { get; set; } 10 | 11 | public string Body { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RepositoryFramework.Api.Test/Comment.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Api.Test 2 | { 3 | public class Comment 4 | { 5 | public int PostId { get; set; } 6 | 7 | public int Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string Email { get; set; } 12 | 13 | public string Body { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Models/Part.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Test.Models 2 | { 3 | public class Part 4 | { 5 | public int? Id { get; set; } 6 | public string Name { get; set; } 7 | public string Description { get; set; } 8 | public decimal? Price { get; set; } 9 | public virtual Product Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/Models/Part.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Test.Models 2 | { 3 | public class Part 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | public string Description { get; set; } 8 | public decimal Price { get; set; } 9 | public virtual Product Product { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/Models/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RepositoryFramework.Test.Models 5 | { 6 | public class Order 7 | { 8 | public int OrderKey { get; set; } 9 | public DateTime OrderDate { get; set; } 10 | public virtual ICollection OrderDetails { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/Models/OrderDetail.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Test.Models 2 | { 3 | public class OrderDetail 4 | { 5 | public int Id { get; set; } 6 | 7 | public int Quantity { get; set; } 8 | public decimal Price { get; set; } 9 | public virtual Product Product { get; set; } 10 | public virtual Order Order { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/Models/Product.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Test.Models 4 | { 5 | public class Product 6 | { 7 | public int Id { get; set; } 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public decimal Price { get; set; } 11 | public virtual Category Category { get; set; } 12 | public ICollection Parts { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/ICreate.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Interfaces 2 | { 3 | /// 4 | /// Creates a new entity. 5 | /// 6 | /// Entity type 7 | public interface ICreate 8 | where TEntity : class 9 | { 10 | /// 11 | /// Create a new entity 12 | /// 13 | /// Entity 14 | void Create(TEntity entity); 15 | } 16 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IUpdate.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Interfaces 2 | { 3 | /// 4 | /// Updates an etity. 5 | /// 6 | /// Entity type 7 | public interface IUpdate 8 | where TEntity : class 9 | { 10 | /// 11 | /// Update an existing entity 12 | /// 13 | /// Entity 14 | void Update(TEntity entity); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IDelete.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Interfaces 2 | { 3 | /// 4 | /// Deletes an entity. 5 | /// 6 | /// Entity type 7 | public interface IDelete 8 | where TEntity : class 9 | { 10 | /// 11 | /// Delete an existing entity 12 | /// 13 | /// Entity 14 | void Delete(TEntity entity); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Models/Product.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Test.Models 4 | { 5 | public class Product 6 | { 7 | public int? Id { get; set; } 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public decimal? Price { get; set; } 11 | public int? CategoryId { get; set; } 12 | public virtual Category Category { get; set; } 13 | public virtual ICollection Parts { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFind.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Finds a list of entites. 7 | /// 8 | /// Entity type 9 | public interface IFind 10 | where TEntity : class 11 | { 12 | /// 13 | /// Get a list of entities 14 | /// 15 | /// Query result 16 | IEnumerable Find(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Models/Category.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RepositoryFramework.Test.Models 5 | { 6 | public class Category 7 | { 8 | public int? Id { get; set; } 9 | public string Name { get; set; } 10 | public string Description { get; set; } 11 | public string NullField { get; set; } = null; 12 | public DateTime? DateTimeField { get; set; } = DateTime.Now; 13 | public virtual ICollection Products { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/Models/Category.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RepositoryFramework.Test.Models 5 | { 6 | public class Category 7 | { 8 | public int? Id { get; set; } 9 | public string Name { get; set; } 10 | public string Description { get; set; } 11 | public string NullField { get; set; } = null; 12 | public DateTime? DateTimeField { get; set; } = DateTime.Now; 13 | public virtual ICollection Products { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IGetById.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Interfaces 2 | { 3 | /// 4 | /// Gets an entity by id. 5 | /// 6 | /// Entity type 7 | public interface IGetById 8 | where TEntity : class 9 | { 10 | /// 11 | /// Gets an entity by id. 12 | /// 13 | /// Filter to find a single item 14 | /// Entity 15 | TEntity GetById(object id); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/ICreateAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Creates a new entity. 7 | /// 8 | /// Entity type 9 | public interface ICreateAsync 10 | where TEntity : class 11 | { 12 | /// 13 | /// Create a new entity 14 | /// 15 | /// Entity 16 | /// Task 17 | Task CreateAsync(TEntity entity); 18 | } 19 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IDeleteMany.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Deletes an entity. 7 | /// 8 | /// Entity type 9 | public interface IDeleteMany 10 | where TEntity : class 11 | { 12 | /// 13 | /// Delete a list of existing entities 14 | /// 15 | /// Entity list 16 | void DeleteMany(IEnumerable entities); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IDeleteAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Deletes an entity. 7 | /// 8 | /// Entity type 9 | public interface IDeleteAsync 10 | where TEntity : class 11 | { 12 | /// 13 | /// Delete an existing entity 14 | /// 15 | /// Entity 16 | /// Task 17 | Task DeleteAsync(TEntity entity); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IUpdateAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Updates an etity. 7 | /// 8 | /// Entity type 9 | public interface IUpdateAsync 10 | where TEntity : class 11 | { 12 | /// 13 | /// Update an existing entity 14 | /// 15 | /// Entity 16 | /// Task 17 | Task UpdateAsync(TEntity entity); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/ICreateMany.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Creates a list of new entities. 7 | /// 8 | /// Entity type 9 | public interface ICreateMany 10 | where TEntity : class 11 | { 12 | /// 13 | /// Create a list of new entities 14 | /// 15 | /// List of entities 16 | void CreateMany(IEnumerable entities); 17 | } 18 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// Finds a list of entites. 8 | /// 9 | /// Entity type 10 | public interface IFindAsync 11 | where TEntity : class 12 | { 13 | /// 14 | /// Get a list of entities 15 | /// 16 | /// Query result 17 | Task> FindAsync(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IGetByIdAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Gets an entity by id. 7 | /// 8 | /// Entity type 9 | public interface IGetByIdAsync 10 | where TEntity : class 11 | { 12 | /// 13 | /// Gets an entity by id. 14 | /// 15 | /// Filter to find a single item 16 | /// Entity 17 | Task GetByIdAsync(object id); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/SortOrder.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Interfaces 2 | { 3 | /// 4 | /// Specifies how rows of data are sorted. 5 | /// 6 | public enum SortOrder 7 | { 8 | /// 9 | /// The default. No sort order is specified. 10 | /// 11 | Unspecified = -1, 12 | 13 | /// 14 | /// Rows are sorted in ascending order. 15 | /// 16 | Ascending = 0, 17 | 18 | /// 19 | /// Rows are sorted in descending order. 20 | /// 21 | Descending = 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/TestLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Diagnostics; 4 | using Xunit.Abstractions; 5 | 6 | namespace RepositoryFramework.Test 7 | { 8 | public class TestLoggerProvider : ILoggerProvider 9 | { 10 | public TestLoggerProvider(ILogger logger) 11 | { 12 | Logger = logger; 13 | } 14 | 15 | public ILogger Logger { get; set; } 16 | 17 | public ILogger CreateLogger(string categoryName) 18 | { 19 | return Logger; 20 | } 21 | 22 | public void Dispose() 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IDeleteManyAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// Deletes an entity. 8 | /// 9 | /// Entity type 10 | public interface IDeleteManyAsync 11 | where TEntity : class 12 | { 13 | /// 14 | /// Delete a list of existing entities 15 | /// 16 | /// Entity list 17 | /// Task 18 | Task DeleteManyAsync(IEnumerable entities); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/ICreateManyAsync.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// Creates a list of new entities. 8 | /// 9 | /// Entity type 10 | public interface ICreateManyAsync 11 | where TEntity : class 12 | { 13 | /// 14 | /// Create a list of new entities 15 | /// 16 | /// List of entities 17 | /// Task 18 | Task CreateManyAsync(IEnumerable entities); 19 | } 20 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindFilterAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Filters a sequence of entities 10 | /// 11 | /// Entity type 12 | public interface IFindFilterAsync 13 | where TEntity : class 14 | { 15 | /// 16 | /// Filters a collection of entities 17 | /// 18 | /// Filter 19 | /// Filtered collection of entities 20 | Task> FindAsync(string filter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Filters a sequence of entities from a filter definition 10 | /// 11 | /// Entity type 12 | public interface IFindFilter 13 | where TEntity : class 14 | { 15 | /// 16 | /// Filters a collection of entities from a filter definition 17 | /// 18 | /// Filter definition 19 | /// Filtered collection of entities 20 | IEnumerable Find(string filter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindWhere.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace RepositoryFramework.Interfaces 8 | { 9 | /// 10 | /// Filters a sequence of entities using a predicate 11 | /// 12 | /// Entity type 13 | public interface IFindWhere 14 | where TEntity : class 15 | { 16 | /// 17 | /// Filters a collection of entities using a predicate 18 | /// 19 | /// Where predicate 20 | /// Filtered collection of entities 21 | IEnumerable Find(Expression> where); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindWhereAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace RepositoryFramework.Interfaces 8 | { 9 | /// 10 | /// Filters a sequence of entities using a predicate 11 | /// 12 | /// Entity type 13 | public interface IFindWhereAsync 14 | where TEntity : class 15 | { 16 | /// 17 | /// Filters a collection of entities using a predicate 18 | /// 19 | /// Where predicate 20 | /// Filtered collection of entities 21 | Task> FindAsync(Expression> where); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/AuthenticationType.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Api 2 | { 3 | /// 4 | /// Authentication type 5 | /// 6 | public enum AuthenticationType 7 | { 8 | /// 9 | /// Anumynous access, no authentication 10 | /// 11 | Anonymous, 12 | 13 | /// 14 | /// API key authentication 15 | /// 16 | ApiKey, 17 | 18 | /// 19 | /// Basic authentication. HTTP Authorization header containing [user name]:[password] 20 | /// 21 | BasicAuthentication, 22 | 23 | /// 24 | /// OAuth2 / OpenID Connect token authentication 25 | /// 26 | OAuth2, 27 | 28 | /// 29 | /// Jwt authentication 30 | /// 31 | Jwt 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework/ModelBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 4 | 5 | namespace RepositoryFramework.EntityFramework 6 | { 7 | /// 8 | /// ModelBuilder extension methods 9 | /// 10 | public static class ModelBuilderExtensions 11 | { 12 | /// 13 | /// Avoid pluralizing table names convention 14 | /// 15 | /// Model builder 16 | public static void RemovePluralizingTableNameConvention(this ModelBuilder modelBuilder) 17 | { 18 | foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes()) 19 | { 20 | entity.Relational().TableName = entity.ClrType.Name; 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Abstracts data access for en entity 10 | /// 11 | /// Type of entity 12 | public interface IRepository : 13 | ICreate, 14 | ICreateAsync, 15 | ICreateMany, 16 | ICreateManyAsync, 17 | IDelete, 18 | IDeleteAsync, 19 | IDeleteMany, 20 | IDeleteManyAsync, 21 | IGetById, 22 | IGetByIdAsync, 23 | IFind, 24 | IFindAsync, 25 | IUpdate, 26 | IUpdateAsync 27 | where TEntity : class 28 | { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RepositoryFramework.Api")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("b0a00cff-e9a2-44c5-bf9d-003466f71975")] 19 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RepositoryFramework.Dapper")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("fafda019-9c49-435b-9bb1-51c4c0c756a6")] 19 | -------------------------------------------------------------------------------- /RepositoryFramework.Api.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RepositoryFramework.Api.Test")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("1f3af3a4-2c4a-4641-a32b-f18f90db8579")] 19 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RepositoryFramework.Interfaces")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("27c7c76d-677d-4241-be70-d04e3442a75d")] 19 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RepositoryFramework.Test")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("a900fe5b-584e-4602-87ec-79529e6ae180")] 19 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("RepositoryFramework.EntityFramework")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("66fb133d-e282-481d-bc7b-08c942f8a5cb")] 19 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB.Test/MongoDBFixture.cs: -------------------------------------------------------------------------------- 1 | using Mongo2Go; 2 | using MongoDB.Driver; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace RepositoryFramework.MongoDB.Test 9 | { 10 | public class MongoDBFixture : IDisposable 11 | { 12 | public MongoDBFixture() 13 | { 14 | runner = MongoDbRunner.Start(@".\data\"); 15 | Client = new MongoClient(runner.ConnectionString); 16 | Database = Client.GetDatabase(databaseName); 17 | } 18 | 19 | private MongoDbRunner runner; 20 | 21 | private readonly string databaseName = Guid.NewGuid().ToString(); 22 | 23 | public IMongoDatabase Database { get; private set; } 24 | 25 | public MongoClient Client { get; private set; } 26 | 27 | 28 | public void Dispose() 29 | { 30 | Client.DropDatabase(databaseName); 31 | runner.Dispose(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/TestLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Xunit.Abstractions; 7 | 8 | namespace RepositoryFramework.Test 9 | { 10 | public class TestLogger : ILogger 11 | { 12 | protected readonly ITestOutputHelper output; 13 | 14 | public TestLogger(ITestOutputHelper output) 15 | { 16 | this.output = output; 17 | } 18 | 19 | public bool IsEnabled(LogLevel logLevel) 20 | { 21 | return true; 22 | } 23 | 24 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 25 | { 26 | output.WriteLine(formatter(state, exception)); 27 | } 28 | 29 | public IDisposable BeginScope(TState state) 30 | { 31 | return null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.AWS.S3")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("95d9510f-17b5-4ad7-af49-3970b40f24e1")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.MongoDB")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("5e82ff79-d884-408e-b2ed-b8a8f301fe55")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.Azure.Blob")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("7e103fdf-ba13-424d-bb18-0478e0c1c656")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.AWS.S3.Test")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("cd6e4686-d46f-47f9-812d-7c6457431391")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.Dapper.Test")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("2bb73b46-25c3-42a4-8f73-ed4c1c97fd02")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.MongoDB.Test")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("91a064be-b208-47af-9b35-cc1a4e873c40")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("RepositoryFramework.Azure.Blob.Test")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("96fb9d0e-6d8c-4e8e-8b50-10a6a41c267c")] 20 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IUnitOfWorkRepositoryAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// Asynchronously persists changes made to a repository and detaches all entities from the repository 8 | /// 9 | /// Entity type 10 | public interface IUnitOfWorkRepositoryAsync 11 | where TEntity : class 12 | { 13 | /// 14 | /// Persists all changes to the data storage 15 | /// Current instance 16 | /// 17 | /// Current instance 18 | Task> SaveChangesAsync(); 19 | 20 | /// 21 | /// Detaches all entites from the repository 22 | /// Current instance 23 | /// 24 | /// Current instance 25 | Task> DetachAllAsync(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper/IStoredProcedureDapperRepository.cs: -------------------------------------------------------------------------------- 1 | using RepositoryFramework.Interfaces; 2 | 3 | namespace RepositoryFramework.Dapper 4 | { 5 | /// 6 | /// A Dapper repository using stored procedures 7 | /// 8 | /// Entity type 9 | public interface IStoredProcedureDapperRepository : 10 | IRepository, 11 | IParameterizedRepository 12 | where TEntity : class 13 | { 14 | /// 15 | /// Adds a parameter to queries 16 | /// 17 | /// Parameter name 18 | /// Parameter value 19 | /// Current instance 20 | new IStoredProcedureDapperRepository SetParameter(string name, object value); 21 | 22 | /// 23 | /// Clears parameters 24 | /// 25 | /// Current instance 26 | new IStoredProcedureDapperRepository ClearParameters(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | using RepositoryFramework.Test.Filters; 2 | using RepositoryFramework.Test.Models; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using RepositoryFramework.Interfaces; 6 | using Dapper; 7 | using System.Linq; 8 | using System; 9 | 10 | namespace RepositoryFramework.Dapper.Test 11 | { 12 | public class ProductRepository : DapperRepository 13 | { 14 | public ProductRepository(IDbConnection connection, 15 | string lastRowIdCommand = "SELECT @@IDENTITY") 16 | : base(connection, lastRowIdCommand) 17 | { 18 | } 19 | 20 | public IEnumerable FindByCategoryId(object categoryId) 21 | { 22 | if (Connection.State != ConnectionState.Open) 23 | { 24 | Connection.Open(); 25 | } 26 | 27 | var findQuery = $@" 28 | SELECT * FROM Product 29 | WHERE CategoryId = @CategoryId"; 30 | 31 | return Connection.Query( 32 | findQuery, 33 | new { CategoryId = categoryId }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindSql.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Filters a sequence of entities using a predicate 10 | /// 11 | /// Entity type 12 | public interface IFindSql 13 | where TEntity : class 14 | { 15 | /// 16 | /// Filters a collection of entities using a predicate 17 | /// 18 | /// SQL containing named parameter placeholders. For example: SELECT * FROM Customer WHERE Id = @Id 19 | /// Named parameters 20 | /// Parameter Regex pattern, Defualts to @(\w+) 21 | /// Filtered collection of entities 22 | IEnumerable Find( 23 | string sql, 24 | IDictionary parameters = null, 25 | string parameterPattern = @"@(\w+)"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/IApiRepository.cs: -------------------------------------------------------------------------------- 1 | using RepositoryFramework.Interfaces; 2 | 3 | namespace RepositoryFramework.Api 4 | { 5 | /// 6 | /// Repository abstraction of a ReSTful API 7 | /// 8 | /// Entity type 9 | public interface IApiRepository : 10 | IRepository, 11 | IParameterizedRepository 12 | where TEntity : class 13 | { 14 | /// 15 | /// Gets configuration object 16 | /// 17 | ApiConfiguration Configuration { get; } 18 | 19 | /// 20 | /// Adds a parameter to queries 21 | /// 22 | /// Parameter name 23 | /// Parameter value 24 | /// Current instance 25 | new IApiRepository SetParameter(string name, object value); 26 | 27 | /// 28 | /// Clears parameters 29 | /// 30 | /// Current instance 31 | new IApiRepository ClearParameters(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IFindSqlAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Filters a sequence of entities using a predicate 10 | /// 11 | /// Entity type 12 | public interface IFindSqlAsync 13 | where TEntity : class 14 | { 15 | /// 16 | /// Filters a collection of entities using a predicate 17 | /// 18 | /// SQL containing named parameter placeholders. For example: SELECT * FROM Customer WHERE Id = @Id 19 | /// Named parameters 20 | /// Parameter Regex pattern, Defualts to @(\w+) 21 | /// Filtered collection of entities 22 | Task> FindAsync( 23 | string sql, 24 | IDictionary parameters = null, 25 | string parameterPattern = @"@(\w+)"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IUnitOfWorkRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RepositoryFramework.Interfaces 4 | { 5 | /// 6 | /// Persists changes made to a repository and detaches all entities from the repository 7 | /// 8 | /// Entity type 9 | public interface IUnitOfWorkRepository 10 | where TEntity : class 11 | { 12 | /// 13 | /// Gets a value indicating whether changes are committed automatically 14 | /// 15 | /// If false, SaveChanges() msut be called before changes are committed 16 | bool AutoCommit { get; } 17 | 18 | /// 19 | /// Persist all changes to the data storage 20 | /// Current instance 21 | /// 22 | /// Current instance 23 | IRepository SaveChanges(); 24 | 25 | /// 26 | /// Detach all entites from the repository 27 | /// Current instance 28 | /// 29 | /// Current instance 30 | IRepository DetachAll(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pack.ps1: -------------------------------------------------------------------------------- 1 | Set-Location $PSScriptRoot 2 | 3 | cd RepositoryFramework.Api 4 | dotnet restore --no-cache 5 | del E:\Packages\RepositoryFramework.Api.* 6 | dotnet pack -c Release -o \Packages 7 | 8 | cd ..\RepositoryFramework.AWS.S3 9 | dotnet restore --no-cache 10 | del E:\Packages\RepositoryFramework.AWS.S3.* 11 | dotnet pack -c Release -o \Packages 12 | 13 | cd ..\RepositoryFramework.Azure.Blob 14 | dotnet restore --no-cache 15 | del E:\Packages\RepositoryFramework.Azure.Blob.* 16 | dotnet pack -c Release -o \Packages 17 | 18 | cd ..\RepositoryFramework.Dapper 19 | dotnet restore --no-cache 20 | del E:\Packages\RepositoryFramework.Dapper.* 21 | dotnet pack -c Release -o \Packages 22 | 23 | cd ..\RepositoryFramework.EntityFramework 24 | dotnet restore --no-cache 25 | del E:\Packages\RepositoryFramework.EntityFramework.* 26 | dotnet pack -c Release -o \Packages 27 | 28 | cd ..\RepositoryFramework.Interfaces 29 | dotnet restore --no-cache 30 | del E:\Packages\RepositoryFramework.Interfaces.* 31 | dotnet pack -c Release -o \Packages 32 | 33 | cd ..\RepositoryFramework.MongoDB 34 | dotnet restore --no-cache 35 | del E:\Packages\RepositoryFramework.MongoDB.* 36 | dotnet pack -c Release -o \Packages 37 | 38 | cd .. -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IQueryableRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Threading.Tasks; 6 | 7 | namespace RepositoryFramework.Interfaces 8 | { 9 | /// 10 | /// Repository that supports IQueryable 11 | /// 12 | /// Entity type 13 | public interface IQueryableRepository : 14 | IRepository 15 | where TEntity : class 16 | { 17 | /// 18 | /// Gets a queryable collection of entities 19 | /// 20 | /// Queryable collection of entities 21 | IQueryable AsQueryable(); 22 | 23 | /// 24 | /// Filters a collection of entities using a predicate using deferred execution 25 | /// 26 | /// Where predicate 27 | /// Filtered collection of entities for deferred execution 28 | IQueryableRepository Where(Expression> where); 29 | 30 | /// 31 | /// Clear filters 32 | /// 33 | /// Current instance 34 | IQueryableRepository ClearWhere(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB.Test/TestDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.MongoDB.Test 7 | { 8 | public class TestDocument 9 | { 10 | public string TestDocumentId { get; set; } 11 | 12 | public string StringTest { get; set; } 13 | 14 | public int IntTest { get; set; } 15 | 16 | public DateTime? DateTest { get; set; } 17 | 18 | public List ListTest { get; set; } 19 | 20 | public static TestDocument DummyData1() 21 | { 22 | return new TestDocument 23 | { 24 | StringTest = "A", 25 | IntTest = 1, 26 | DateTest = new DateTime(1984, 09, 30, 6, 6, 6, 171, DateTimeKind.Utc).ToLocalTime(), 27 | ListTest = new List { "I", "am", "a", "list", "of", "strings" } 28 | }; 29 | } 30 | 31 | public static TestDocument DummyData2() 32 | { 33 | return new TestDocument 34 | { 35 | StringTest = "B", 36 | IntTest = 2, 37 | }; 38 | } 39 | 40 | public static TestDocument DummyData3() 41 | { 42 | return new TestDocument 43 | { 44 | StringTest = "C", 45 | IntTest = 3, 46 | }; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IParameterizedRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace RepositoryFramework.Interfaces 6 | { 7 | /// 8 | /// Parameterizes queries 9 | /// 10 | /// Type of entity 11 | public interface IParameterizedRepository : IRepository 12 | where TEntity : class 13 | { 14 | /// 15 | /// Gets a list of reference properties to include 16 | /// 17 | IDictionary Parameters { get; } 18 | 19 | /// 20 | /// Adds a parameter to queries 21 | /// 22 | /// Parameter name 23 | /// Parameter value 24 | /// Current instance 25 | IParameterizedRepository SetParameter(string name, object value); 26 | 27 | /// 28 | /// Gets parameter value 29 | /// 30 | /// Parameter name 31 | /// Parameter value 32 | object GetParameter(string name); 33 | 34 | /// 35 | /// Clears parameters 36 | /// 37 | /// Current instance 38 | IParameterizedRepository ClearParameters(); 39 | } 40 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IExpandableRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace RepositoryFramework.Interfaces 6 | { 7 | /// 8 | /// Loads data for referenced objects 9 | /// 10 | /// Type of entity 11 | public interface IExpandableRepository : IRepository 12 | where TEntity : class 13 | { 14 | /// 15 | /// Gets a list of reference properties to include 16 | /// 17 | List Includes { get; } 18 | 19 | /// 20 | /// Include referenced properties 21 | /// 22 | /// Comma-separated list of property paths 23 | /// Current instance 24 | IExpandableRepository Include(string propertyPaths); 25 | 26 | /// 27 | /// Include referenced property 28 | /// 29 | /// Property expression 30 | /// Current instance 31 | IExpandableRepository Include(Expression> property); 32 | 33 | /// 34 | /// Clear includes 35 | /// 36 | /// Current instance 37 | IExpandableRepository ClearIncludes(); 38 | } 39 | } -------------------------------------------------------------------------------- /RULES.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/RepositoryFramework.Interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework 5 | RepositoryFramework.Interfaces 6 | 2.0.5 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.Interfaces 11 | RepositoryFramework.Interfaces 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | ../RULES.ruleset 18 | 2.0.8 19 | 2.0.88.0 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | All 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IBlobRepository.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// Binary large object repository 8 | /// 9 | public interface IBlobRepository : 10 | IDelete, 11 | IDeleteAsync, 12 | IDeleteMany, 13 | IDeleteManyAsync, 14 | IGetById, 15 | IGetByIdAsync, 16 | IFind, 17 | IFindAsync, 18 | IFindFilter, 19 | IFindFilterAsync 20 | { 21 | /// 22 | /// Upload blob 23 | /// 24 | /// Blob info entity 25 | /// Upload stream 26 | void Upload(BlobInfo entity, Stream stream); 27 | 28 | /// 29 | /// Upload blob 30 | /// 31 | /// Blob info entity 32 | /// Upload stream 33 | /// Task 34 | Task UploadAsync(BlobInfo entity, Stream stream); 35 | 36 | /// 37 | /// Download blob payload 38 | /// 39 | /// Blob info entity 40 | /// Download stream 41 | void Download(BlobInfo entity, Stream stream); 42 | 43 | /// 44 | /// Download blob payload 45 | /// 46 | /// Blob info entity 47 | /// Download stream 48 | /// Task 49 | Task DownloadAsync(BlobInfo entity, Stream stream); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/RepositoryFramework.Dapper.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;netcoreapp1.1;net452 5 | RepositoryFramework.Dapper.Test 6 | RepositoryFramework.Dapper.Test 7 | true 8 | false 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /RepositoryFramework.Api.Test/RepositoryFramework.Api.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;netcoreapp1.1;net452 5 | RepositoryFramework.Api.Test 6 | RepositoryFramework.Api.Test 7 | true 8 | false 9 | false 10 | false 11 | 12 | Library 13 | 14 | 15 | 16 | 17 | 1701;1702;1705 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/SQLiteContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Logging; 3 | using RepositoryFramework.EntityFramework; 4 | using RepositoryFramework.Test.Models; 5 | using System; 6 | 7 | namespace RepositoryFramework.Test 8 | { 9 | public class SQLiteContext : DbContext 10 | { 11 | public DbSet Products { get; set; } 12 | public DbSet Categories { get; set; } 13 | public DbSet Orders { get; set; } 14 | public DbSet OrderDetails { get; set; } 15 | public SQLiteContext(ILogger logger= null) 16 | { 17 | Logger = logger; 18 | Database.EnsureDeleted(); 19 | Database.EnsureCreated(); 20 | } 21 | 22 | public ILogger Logger { get; set; } 23 | 24 | public override void Dispose() 25 | { 26 | try 27 | { 28 | Database.EnsureDeleted(); 29 | base.Dispose(); 30 | } 31 | catch { } 32 | } 33 | 34 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 35 | { 36 | optionsBuilder.UseSqlite($"Filename=./{System.Guid.NewGuid().ToString()}.db"); 37 | if (Logger != null) 38 | { 39 | var lf = new LoggerFactory(); 40 | lf.AddProvider(new TestLoggerProvider(Logger)); 41 | optionsBuilder.UseLoggerFactory(lf); 42 | } 43 | } 44 | 45 | protected override void OnModelCreating(ModelBuilder modelBuilder) 46 | { 47 | base.OnModelCreating(modelBuilder); 48 | 49 | modelBuilder.RemovePluralizingTableNameConvention(); 50 | 51 | modelBuilder.Entity(entity => 52 | { 53 | entity.HasKey(e => e.OrderKey); 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob.Test/RepositoryFramework.Azure.Blob.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;netcoreapp1.1;net452 5 | RepositoryFramework.Azure.Blob.Test 6 | RepositoryFramework.Azure.Blob.Test 7 | true 8 | false 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IPageableRepository.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Interfaces 2 | { 3 | /// 4 | /// Pages a result sets 5 | /// 6 | /// Entity type 7 | public interface IPageableRepository : IRepository 8 | where TEntity : class 9 | { 10 | /// 11 | /// Gets number of items per page (when paging is used) 12 | /// 13 | int PageSize { get; } 14 | 15 | /// 16 | /// Gets page number (one based index) 17 | /// 18 | int PageNumber { get; } 19 | 20 | /// 21 | /// Gets the total number of items available in this set. For example, if a user has 100 blog posts, the response may only contain 10 items, but the totalItems would be 100. 22 | /// 23 | long TotalItems { get; } 24 | 25 | /// 26 | /// Gets the index of the first item. For consistency, startIndex should be 1-based. For example, the first item in the first set of items should have a startIndex of 1. If the user requests the next set of data, the startIndex may be 10. 27 | /// 28 | int StartIndex { get; } 29 | 30 | /// 31 | /// Gets the total number of pages in the result set. 32 | /// 33 | int TotalPages { get; } 34 | 35 | /// 36 | /// Use paging 37 | /// 38 | /// Page to get (one based index). 39 | /// Number of items per page. 40 | /// Current instance 41 | IPageableRepository Page(int pageNumber, int pageSize); 42 | 43 | /// 44 | /// Clear paging 45 | /// 46 | /// Current instance 47 | IPageableRepository ClearPaging(); 48 | } 49 | } -------------------------------------------------------------------------------- /RepositoryFramework.Dapper/RepositoryFramework.Dapper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework for Dapper 5 | RepositoryFramework.Dapper 6 | 2.0.2 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.Dapper 11 | RepositoryFramework.Dapper 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | ../RULES.ruleset 18 | 2.0.4 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | All 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB/RepositoryFramework.MongoDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework for Mongo DB 5 | RepositoryFramework.MongoDB 6 | 2.0.4 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.MongoDB 11 | RepositoryFramework.MongoDB 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | ../RULES.ruleset 18 | 2.0.7 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | All 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3/RepositoryFramework.AWS.S3.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework for Amazon web Services Simple Storage Service 5 | RepositoryFramework.AWS.S3 6 | 1.0.2 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.AWS.S3 11 | RepositoryFramework.AWS.S3 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | ../RULES.ruleset 18 | 1.0.3 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | All 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Extensions for 10 | /// 11 | public static class TaskExtensions 12 | { 13 | /// 14 | /// Wait for the task to complete and rethrow original exceptions 15 | /// 16 | /// Current instance 17 | public static void WaitSync(this Task instance) 18 | { 19 | Exception exceptionInThread = null; 20 | try 21 | { 22 | instance.Wait(); 23 | } 24 | catch (AggregateException aggregateException) 25 | { 26 | aggregateException.Handle((exc) => 27 | { 28 | exceptionInThread = exc; 29 | return true; 30 | }); 31 | } 32 | 33 | if (exceptionInThread != null) 34 | { 35 | throw exceptionInThread; 36 | } 37 | } 38 | 39 | /// 40 | /// Crates a task that will complete when all the task objects in has completed. Original exceptons are rethrown. 41 | /// 42 | /// Current instance 43 | /// The tasks to wait for completion 44 | public static void WhenAllSync(this Task instance, params Task[] tasks) 45 | { 46 | Exception exceptionInThread = null; 47 | try 48 | { 49 | Task.WhenAll(tasks); 50 | } 51 | catch (AggregateException aggregateException) 52 | { 53 | aggregateException.Handle((exc) => 54 | { 55 | exceptionInThread = exc; 56 | return true; 57 | }); 58 | } 59 | 60 | if (exceptionInThread != null) 61 | { 62 | throw exceptionInThread; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB.Test/RepositoryFramework.MongoDB.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;netcoreapp1.1;net461 5 | RepositoryFramework.MongoDB.Test 6 | RepositoryFramework.MongoDB.Test 7 | true 8 | false 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/ISortableRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// Sorts a result sets 8 | /// 9 | /// Entity type 10 | public interface ISortableRepository : IRepository 11 | where TEntity : class 12 | { 13 | /// 14 | /// Gets the kind of sort order 15 | /// 16 | SortOrder SortOrder { get; } 17 | 18 | /// 19 | /// Gets property name for the property to sort by. 20 | /// 21 | string SortPropertyName { get; } 22 | 23 | /// 24 | /// Sort ascending by a property 25 | /// 26 | /// Name of the property. 27 | /// Current instance 28 | ISortableRepository SortBy(string propertyName); 29 | 30 | /// 31 | /// Sort descending by a property. 32 | /// 33 | /// Name of the property. 34 | /// Current instance 35 | ISortableRepository SortByDescending(string propertyName); 36 | 37 | /// 38 | /// Property to sort by (ascending) 39 | /// 40 | /// The property. 41 | /// Current instance 42 | ISortableRepository SortBy(Expression> property); 43 | 44 | /// 45 | /// Property to sort by (descending) 46 | /// 47 | /// The property 48 | /// Current instance 49 | ISortableRepository SortByDescending(Expression> property); 50 | 51 | /// 52 | /// Clear sorting 53 | /// 54 | /// Current instance 55 | ISortableRepository ClearSorting(); 56 | } 57 | } -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/BlobInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RepositoryFramework.Interfaces 5 | { 6 | /// 7 | /// A binary large object 8 | /// 9 | public class BlobInfo 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public BlobInfo() 15 | { 16 | } 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// Id or name of the object 22 | /// Size of the object 23 | /// Uri to access the object 24 | public BlobInfo(string id, long size = -1, Uri uri = null) 25 | { 26 | Id = id; 27 | Size = size; 28 | Uri = uri; 29 | } 30 | 31 | /// 32 | /// Gets or sets Id or name of the object 33 | /// 34 | public virtual string Id { get; set; } 35 | 36 | /// 37 | /// Gets or sets Size of the object 38 | /// 39 | public virtual long Size { get; set; } 40 | 41 | /// 42 | /// Gets or sets Uri to access the object 43 | /// 44 | public virtual Uri Uri { get; set; } 45 | 46 | /// 47 | /// Creates a stream to upload the object. Please wrap the stream into a using block to make sure that all IO resources are closed upon completion of the operation 48 | /// 49 | /// Upload stream 50 | public virtual Stream CreateUploadStream() 51 | { 52 | return null; 53 | } 54 | 55 | /// 56 | /// Creates a stream to download the object. Please wrap the stream into a using block to make sure that all IO resources are closed upon completion of the operation 57 | /// 58 | /// Upload stream 59 | public virtual Stream CreateDownloadStream() 60 | { 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob/RepositoryFramework.Azure.Blob.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework for Microsoft Azure Blob Storage 5 | RepositoryFramework.Azure.Blob 6 | 1.0.2 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.Azure.Blob 11 | RepositoryFramework.Azure.Blob 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | ../RULES.ruleset 18 | 1.0.4 19 | 1.0.4.0 20 | 1.0.4.0 21 | false 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | All 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3.Test/RepositoryFramework.AWS.S3.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0;netcoreapp1.1 5 | RepositoryFramework.AWS.S3.Test 6 | RepositoryFramework.AWS.S3.Test 7 | true 8 | false 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 2.0.0 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB.Test/Mongo2GoTest.cs: -------------------------------------------------------------------------------- 1 | using Mongo2Go; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using MongoDB.Driver; 7 | using MongoDB.Bson.Serialization.Attributes; 8 | using System.IO; 9 | using MongoDB.Bson.Serialization; 10 | using MongoDB.Bson; 11 | using Xunit; 12 | using MongoDB.Bson.Serialization.IdGenerators; 13 | 14 | namespace RepositoryFramework.MongoDB.Test 15 | { 16 | public class Mongo2GoTest : IClassFixture 17 | { 18 | private MongoDBFixture mongoDBFixture; 19 | private string collectionName = "TestCollection"; 20 | 21 | public Mongo2GoTest(MongoDBFixture mongoDBFixture) 22 | { 23 | this.mongoDBFixture = mongoDBFixture; 24 | if (!BsonClassMap.IsClassMapRegistered(typeof(TestDocument))) 25 | { 26 | try 27 | { 28 | BsonClassMap.RegisterClassMap( 29 | map => 30 | { 31 | map.AutoMap(); 32 | map.MapIdMember(c => c.TestDocumentId) 33 | .SetIdGenerator(StringObjectIdGenerator.Instance); 34 | }); 35 | } 36 | catch 37 | { 38 | } 39 | } 40 | } 41 | 42 | public static IList ReadBsonFile(string fileName) 43 | { 44 | string[] content = File.ReadAllLines(fileName); 45 | return content.Select(s => BsonSerializer.Deserialize(s)).ToList(); 46 | } 47 | 48 | [Fact] 49 | public void SaveAndRetrieve() 50 | { 51 | var collection = mongoDBFixture.Database 52 | .GetCollection(collectionName); 53 | 54 | List IEnumerable; 55 | 56 | collection.InsertOne(TestDocument.DummyData1()); 57 | collection.InsertOne(TestDocument.DummyData2()); 58 | collection.InsertOne(TestDocument.DummyData3()); 59 | 60 | IEnumerable = (from c in collection.AsQueryable() 61 | where c.StringTest == TestDocument.DummyData2().StringTest || c.StringTest == TestDocument.DummyData3().StringTest 62 | select c).ToList(); 63 | 64 | Assert.Equal(2, IEnumerable.Count()); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/RepositoryFramework.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework for ReSTful API clients 5 | RepositoryFramework.Api 6 | 2.0.2 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.Api 11 | RepositoryFramework.Api 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | true 18 | ../RULES.ruleset 19 | 2.0.4 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | All 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/ApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace RepositoryFramework.Api 5 | { 6 | /// 7 | /// API Exception 8 | /// 9 | public class ApiException : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public ApiException() 15 | { 16 | } 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// Error code 22 | /// Message 23 | /// Method 24 | /// Base path 25 | /// Entity path 26 | /// Error content 27 | public ApiException( 28 | int errorCode, 29 | string message, 30 | string method, 31 | string basePath, 32 | string path, 33 | object errorContent = null) 34 | : base(message) 35 | { 36 | ErrorCode = errorCode; 37 | Method = method; 38 | BasePath = basePath; 39 | Path = path; 40 | ErrorContent = errorContent; 41 | } 42 | 43 | /// 44 | /// Gets the error code (HTTP status code) 45 | /// 46 | /// The error code (HTTP status code). 47 | public int ErrorCode { get; private set; } 48 | 49 | /// 50 | /// Gets the error content (body json object) 51 | /// 52 | /// The error content (Http response body). 53 | public object ErrorContent { get; private set; } 54 | 55 | /// 56 | /// Gets HTTP Method 57 | /// 58 | public string Method { get; private set; } 59 | 60 | /// 61 | /// Gets Base path 62 | /// 63 | public string BasePath { get; private set; } 64 | 65 | /// 66 | /// Gets Request path 67 | /// 68 | public string Path { get; private set; } 69 | 70 | /// 71 | /// Convert to string 72 | /// 73 | /// String 74 | public override string ToString() 75 | { 76 | return $"{base.ToString()}\nErrorCode: {ErrorCode}\nErrorContent: {ErrorContent}\nMethod: {Method}\nBasePath: {BasePath}\nPath: {Path}"; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper/IDapperRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using RepositoryFramework.Interfaces; 4 | 5 | namespace RepositoryFramework.Dapper 6 | { 7 | /// 8 | /// Repository that uses the Dapper micro-ORM framework, see https://github.com/StackExchange/Dapper 9 | /// 10 | /// Entity type 11 | public interface IDapperRepository : 12 | IRepository, 13 | ISortableRepository, 14 | IPageableRepository, 15 | IFindSql, 16 | IFindSqlAsync 17 | where TEntity : class 18 | { 19 | /// 20 | /// Use paging 21 | /// 22 | /// Page to get (one based index). 23 | /// Number of items per page. 24 | /// Current instance 25 | new IDapperRepository Page(int pageNumber, int pageSize); 26 | 27 | /// 28 | /// Clear paging 29 | /// 30 | /// Current instance 31 | new IDapperRepository ClearPaging(); 32 | 33 | /// 34 | /// Sort ascending by a property 35 | /// 36 | /// Name of the property. 37 | /// Current instance 38 | new IDapperRepository SortBy(string propertyName); 39 | 40 | /// 41 | /// Sort descending by a property. 42 | /// 43 | /// Name of the property. 44 | /// Current instance 45 | new IDapperRepository SortByDescending(string propertyName); 46 | 47 | /// 48 | /// Property to sort by (ascending) 49 | /// 50 | /// The property. 51 | /// Current instance 52 | new IDapperRepository SortBy(Expression> property); 53 | 54 | /// 55 | /// Property to sort by (descending) 56 | /// 57 | /// The property 58 | /// Current instance 59 | new IDapperRepository SortByDescending(Expression> property); 60 | 61 | /// 62 | /// Clear sorting 63 | /// 64 | /// Current instance 65 | new IDapperRepository ClearSorting(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /RepositoryFramework.MongoDB/IMongoDBRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using RepositoryFramework.Interfaces; 4 | 5 | namespace RepositoryFramework.MongoDB 6 | { 7 | /// 8 | /// Repository that uses the MongoDB document database, see https://docs.mongodb.com/ 9 | /// 10 | /// Entity type 11 | public interface IMongoDBRepository : 12 | IRepository, 13 | ISortableRepository, 14 | IPageableRepository, 15 | IQueryableRepository, 16 | IFindWhere, 17 | IFindWhereAsync, 18 | IFindFilter, 19 | IFindFilterAsync 20 | where TEntity : class 21 | { 22 | /// 23 | /// Use paging 24 | /// 25 | /// Page to get (one based index). 26 | /// Number of items per page. 27 | /// Current instance 28 | new IMongoDBRepository Page(int pageNumber, int pageSize); 29 | 30 | /// 31 | /// Clear paging 32 | /// 33 | /// Current instance 34 | new IMongoDBRepository ClearPaging(); 35 | 36 | /// 37 | /// Sort ascending by a property 38 | /// 39 | /// Name of the property. 40 | /// Current instance 41 | new IMongoDBRepository SortBy(string propertyName); 42 | 43 | /// 44 | /// Sort descending by a property. 45 | /// 46 | /// Name of the property. 47 | /// Current instance 48 | new IMongoDBRepository SortByDescending(string propertyName); 49 | 50 | /// 51 | /// Property to sort by (ascending) 52 | /// 53 | /// The property. 54 | /// Current instance 55 | new IMongoDBRepository SortBy(Expression> property); 56 | 57 | /// 58 | /// Property to sort by (descending) 59 | /// 60 | /// The property 61 | /// Current instance 62 | new IMongoDBRepository SortByDescending(Expression> property); 63 | 64 | /// 65 | /// Clear sorting 66 | /// 67 | /// Current instance 68 | new IMongoDBRepository ClearSorting(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework/RepositoryFramework.EntityFramework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A repository framework for Entity Framework Core 5 | RepositoryFramework.EntityFramework 6 | 2.0.5 7 | Henrik Thomsen 8 | netcoreapp2.0;netcoreapp1.1;net452 9 | true 10 | RepositoryFramework.EntityFramework 11 | RepositoryFramework.EntityFramework 12 | https://github.com/henrikbulldog/RepositoryFramework 13 | https://github.com/henrikbulldog/RepositoryFramework/blob/master/LICENSE 14 | false 15 | false 16 | false 17 | ../RULES.ruleset 18 | 2.0.10 19 | 2.0.10.0 20 | 2.0.10.0 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | All 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/RepositoryFramework.EntityFramework.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | RepositoryFramework.EntityFramework.Test 4 | netcoreapp2.0;netcoreapp1.1;net452 5 | RepositoryFramework.EntityFramework.Test 6 | true 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/GenericRepositoryBaseTest.cs: -------------------------------------------------------------------------------- 1 | using RepositoryFramework.Interfaces; 2 | using RepositoryFramework.Test.Models; 3 | using System; 4 | using System.Linq.Expressions; 5 | using Xunit; 6 | 7 | namespace RepositoryFramework.Test 8 | { 9 | public class GenericRepositoryBaseTest 10 | { 11 | private class Foo 12 | { 13 | public Foo() { } 14 | public int Id { get; set; } 15 | public Foo Next { get; set; } 16 | } 17 | 18 | private class GenericRepostoryTest : GenericRepositoryBase 19 | where TEntity : class 20 | { 21 | public bool TryCheckPropertyNameAccessor(string property, out string validatedPropertyName) 22 | { 23 | return TryCheckPropertyName(property, out validatedPropertyName); 24 | } 25 | 26 | public bool TryCheckPropertyPathAccessor(string path, out string validatedPath) 27 | { 28 | return TryCheckPropertyPath(path, out validatedPath); 29 | } 30 | } 31 | 32 | [Theory] 33 | [InlineData("Id", true, "Id")] 34 | [InlineData("Next", true, "Next")] 35 | [InlineData("Invalid", false, "Invalid")] 36 | [InlineData("id", true, "Id")] 37 | [InlineData("neXt", true, "Next")] 38 | public void TryCheckPropertyName(string propertyName, bool expectedReturn, string expectedValidatedName) 39 | { 40 | var genericRepostoryTest = new GenericRepostoryTest(); 41 | string validatedName; 42 | bool actualReturn = genericRepostoryTest.TryCheckPropertyNameAccessor(propertyName, out validatedName); 43 | Assert.Equal(expectedReturn, actualReturn); 44 | Assert.Equal(expectedValidatedName, validatedName); 45 | } 46 | 47 | [Theory] 48 | [InlineData("Id", true, "Id")] 49 | [InlineData("neXt.iD", true, "Next.Id")] 50 | [InlineData("Invalid.Path", false, "Invalid.Path")] 51 | public void TryCheckPropertyPath_Foo( 52 | string propertyPath, 53 | bool expectedReturn, 54 | string expectedValidatedPath) 55 | { 56 | var genericRepostoryTest = new GenericRepostoryTest(); 57 | string validatedPath; 58 | bool actualReturn = genericRepostoryTest.TryCheckPropertyPathAccessor(propertyPath, out validatedPath); 59 | Assert.Equal(expectedReturn, actualReturn); 60 | Assert.Equal(expectedValidatedPath, validatedPath); 61 | } 62 | 63 | [Theory] 64 | [InlineData("products.parts", true, "Products.Parts")] 65 | public void TryCheckPropertyPath_Category(string propertyPath, bool expectedReturn, string expectedValidatedPath) 66 | { 67 | var genericRepostoryTest = new GenericRepostoryTest(); 68 | string validatedPath; 69 | bool actualReturn = genericRepostoryTest.TryCheckPropertyPathAccessor(propertyPath, out validatedPath); 70 | Assert.Equal(expectedReturn, actualReturn); 71 | Assert.Equal(expectedValidatedPath, validatedPath); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/CategoryRepository.cs: -------------------------------------------------------------------------------- 1 | using RepositoryFramework.Test.Filters; 2 | using RepositoryFramework.Test.Models; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using RepositoryFramework.Interfaces; 6 | using Dapper; 7 | using System.Linq; 8 | using System; 9 | 10 | namespace RepositoryFramework.Dapper.Test 11 | { 12 | public class CategoryRepository : DapperRepository 13 | { 14 | public CategoryRepository(IDbConnection connection, 15 | string lastRowIdCommand = "SELECT @@IDENTITY") 16 | : base(connection, lastRowIdCommand) 17 | { 18 | productRepository = new ProductRepository(connection, lastRowIdCommand); 19 | } 20 | 21 | private ProductRepository productRepository = null; 22 | 23 | public override void Create(Category entity) 24 | { 25 | base.Create(entity); 26 | if(entity.Products == null) 27 | { 28 | return; 29 | } 30 | foreach (var product in entity.Products) 31 | { 32 | product.CategoryId = entity.Id; 33 | productRepository.Create(product); 34 | } 35 | } 36 | 37 | public override Category GetById(object id) 38 | { 39 | var category = base.GetById(id); 40 | 41 | if (category == null) 42 | { 43 | return null; 44 | } 45 | 46 | category.Products = new List(); 47 | var products = productRepository 48 | .FindByCategoryId(id); 49 | 50 | if(products.Count() > 0) 51 | { 52 | foreach(var product in products) 53 | { 54 | category.Products.Add(product); 55 | } 56 | } 57 | 58 | return category; 59 | } 60 | 61 | public override IEnumerable Find() 62 | { 63 | if (Connection.State != ConnectionState.Open) 64 | { 65 | Connection.Open(); 66 | } 67 | 68 | var findQuery = $@" 69 | SELECT * FROM Category 70 | OUTER LEFT JOIN Product 71 | ON Product.CategoryId = Category.Id"; 72 | 73 | var lookup = new Dictionary(); 74 | IEnumerable result = SqlMapper.Query( 75 | Connection, 76 | findQuery, 77 | (category, product) => Map(category, product, lookup)); 78 | 79 | return lookup.Values.AsEnumerable(); 80 | } 81 | 82 | private Category Map(Category category, Product product, Dictionary lookup) 83 | { 84 | Category currentCategory; 85 | 86 | if (!lookup.TryGetValue(category.Id, out currentCategory)) 87 | { 88 | currentCategory = category; 89 | lookup.Add(category.Id, currentCategory); 90 | } 91 | 92 | if (currentCategory.Products == null) 93 | { 94 | currentCategory.Products = new List(); 95 | } 96 | 97 | if (product != null) 98 | { 99 | currentCategory.Products.Add(product); 100 | } 101 | return currentCategory; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/IQueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace RepositoryFramework.Interfaces 7 | { 8 | /// 9 | /// Extensions for 10 | /// 11 | public static class IQueryableExtensions 12 | { 13 | /// 14 | /// Sorts a queryable collection 15 | /// 16 | /// Queryable collection 17 | /// Sortable repository 18 | /// Entity type 19 | /// Sorted queryable collection 20 | public static IQueryable Sort( 21 | this IQueryable query, 22 | ISortableRepository sortable) 23 | where TEntity : class 24 | { 25 | if (query == null) 26 | { 27 | throw new ArgumentNullException(nameof(query)); 28 | } 29 | 30 | if (sortable == null) 31 | { 32 | throw new ArgumentNullException(nameof(sortable)); 33 | } 34 | 35 | if (string.IsNullOrEmpty(sortable.SortPropertyName)) 36 | { 37 | return query; 38 | } 39 | 40 | if (sortable.SortOrder == SortOrder.Unspecified) 41 | { 42 | return query; 43 | } 44 | 45 | var property = typeof(TEntity).GetProperty(sortable.SortPropertyName); 46 | var parameter = Expression.Parameter(typeof(TEntity), "p"); 47 | var propertyAccess = Expression.MakeMemberAccess(parameter, property); 48 | var orderByExp = Expression.Lambda(propertyAccess, parameter); 49 | var resultExp = Expression.Call( 50 | typeof(Queryable), 51 | sortable.SortOrder == SortOrder.Ascending ? "OrderBy" : "OrderByDescending", 52 | new[] { typeof(TEntity), property.PropertyType }, 53 | query.Expression, 54 | Expression.Quote(orderByExp)); 55 | return query.Provider.CreateQuery(resultExp); 56 | } 57 | 58 | /// 59 | /// Page a queryable collection 60 | /// 61 | /// Queryable collection 62 | /// Pageable repository 63 | /// Entity type 64 | /// Paged queryable collection 65 | public static IQueryable Page( 66 | this IQueryable query, 67 | IPageableRepository pageable) 68 | where TEntity : class 69 | { 70 | if (query == null) 71 | { 72 | throw new ArgumentNullException(nameof(query)); 73 | } 74 | 75 | if (pageable == null) 76 | { 77 | throw new ArgumentNullException(nameof(pageable)); 78 | } 79 | 80 | if (pageable.PageSize > 0) 81 | { 82 | if (pageable.PageNumber >= 1) 83 | { 84 | query = query.Skip((pageable.PageNumber - 1) * pageable.PageSize); 85 | } 86 | 87 | query = query.Take(pageable.PageSize); 88 | } 89 | 90 | return query; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework/IEntityFrameworkRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using RepositoryFramework.Interfaces; 4 | 5 | namespace RepositoryFramework.EntityFramework 6 | { 7 | /// 8 | /// Repository that uses Entity Framework Core 9 | /// 10 | /// Entity type 11 | public interface IEntityFrameworkRepository : 12 | IRepository, 13 | IExpandableRepository, 14 | ISortableRepository, 15 | IPageableRepository, 16 | IQueryableRepository, 17 | IUnitOfWorkRepository, 18 | IUnitOfWorkRepositoryAsync, 19 | IFindWhere, 20 | IFindWhereAsync, 21 | IFindSql, 22 | IFindSqlAsync 23 | where TEntity : class 24 | { 25 | /// 26 | /// Use paging 27 | /// 28 | /// Page to get (one based index). 29 | /// Number of items per page. 30 | /// Current instance 31 | new IEntityFrameworkRepository Page(int pageNumber, int pageSize); 32 | 33 | /// 34 | /// Clear paging 35 | /// 36 | /// Current instance 37 | new IEntityFrameworkRepository ClearPaging(); 38 | 39 | /// 40 | /// Include referenced properties 41 | /// 42 | /// Comma-separated list of property paths 43 | /// Current instance 44 | new IEntityFrameworkRepository Include(string propertyPaths); 45 | 46 | /// 47 | /// Include referenced property 48 | /// 49 | /// Property expression 50 | /// Current instance 51 | new IEntityFrameworkRepository Include(Expression> property); 52 | 53 | /// 54 | /// Clear includes 55 | /// 56 | /// Current instance 57 | new IEntityFrameworkRepository ClearIncludes(); 58 | 59 | /// 60 | /// Sort ascending by a property 61 | /// 62 | /// Name of the property. 63 | /// Current instance 64 | new IEntityFrameworkRepository SortBy(string propertyName); 65 | 66 | /// 67 | /// Sort descending by a property. 68 | /// 69 | /// Name of the property. 70 | /// Current instance 71 | new IEntityFrameworkRepository SortByDescending(string propertyName); 72 | 73 | /// 74 | /// Property to sort by (ascending) 75 | /// 76 | /// The property. 77 | /// Current instance 78 | new IEntityFrameworkRepository SortBy(Expression> property); 79 | 80 | /// 81 | /// Property to sort by (descending) 82 | /// 83 | /// The property 84 | /// Current instance 85 | new IEntityFrameworkRepository SortByDescending(Expression> property); 86 | 87 | /// 88 | /// Clear sorting 89 | /// 90 | /// Current instance 91 | new IEntityFrameworkRepository ClearSorting(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /RepositoryFramework.Api/ApiConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace RepositoryFramework.Api 6 | { 7 | /// 8 | /// Represents a set of configuration settings 9 | /// 10 | public class ApiConfiguration 11 | { 12 | private const string Iso8601DateTimeFormat = "o"; 13 | 14 | private static string tempFolderPath = Path.GetTempPath(); 15 | 16 | private string dateTimeFormat = Iso8601DateTimeFormat; 17 | 18 | /// 19 | /// Gets or sets the temporary folder path to store the files downloaded from the server. 20 | /// 21 | /// Folder path. 22 | public static string TempFolderPath 23 | { 24 | get 25 | { 26 | return tempFolderPath; 27 | } 28 | 29 | set 30 | { 31 | if (string.IsNullOrEmpty(value)) 32 | { 33 | tempFolderPath = value; 34 | return; 35 | } 36 | 37 | // create the directory if it does not exist 38 | if (!Directory.Exists(value)) 39 | { 40 | Directory.CreateDirectory(value); 41 | } 42 | 43 | // check if the path contains directory separator at the end 44 | if (value[value.Length - 1] == Path.DirectorySeparatorChar) 45 | { 46 | tempFolderPath = value; 47 | } 48 | else 49 | { 50 | tempFolderPath = value + Path.DirectorySeparatorChar; 51 | } 52 | } 53 | } 54 | 55 | /// 56 | /// Gets or sets the username (HTTP basic authentication). 57 | /// 58 | /// The username. 59 | public string Username { get; set; } 60 | 61 | /// 62 | /// Gets or sets the password (HTTP basic authentication). 63 | /// 64 | /// The password. 65 | public string Password { get; set; } 66 | 67 | /// 68 | /// Gets or sets authentication settings 69 | /// 70 | public AuthenticationType AuthenticationType { get; set; } = AuthenticationType.Anonymous; 71 | 72 | /// 73 | /// Gets or sets the API key based on the authentication name. 74 | /// 75 | /// The API key. 76 | public Dictionary ApiKey { get; set; } = new Dictionary(); 77 | 78 | /// 79 | /// Gets or sets the prefix (e.g. Token) of the API key based on the authentication name. 80 | /// 81 | /// The prefix of the API key. 82 | public Dictionary ApiKeyPrefix { get; set; } = new Dictionary(); 83 | 84 | /// 85 | /// Gets or sets the the date time format used when serializing in the ApiClient 86 | /// By default, it's set to ISO 8601 - "o", for others see: 87 | /// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx 88 | /// and https://msdn.microsoft.com/en-us/library/8kb3ddd4(v=vs.110).aspx 89 | /// No validation is done to ensure that the string you're providing is valid 90 | /// 91 | /// The DateTimeFormat string 92 | public string DateTimeFormat 93 | { 94 | get 95 | { 96 | return dateTimeFormat; 97 | } 98 | 99 | set 100 | { 101 | if (string.IsNullOrEmpty(value)) 102 | { 103 | // Never allow a blank or null string, go back to the default 104 | dateTimeFormat = Iso8601DateTimeFormat; 105 | return; 106 | } 107 | 108 | // Caution, no validation when you choose date time format other than ISO 8601 109 | // Take a look at the above links 110 | dateTimeFormat = value; 111 | } 112 | } 113 | 114 | /// 115 | /// Gets or sets OAuth2 access token 116 | /// 117 | public string AccessToken { get; set; } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /RepositoryFramework.EntityFramework.Test/EFCoreTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using RepositoryFramework.EntityFramework; 3 | using RepositoryFramework.Test.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace RepositoryFramework.Test 11 | { 12 | public class EFCoreTest 13 | { 14 | [Fact] 15 | public void EFCore_Should_Not_Support_Lazy_Load() 16 | { 17 | // Create new empty database 18 | using (var db = new SQLiteContext()) 19 | { 20 | // Arrange 21 | var cr = new EntityFrameworkRepository(db, null, false); 22 | var c = CreateCategory(100); 23 | cr.Create(c); 24 | cr.SaveChanges(); 25 | 26 | // Detach all to avoid expansions already cached in the context 27 | cr.DetachAll(); 28 | 29 | // Act 30 | var cdb = cr.Find().First(); 31 | Assert.NotNull(cdb); 32 | 33 | // Assert 34 | // Looks like lazy loading is not supported in EF Core 35 | Assert.Null(cdb.Products); 36 | } 37 | } 38 | 39 | [Fact] 40 | public void EFCore_Should_Support_Circular_References() 41 | { 42 | // Create new empty database 43 | using (var db = new SQLiteContext()) 44 | { 45 | // Arrange 46 | var cr = new EntityFrameworkRepository(db, null, false); 47 | var c = CreateCategory(100); 48 | cr.Create(c); 49 | cr.SaveChanges(); 50 | 51 | // Detach all to avoid expansions already cached in the context 52 | cr.DetachAll(); 53 | 54 | // Act 55 | var cdb = cr 56 | .Include("Products.Parts.Product") 57 | .Find().First(); 58 | 59 | Assert.NotNull(cdb); 60 | var products = cdb.Products; 61 | 62 | // Assert 63 | Assert.NotNull(products); 64 | Assert.Equal(100, products.Count); 65 | var product = products.First(); 66 | for (int i = 0; i < 10; i++) 67 | { 68 | Assert.NotNull(product); 69 | Assert.NotNull(product.Parts); 70 | var part = product.Parts.First(); 71 | product = part.Product; 72 | } 73 | } 74 | } 75 | 76 | [Fact] 77 | public void Join_Should_Work_Without_Expansion() 78 | { 79 | // Create new empty database 80 | using (var db = new SQLiteContext()) 81 | { 82 | // Arrange 83 | var cr = new EntityFrameworkRepository(db); 84 | var c = CreateCategory(100); 85 | cr.Create(c); 86 | 87 | // Act 88 | var pr = new EntityFrameworkRepository(db); 89 | var pdb = pr 90 | .Find(p => p.Product.Id == 1).First(); 91 | 92 | Assert.NotNull(pdb); 93 | 94 | // Assert 95 | Assert.NotNull(pdb); 96 | } 97 | } 98 | 99 | private Category CreateCategory(int productRows = 100) 100 | { 101 | var c = new Category 102 | { 103 | Name = Guid.NewGuid().ToString(), 104 | Description = Guid.NewGuid().ToString() 105 | }; 106 | var products = new List(); 107 | for (int i = 0; i < productRows; i++) 108 | { 109 | products.Add(CreateProduct(c)); 110 | }; 111 | c.Products = products; 112 | return c; 113 | } 114 | private Product CreateProduct(Category c, int partRows = 100) 115 | { 116 | var p = new Product 117 | { 118 | Name = Guid.NewGuid().ToString().ToLower().Replace("aa", "ab"), 119 | Category = c, 120 | Description = Guid.NewGuid().ToString(), 121 | Price = 1.23M 122 | }; 123 | var l = new List(); 124 | for (int i = 0; i < partRows; i++) 125 | { 126 | l.Add(CreatePart(p)); 127 | }; 128 | p.Parts = l; 129 | return p; 130 | } 131 | 132 | private Part CreatePart(Product p) 133 | { 134 | return new Part 135 | { 136 | Name = Guid.NewGuid().ToString().ToLower().Replace("aa", "ab"), 137 | Product = p, 138 | Description = Guid.NewGuid().ToString(), 139 | Price = 1.23M 140 | }; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3.Test/AWSS3RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.IO; 4 | using Microsoft.Extensions.Configuration; 5 | using Amazon.S3; 6 | using Moq; 7 | using RepositoryFramework.Interfaces; 8 | using RepositoryFramework.AWS.S3; 9 | using Xunit; 10 | using Amazon.S3.Model; 11 | using System.Threading.Tasks; 12 | using System.Threading; 13 | using System; 14 | using System.Text; 15 | 16 | namespace RepositoryFramework.Azure.Blob.Test 17 | { 18 | public class AWSS3RepositoryTest 19 | { 20 | /// 21 | /// Set to false to test AWS Simple Storage Service 22 | /// An AWS credential file must be present in C:\Users\[user name]\.aws with this content: 23 | /// [local-test-profile] 24 | /// aws_access_key_id = key 25 | /// aws_secret_access_key = key 26 | /// 27 | /// appSettings múst contaion valid AWS settings (check region) 28 | /// 29 | /// bucketName must be set to a valid bucket name 30 | /// 31 | private bool useMockContainer = true; 32 | 33 | private const string bucketName = "foss.enablement.s3"; 34 | 35 | [Fact] 36 | public void Upload_And_Download() 37 | { 38 | using (var s3client = GetAWSS3Client()) 39 | { 40 | var r = new AWSS3Repository(s3client, bucketName); 41 | var blob = new BlobInfo($"AWSS3RepositoryTest/Upload_And_Download/{Guid.NewGuid()}"); 42 | var payload = "payload"; 43 | Upload(blob, payload, r); 44 | var result = Download(blob.Id, r); 45 | 46 | if (!useMockContainer) 47 | { 48 | Assert.Equal(payload, result); 49 | } 50 | 51 | r.Delete(blob); 52 | } 53 | } 54 | 55 | private void Upload(BlobInfo blob, string payload, IBlobRepository repository) 56 | { 57 | var uploadBuffer = Encoding.UTF8.GetBytes(payload); 58 | using (var uploadStream = new MemoryStream(uploadBuffer)) 59 | { 60 | repository.Upload(blob, uploadStream); 61 | } 62 | } 63 | 64 | private string Download(string id, IBlobRepository repository) 65 | { 66 | var blob = repository.GetById(id); 67 | if (blob != null && blob.Size > 0) 68 | { 69 | var downloadBuffer = new byte[blob.Size]; 70 | using (var downloadStream = new MemoryStream(downloadBuffer)) 71 | { 72 | repository.Download(blob, downloadStream); 73 | } 74 | return Encoding.UTF8.GetString(downloadBuffer); 75 | } 76 | return string.Empty; 77 | } 78 | 79 | [Fact] 80 | public void Find() 81 | { 82 | using (var s3client = GetAWSS3Client()) 83 | { 84 | var r = new AWSS3Repository(s3client, bucketName); 85 | var blobs = new List 86 | { 87 | new BlobInfo("AWSS3RepositoryTest.Find/file1.ext"), 88 | new BlobInfo("AWSS3RepositoryTest.Find/folder1/file2.ext"), 89 | new BlobInfo("AWSS3RepositoryTest.Find/folder1/file3.ext") 90 | }; 91 | Upload(blobs[0], "payload1", r); 92 | Upload(blobs[1], "payload2", r); 93 | Upload(blobs[2], "payload3", r); 94 | if (!useMockContainer) 95 | { 96 | Assert.Equal(3, r.Find("AWSS3RepositoryTest.Find/").Count()); 97 | Assert.Equal(2, r.Find("AWSS3RepositoryTest.Find/folder1/").Count()); 98 | } 99 | r.DeleteMany(blobs); 100 | } 101 | } 102 | 103 | [Fact] 104 | public void GetById_Not_Found() 105 | { 106 | using (var s3client = GetAWSS3Client()) 107 | { 108 | var r = new AWSS3Repository(s3client, bucketName); 109 | var id = "AzureBlobRepositoryTest.GetById_Not_Found.ext"; 110 | var result = r.GetById(id); 111 | if (!useMockContainer) 112 | { 113 | Assert.Null(result); 114 | } 115 | } 116 | } 117 | 118 | public static IConfigurationRoot Configuration { get; set; } 119 | 120 | private IAmazonS3 GetAWSS3Client() 121 | { 122 | if (useMockContainer) 123 | { 124 | return GetAWSS3ClientMock().Object; 125 | } 126 | var builder = new ConfigurationBuilder() 127 | .SetBasePath(Directory.GetCurrentDirectory()) 128 | .AddJsonFile("appsettings.json"); 129 | 130 | Configuration = builder.Build(); 131 | var options = Configuration.GetAWSOptions(); 132 | 133 | return options.CreateServiceClient(); 134 | } 135 | 136 | private Mock GetAWSS3ClientMock() 137 | { 138 | var s3ClientMock = new Mock(MockBehavior.Loose); 139 | var getObjectResponseMock = new Mock(MockBehavior.Loose); 140 | s3ClientMock 141 | .Setup(c => c.GetObjectAsync(It.IsAny(), It.IsAny())) 142 | .Returns(Task.FromResult(getObjectResponseMock.Object)); 143 | return s3ClientMock; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | /RepositoryFramework.MongoDB.Test/data 244 | /RepositoryFramework.Api.Test 245 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob.Test/AzureBlobRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using System.Threading; 7 | using System.IO; 8 | using Xunit; 9 | using RepositoryFramework.Interfaces; 10 | using System.Text; 11 | using Microsoft.Azure.Storage.Auth; 12 | using Microsoft.Azure.Storage.Blob; 13 | using Microsoft.Azure.Storage; 14 | using Microsoft.Azure.Services.AppAuthentication; 15 | 16 | namespace RepositoryFramework.Azure.Blob.Test 17 | { 18 | public class AzureBlobRepositoryTest 19 | { 20 | /// 21 | /// Set to false to test Microsoft Azure Storage Service and set connection string in environment variable azureStorageConnectionEnvironmentVariable 22 | /// 23 | private bool useMockContainer = true; 24 | 25 | /// 26 | /// Specify environment variable that contains Azure Storage connection string 27 | /// 28 | private const string azureStorageConnectionEnvironmentVariable = "Azure.Storage.Connection"; 29 | 30 | /// 31 | /// Specify environment variable that contains Azure Storage URI 32 | /// 33 | private const string azureStorageAccountNameEnvironmentVariable = "Azure.Storage.AccountName"; 34 | 35 | [Fact] 36 | public void Upload_And_Download_File() 37 | { 38 | var uploadFolder = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Upload"); 39 | Directory.CreateDirectory(uploadFolder); 40 | using (var file = File.CreateText(Path.Combine(uploadFolder, "file1.txt"))) 41 | { 42 | file.WriteLine("payload"); 43 | } 44 | 45 | var blobRepository = new AzureBlobRepository(GetCloudBlobContainer()); 46 | var blob = new BlobInfo("cloudFolder/file1.ext"); 47 | using (var uploadStream = new FileStream(Path.Combine(uploadFolder, "file1.txt"), FileMode.Open)) 48 | { 49 | blobRepository.Upload(blob, uploadStream); 50 | } 51 | 52 | var downloadFolder = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Download"); 53 | Directory.CreateDirectory(downloadFolder); 54 | 55 | using (var uploadStream = new FileStream(Path.Combine(downloadFolder, "file1.txt"), FileMode.Create)) 56 | { 57 | blobRepository.Download(blob, uploadStream); 58 | } 59 | 60 | if (!useMockContainer) 61 | { 62 | Assert.Equal( 63 | new FileInfo(Path.Combine(uploadFolder, "file1.txt")).Length, 64 | new FileInfo(Path.Combine(downloadFolder, "file1.txt")).Length); 65 | Assert.True( 66 | File.ReadAllBytes(Path.Combine(uploadFolder, "file1.txt")) 67 | .SequenceEqual(File.ReadAllBytes(Path.Combine(downloadFolder, "file1.txt")))); 68 | } 69 | 70 | blobRepository.Delete(blob); 71 | File.Delete(Path.Combine(uploadFolder, "file1.txt")); 72 | File.Delete(Path.Combine(downloadFolder, "file1.txt")); 73 | } 74 | 75 | 76 | [Fact] 77 | public void Upload_And_Download() 78 | { 79 | var r = new AzureBlobRepository(GetCloudBlobContainer()); 80 | var blob = new BlobInfo($"AzureBlobRepositoryTest/Upload_And_Download/{Guid.NewGuid()}"); 81 | var payload = "payload"; 82 | Upload(blob, payload, r); 83 | var result = Download(blob.Id, r); 84 | 85 | if (!useMockContainer) 86 | { 87 | Assert.Equal(payload, result); 88 | } 89 | 90 | r.Delete(blob); 91 | } 92 | 93 | private void Upload(BlobInfo blob, string payload, IBlobRepository repository) 94 | { 95 | var uploadBuffer = Encoding.UTF8.GetBytes(payload); 96 | using (var uploadStream = new MemoryStream(uploadBuffer)) 97 | { 98 | repository.Upload(blob, uploadStream); 99 | } 100 | } 101 | 102 | private string Download(string id, IBlobRepository repository) 103 | { 104 | var blob = repository.GetById(id); 105 | if (blob != null && blob.Size > 0) 106 | { 107 | var downloadBuffer = new byte[blob.Size]; 108 | using (var downloadStream = new MemoryStream(downloadBuffer)) 109 | { 110 | repository.Download(blob, downloadStream); 111 | } 112 | return Encoding.UTF8.GetString(downloadBuffer); 113 | } 114 | return string.Empty; 115 | } 116 | 117 | [Fact] 118 | public void Find() 119 | { 120 | var r = new AzureBlobRepository(GetCloudBlobContainer()); 121 | var blobs = new List 122 | { 123 | new BlobInfo("AzureBlobRepositoryTest.Find/file1.ext"), 124 | new BlobInfo("AzureBlobRepositoryTest.Find/folder1/file2.ext"), 125 | new BlobInfo("AzureBlobRepositoryTest.Find/folder1/file3.ext") 126 | }; 127 | Upload(blobs[0], "payload1", r); 128 | Upload(blobs[1], "payload2", r); 129 | Upload(blobs[2], "payload3", r); 130 | if (!useMockContainer) 131 | { 132 | Assert.Equal(3, r.Find("AzureBlobRepositoryTest.Find/").Count()); 133 | Assert.Equal(2, r.Find("AzureBlobRepositoryTest.Find/folder1/").Count()); 134 | } 135 | r.DeleteMany(blobs); 136 | } 137 | 138 | [Fact] 139 | public void GetById_Not_Found() 140 | { 141 | var r = new AzureBlobRepository(GetCloudBlobContainer()); 142 | var id = "AzureBlobRepositoryTest.GetById_Not_Found.ext"; 143 | var result = r.GetById(id); 144 | if (!useMockContainer) 145 | { 146 | Assert.Null(result); 147 | } 148 | } 149 | 150 | private CloudBlobContainer GetCloudBlobContainer() 151 | { 152 | if (useMockContainer) 153 | { 154 | return GetCloudBlobContainerMock().Object; 155 | } 156 | 157 | var connectionString = Environment.GetEnvironmentVariable(azureStorageConnectionEnvironmentVariable); 158 | if (!string.IsNullOrWhiteSpace(connectionString)) 159 | { 160 | CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString); 161 | CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); 162 | return blobClient.GetContainerReference("data"); 163 | } 164 | 165 | var accountName = Environment.GetEnvironmentVariable(azureStorageAccountNameEnvironmentVariable); 166 | if (!string.IsNullOrWhiteSpace(accountName)) 167 | { 168 | var tokenProvider = new AzureServiceTokenProvider(); 169 | var token = tokenProvider.GetAccessTokenAsync($"https://{accountName}.core.windows.net").GetAwaiter().GetResult(); 170 | var tokenCred = new TokenCredential(token); 171 | var creds = new StorageCredentials(tokenCred); 172 | 173 | var cloudStorageAccount = new CloudStorageAccount(storageCredentials: creds, accountName: accountName, endpointSuffix: "core.windows.net", useHttps: true); 174 | var blobClient = cloudStorageAccount.CreateCloudBlobClient(); 175 | return blobClient.GetContainerReference("data"); 176 | } 177 | 178 | return null; 179 | } 180 | 181 | private Mock GetCloudBlobContainerMock() 182 | { 183 | var mockBlobUri = new Uri("http://bogus/myaccount/blob"); 184 | var mockBlobContainer = new Mock(MockBehavior.Loose, mockBlobUri); 185 | var mockBlobItem = new Mock(MockBehavior.Loose, mockBlobUri); 186 | mockBlobContainer 187 | .Setup(c => c.GetBlockBlobReference(It.IsAny())) 188 | .Returns(mockBlobItem.Object) 189 | .Verifiable(); 190 | return mockBlobContainer; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /RepositoryFramework.Interfaces/GenericRepositoryBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | 9 | namespace RepositoryFramework.Interfaces 10 | { 11 | /// 12 | /// Base class for generic repositories 13 | /// 14 | /// Entoty type 15 | public abstract class GenericRepositoryBase 16 | where TEntity : class 17 | { 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// Id property expression 22 | public GenericRepositoryBase(Expression> idProperty = null) 23 | { 24 | if (idProperty != null) 25 | { 26 | IdPropertyName = GetPropertyName(idProperty); 27 | } 28 | else 29 | { 30 | IdPropertyName = FindIdProperty(); 31 | } 32 | } 33 | 34 | /// 35 | /// Gets entity type 36 | /// 37 | protected static Type EntityType { get; private set; } = typeof(TEntity); 38 | 39 | /// 40 | /// Gets entity type name 41 | /// 42 | protected static string EntityTypeName { get; private set; } = typeof(TEntity).Name; 43 | 44 | /// 45 | /// Gets entity database columns: all value type properties 46 | /// 47 | protected static string[] EntityColumns { get; private set; } = typeof(TEntity).GetProperties() 48 | .Where(p => (p.PropertyType.GetTypeInfo().GetInterface("IEnumerable") == null 49 | && p.PropertyType.GetTypeInfo().GetInterface("ICollection") == null 50 | && !p.PropertyType.GetTypeInfo().IsClass) 51 | || p.PropertyType.IsAssignableFrom(typeof(string))) 52 | .Select(p => p.Name) 53 | .ToArray(); 54 | 55 | /// 56 | /// Gets entity Id property 57 | /// 58 | protected string IdPropertyName { get; private set; } 59 | 60 | /// 61 | /// Find the Id property of the entity type looking for properties with name Id or (entity type name)Id 62 | /// 63 | /// Id property name or null if none could befound 64 | protected static string FindIdProperty() 65 | { 66 | var idProperty = EntityColumns 67 | .FirstOrDefault(c => c.ToLower() == $"{EntityTypeName.ToLower()}id"); 68 | 69 | if (idProperty == null) 70 | { 71 | idProperty = EntityColumns 72 | .FirstOrDefault(c => c.ToLower() == "id"); 73 | } 74 | 75 | return idProperty; 76 | } 77 | 78 | /// 79 | /// Get the name of a property from an expression 80 | /// 81 | /// Property expression 82 | /// Property name 83 | protected static string GetPropertyName(Expression> propertyExpression) 84 | { 85 | var body = propertyExpression.Body as MemberExpression; 86 | 87 | if (body != null) 88 | { 89 | return body.Member.Name; 90 | } 91 | 92 | var ubody = (UnaryExpression)propertyExpression.Body; 93 | body = ubody.Operand as MemberExpression; 94 | 95 | return body?.Member.Name ?? string.Empty; 96 | } 97 | 98 | /// 99 | /// Chech property name 100 | /// 101 | /// Property name 102 | /// Validated property name, casing is corrected 103 | /// Success 104 | protected static bool TryCheckPropertyName(string property, out string validatedPropertyName) 105 | { 106 | validatedPropertyName = property; 107 | var pi = EntityType.GetProperty(property, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); 108 | if (pi == null) 109 | { 110 | return false; 111 | } 112 | 113 | validatedPropertyName = pi.Name; 114 | return true; 115 | } 116 | 117 | /// 118 | /// Check property path 119 | /// 120 | /// Path to a property or a property of a related type 121 | /// Validated path, property name casing is corrected 122 | /// Success 123 | protected static bool TryCheckPropertyPath(string path, out string validatedPath) 124 | { 125 | validatedPath = path; 126 | var properties = path.Split('.'); 127 | List validated = new List(); 128 | 129 | var type = EntityType; 130 | foreach (var property in properties) 131 | { 132 | var pi = type.GetProperty(property, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); 133 | if (pi == null) 134 | { 135 | return false; 136 | } 137 | 138 | validated.Add(pi.Name); 139 | if (pi.PropertyType.IsArray) 140 | { 141 | type = pi.PropertyType.GetElementType(); 142 | } 143 | else if (pi.PropertyType.IsConstructedGenericType) 144 | { 145 | type = pi.PropertyType.GetGenericArguments().Single(); 146 | } 147 | else 148 | { 149 | type = pi.PropertyType; 150 | } 151 | } 152 | 153 | validatedPath = string.Join(".", validated); 154 | return true; 155 | } 156 | 157 | /// 158 | /// Make sure that the property exists in the model. 159 | /// 160 | /// The name. 161 | /// Validated name 162 | protected static void ValidatePropertyName(string name, out string validatedName) 163 | { 164 | validatedName = name; 165 | if (name == null) 166 | { 167 | throw new ArgumentNullException(nameof(name)); 168 | } 169 | 170 | if (!TryCheckPropertyName(name, out validatedName)) 171 | { 172 | throw new ArgumentException( 173 | string.Format( 174 | "'{0}' is not a public property of '{1}'.", 175 | name, 176 | EntityTypeName)); 177 | } 178 | } 179 | 180 | /// 181 | /// Convert parameter collection to an object 182 | /// 183 | /// Parameter collection 184 | /// Object 185 | protected static object ToObject(IDictionary parameters) 186 | { 187 | var dynamicObject = new ExpandoObject() as IDictionary; 188 | foreach (var parameter in parameters) 189 | { 190 | dynamicObject.Add(parameter.Key, parameter.Value); 191 | } 192 | 193 | return dynamicObject; 194 | } 195 | 196 | /// 197 | /// Gets a property selector expression from the property name 198 | /// 199 | /// Property name 200 | /// Property selector expression 201 | protected static Expression> GetPropertySelector(string propertyName) 202 | { 203 | var arg = Expression.Parameter(typeof(TEntity), "x"); 204 | var property = Expression.Property(arg, propertyName); 205 | var conv = Expression.Convert(property, typeof(object)); 206 | var exp = Expression.Lambda>(conv, new ParameterExpression[] { arg }); 207 | return exp; 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /RepositoryFramework.Azure.Blob/AzureBlobRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.Storage.Blob; 5 | using RepositoryFramework.Interfaces; 6 | 7 | namespace RepositoryFramework.Azure.Blob 8 | { 9 | /// 10 | /// Microsoft Azure Blob Storage repository 11 | /// 12 | public class AzureBlobRepository : IBlobRepository 13 | { 14 | private CloudBlobContainer container = null; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// Azure Blob Storage conainer 20 | public AzureBlobRepository( 21 | CloudBlobContainer container) 22 | { 23 | this.container = container; 24 | container.CreateIfNotExistsAsync().WaitSync(); 25 | } 26 | 27 | /// 28 | /// Delete an existing entity 29 | /// 30 | /// Entity 31 | public void Delete(BlobInfo entity) 32 | { 33 | DeleteAsync(entity).WaitSync(); 34 | } 35 | 36 | /// 37 | /// Delete an existing entity 38 | /// 39 | /// Entity 40 | /// Task 41 | public async Task DeleteAsync(BlobInfo entity) 42 | { 43 | CloudBlockBlob blockBlob = container.GetBlockBlobReference(entity.Id); 44 | await blockBlob.DeleteIfExistsAsync(); 45 | } 46 | 47 | /// 48 | /// Delete a list of existing entities 49 | /// 50 | /// Entity list 51 | public void DeleteMany(IEnumerable entities) 52 | { 53 | DeleteManyAsync(entities).WaitSync(); 54 | } 55 | 56 | /// 57 | /// Delete a list of existing entities 58 | /// 59 | /// Entity list 60 | /// Task 61 | public async Task DeleteManyAsync(IEnumerable entities) 62 | { 63 | foreach (var entity in entities) 64 | { 65 | CloudBlockBlob blockBlob = container.GetBlockBlobReference(entity.Id); 66 | await blockBlob.DeleteIfExistsAsync(); 67 | } 68 | } 69 | 70 | /// 71 | /// Get a list of entities 72 | /// 73 | /// Query result 74 | public IEnumerable Find() 75 | { 76 | var t = FindAsync(); 77 | t.WaitSync(); 78 | return t.Result; 79 | } 80 | 81 | /// 82 | /// Get a list of entities 83 | /// 84 | /// Query result 85 | public async Task> FindAsync() 86 | { 87 | return await FindAsync(null); 88 | } 89 | 90 | /// 91 | /// Filters a collection of entities from a filter definition 92 | /// 93 | /// Filter objects by prefix 94 | /// Filtered collection of entities 95 | public IEnumerable Find(string prefix) 96 | { 97 | var t = FindAsync(prefix); 98 | t.WaitSync(); 99 | return t.Result; 100 | } 101 | 102 | /// 103 | /// Filters a collection of entities from a filter definition 104 | /// 105 | /// Filter objects by prefix 106 | /// Filtered collection of entities 107 | public async Task> FindAsync(string prefix) 108 | { 109 | BlobContinuationToken continuationToken = null; 110 | BlobResultSegment resultSegment = null; 111 | var blobs = new List(); 112 | do 113 | { 114 | resultSegment = await container.ListBlobsSegmentedAsync(prefix, true, BlobListingDetails.Metadata, null, continuationToken, null, null); 115 | if (resultSegment != null && resultSegment.Results != null) 116 | { 117 | foreach (var cloudBlob in resultSegment.Results) 118 | { 119 | var block = cloudBlob as CloudBlockBlob; 120 | if (block != null) 121 | { 122 | var blob = new Interfaces.BlobInfo(); 123 | blob.Id = block.Name; 124 | blob.Size = block.Properties.Length; 125 | blob.Uri = block.Uri; 126 | blobs.Add(blob); 127 | } 128 | } 129 | 130 | continuationToken = resultSegment.ContinuationToken; 131 | } 132 | else 133 | { 134 | continuationToken = null; 135 | } 136 | } 137 | while (continuationToken != null); 138 | 139 | return blobs; 140 | } 141 | 142 | /// 143 | /// Gets an entity by id. 144 | /// 145 | /// Filter to find a single item 146 | /// Entity 147 | public Interfaces.BlobInfo GetById(object id) 148 | { 149 | var t = GetByIdAsync(id); 150 | t.WaitSync(); 151 | return t.Result; 152 | } 153 | 154 | /// 155 | /// Gets an entity by id. 156 | /// 157 | /// Filter to find a single item 158 | /// Entity 159 | public async Task GetByIdAsync(object id) 160 | { 161 | try 162 | { 163 | CloudBlockBlob block = container.GetBlockBlobReference(id.ToString()); 164 | await block.FetchAttributesAsync(); 165 | var blob = new Interfaces.BlobInfo(); 166 | blob.Id = block.Name; 167 | blob.Size = block.Properties.Length; 168 | blob.Uri = block.Uri; 169 | return blob; 170 | } 171 | catch 172 | { 173 | return null; 174 | } 175 | } 176 | 177 | /// 178 | /// Download blob payload 179 | /// 180 | /// Blob info entity 181 | /// Download stream 182 | public void Download(BlobInfo entity, Stream stream) 183 | { 184 | DownloadAsync(entity, stream).WaitSync(); 185 | } 186 | 187 | /// 188 | /// Download blob payload 189 | /// 190 | /// Blob info entity 191 | /// Download stream 192 | /// Task 193 | public async Task DownloadAsync(BlobInfo entity, Stream stream) 194 | { 195 | if (entity != null) 196 | { 197 | CloudBlockBlob block = container.GetBlockBlobReference(entity.Id); 198 | if (stream != null) 199 | { 200 | await block.DownloadToStreamAsync(stream); 201 | } 202 | } 203 | } 204 | 205 | /// 206 | /// Upload blob 207 | /// 208 | /// Blob info entity 209 | /// Upload stream 210 | public void Upload(BlobInfo entity, Stream stream) 211 | { 212 | UploadAsync(entity, stream).WaitSync(); 213 | } 214 | 215 | /// 216 | /// Upload blob 217 | /// 218 | /// Blob info entity 219 | /// Upload stream 220 | /// Task 221 | public async Task UploadAsync(BlobInfo entity, Stream stream) 222 | { 223 | CloudBlockBlob block = container.GetBlockBlobReference(entity.Id); 224 | await block.UploadFromStreamAsync(stream); 225 | await block.FetchAttributesAsync(); 226 | entity.Size = block.Properties.Length; 227 | entity.Uri = block.Uri; 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/CategoryRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit; 4 | using RepositoryFramework.Test.Models; 5 | using System; 6 | using RepositoryFramework.Test.Filters; 7 | using Microsoft.Data.Sqlite; 8 | using System.Data; 9 | 10 | namespace RepositoryFramework.Dapper.Test 11 | { 12 | public class CategoryRepositoryTest 13 | { 14 | private void InitializeDatabase(SqliteConnection connection) 15 | { 16 | if (connection.State != ConnectionState.Open) 17 | { 18 | connection.Open(); 19 | } 20 | 21 | var c = new SqliteCommand(@" 22 | CREATE TABLE Category 23 | ( 24 | Id INTEGER PRIMARY KEY, 25 | Name NVARCHAR(100), 26 | NullField NVARCHAR(100) DEFAULT NULL, 27 | DateTimeField DATETIME, 28 | Description NVARCHAR(100) 29 | ); 30 | 31 | CREATE TABLE Product 32 | ( 33 | Id INTEGER PRIMARY KEY, 34 | Name NVARCHAR(100), 35 | Description NVARCHAR(100), 36 | Price FLOAT, 37 | CategoryId INTEGER 38 | )", connection); 39 | 40 | c.ExecuteNonQuery(); 41 | } 42 | 43 | [Fact] 44 | public void Create_And_GetById() 45 | { 46 | using (var connection = new SqliteConnection("Data Source=:memory:")) 47 | { 48 | InitializeDatabase(connection); 49 | 50 | // Arrange 51 | IDapperRepository categoryRepository = 52 | new CategoryRepository(connection, "SELECT last_insert_rowid()"); 53 | var category = new Category 54 | { 55 | Name = "Category 1", 56 | Products = new List 57 | { 58 | new Product { Name = "Product 1" }, 59 | new Product { Name = "Product 2" } 60 | } 61 | }; 62 | 63 | // Act 64 | categoryRepository.Create(category); 65 | 66 | // Assert 67 | var result = categoryRepository.GetById(category.Id.Value); 68 | Assert.NotNull(result); 69 | Assert.Equal(category.Id, result.Id); 70 | Assert.Equal(category.Name, result.Name); 71 | Assert.NotNull(result.Products); 72 | Assert.Equal(2, result.Products.Count); 73 | Assert.False(result.Products.Any(p => p.CategoryId != category.Id)); 74 | } 75 | } 76 | 77 | [Fact] 78 | public void Update() 79 | { 80 | using (var connection = new SqliteConnection("Data Source=:memory:")) 81 | { 82 | InitializeDatabase(connection); 83 | 84 | // Arrange 85 | IDapperRepository categoryRepository = 86 | new CategoryRepository(connection, "SELECT last_insert_rowid()"); 87 | var category = new Category 88 | { 89 | Name = Guid.NewGuid().ToString(), 90 | Description = Guid.NewGuid().ToString() 91 | }; 92 | 93 | // Act 94 | categoryRepository.Create(category); 95 | category.Name = "New Name"; 96 | category.Description = "New Description"; 97 | categoryRepository.Update(category); 98 | 99 | // Assert 100 | var result = categoryRepository.GetById(category.Id.Value); 101 | Assert.NotNull(result); 102 | Assert.Equal(category.Id, result.Id); 103 | Assert.Equal(category.Name, result.Name); 104 | Assert.Equal(category.Description, result.Description); 105 | } 106 | } 107 | 108 | [Fact] 109 | public void Delete() 110 | { 111 | using (var connection = new SqliteConnection("Data Source=:memory:")) 112 | { 113 | InitializeDatabase(connection); 114 | 115 | // Arrange 116 | IDapperRepository categoryRepository = 117 | new CategoryRepository(connection, "SELECT last_insert_rowid()"); 118 | var category = new Category 119 | { 120 | Name = Guid.NewGuid().ToString(), 121 | Description = Guid.NewGuid().ToString() 122 | }; 123 | 124 | // Act 125 | categoryRepository.Create(category); 126 | var result = categoryRepository.GetById(category.Id.Value); 127 | Assert.NotNull(result); 128 | categoryRepository.Delete(category); 129 | 130 | // Assert 131 | result = categoryRepository.GetById(category.Id.Value); 132 | Assert.Null(result); 133 | } 134 | } 135 | 136 | [Theory] 137 | [InlineData(100, 2)] 138 | [InlineData(100, 0)] 139 | public void Find(int categories, int products) 140 | { 141 | using (var connection = new SqliteConnection("Data Source=:memory:")) 142 | { 143 | InitializeDatabase(connection); 144 | 145 | // Arrange 146 | IDapperRepository categoryRepository = 147 | new CategoryRepository(connection, "SELECT last_insert_rowid()"); 148 | for (int i = 0; i < categories; i++) 149 | { 150 | var category = new Category 151 | { 152 | Name = i.ToString(), 153 | Description = i.ToString(), 154 | Products = CreateProducts(products) 155 | }; 156 | categoryRepository.Create(category); 157 | } 158 | 159 | // Act 160 | var result = categoryRepository.Find(); 161 | 162 | // Assert 163 | Assert.NotNull(result); 164 | Assert.Equal(categories, result.Count()); 165 | foreach (var category in result) 166 | { 167 | Assert.Equal(products, category.Products.Count); 168 | } 169 | } 170 | } 171 | 172 | private ICollection CreateProducts(int products) 173 | { 174 | var productList = new List(); 175 | for (var n = 0; n < products; n++) 176 | { 177 | productList.Add(new Product { Name = $"Product {n}" }); 178 | } 179 | return productList; 180 | } 181 | 182 | private void Include_AssertProduct(ICollection products, int expectedProductRows, int expectedPartRows) 183 | { 184 | if (products == null) 185 | { 186 | Assert.Equal(0, expectedProductRows); 187 | } 188 | else 189 | { 190 | Assert.Equal(expectedProductRows, products.Count); 191 | var product = products.First(); 192 | if (product.Parts == null) 193 | { 194 | Assert.Equal(0, expectedPartRows); 195 | } 196 | else 197 | { 198 | Assert.Equal(expectedPartRows, product.Parts.Count); 199 | } 200 | } 201 | } 202 | 203 | private Product CreateProduct(Category c, int partRows = 100) 204 | { 205 | var p = new Product 206 | { 207 | Name = Guid.NewGuid().ToString().ToLower().Replace("aa", "ab"), 208 | Category = c, 209 | Description = Guid.NewGuid().ToString(), 210 | Price = 1.23M 211 | }; 212 | var l = new List(); 213 | for (int i = 0; i < partRows; i++) 214 | { 215 | l.Add(CreatePart(p)); 216 | }; 217 | p.Parts = l; 218 | return p; 219 | } 220 | 221 | private Part CreatePart(Product p) 222 | { 223 | return new Part 224 | { 225 | Name = Guid.NewGuid().ToString().ToLower().Replace("aa", "ab"), 226 | Product = p, 227 | Description = Guid.NewGuid().ToString(), 228 | Price = 1.23M 229 | }; 230 | } 231 | 232 | private Category CreateCategory(int productRows = 100) 233 | { 234 | var c = new Category 235 | { 236 | Name = Guid.NewGuid().ToString(), 237 | Description = Guid.NewGuid().ToString() 238 | }; 239 | var products = new List(); 240 | for (int i = 0; i < productRows; i++) 241 | { 242 | products.Add(CreateProduct(c)); 243 | }; 244 | return c; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /RepositoryFramework.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B9EEDEBC-3C15-43C8-90EC-32193D87CB74}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\MyInstruments\FossAssureGrainAPI\Mainline\global.json = ..\MyInstruments\FossAssureGrainAPI\Mainline\global.json 9 | pack.ps1 = pack.ps1 10 | README.md = README.md 11 | RULES.ruleset = RULES.ruleset 12 | stylecop.json = stylecop.json 13 | EndProjectSection 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.EntityFramework", "RepositoryFramework.EntityFramework\RepositoryFramework.EntityFramework.csproj", "{66FB133D-E282-481D-BC7B-08C942F8A5CB}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.EntityFramework.Test", "RepositoryFramework.EntityFramework.Test\RepositoryFramework.EntityFramework.Test.csproj", "{A900FE5B-584E-4602-87EC-79529E6AE180}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Interfaces", "RepositoryFramework.Interfaces\RepositoryFramework.Interfaces.csproj", "{27C7C76D-677D-4241-BE70-D04E3442A75D}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Api", "RepositoryFramework.Api\RepositoryFramework.Api.csproj", "{B0A00CFF-E9A2-44C5-BF9D-003466F71975}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Api.Test", "RepositoryFramework.Api.Test\RepositoryFramework.Api.Test.csproj", "{1F3AF3A4-2C4A-4641-A32B-F18F90DB8579}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Dapper", "RepositoryFramework.Dapper\RepositoryFramework.Dapper.csproj", "{FAFDA019-9C49-435B-9BB1-51C4C0C756A6}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Dapper.Test", "RepositoryFramework.Dapper.Test\RepositoryFramework.Dapper.Test.csproj", "{2BB73B46-25C3-42A4-8F73-ED4C1C97FD02}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.MongoDB.Test", "RepositoryFramework.MongoDB.Test\RepositoryFramework.MongoDB.Test.csproj", "{91A064BE-B208-47AF-9B35-CC1A4E873C40}" 30 | EndProject 31 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.MongoDB", "RepositoryFramework.MongoDB\RepositoryFramework.MongoDB.csproj", "{5E82FF79-D884-408E-B2ED-B8A8F301FE55}" 32 | EndProject 33 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Azure.Blob", "RepositoryFramework.Azure.Blob\RepositoryFramework.Azure.Blob.csproj", "{7E103FDF-BA13-424D-BB18-0478E0C1C656}" 34 | EndProject 35 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.Azure.Blob.Test", "RepositoryFramework.Azure.Blob.Test\RepositoryFramework.Azure.Blob.Test.csproj", "{96FB9D0E-6D8C-4E8E-8B50-10A6A41C267C}" 36 | EndProject 37 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.AWS.S3", "RepositoryFramework.AWS.S3\RepositoryFramework.AWS.S3.csproj", "{95D9510F-17B5-4AD7-AF49-3970B40F24E1}" 38 | EndProject 39 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepositoryFramework.AWS.S3.Test", "RepositoryFramework.AWS.S3.Test\RepositoryFramework.AWS.S3.Test.csproj", "{CD6E4686-D46F-47F9-812D-7C6457431391}" 40 | EndProject 41 | Global 42 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 43 | Debug|Any CPU = Debug|Any CPU 44 | Release|Any CPU = Release|Any CPU 45 | EndGlobalSection 46 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 47 | {66FB133D-E282-481D-BC7B-08C942F8A5CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {66FB133D-E282-481D-BC7B-08C942F8A5CB}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {66FB133D-E282-481D-BC7B-08C942F8A5CB}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {66FB133D-E282-481D-BC7B-08C942F8A5CB}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {A900FE5B-584E-4602-87EC-79529E6AE180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {A900FE5B-584E-4602-87EC-79529E6AE180}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {A900FE5B-584E-4602-87EC-79529E6AE180}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {A900FE5B-584E-4602-87EC-79529E6AE180}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {27C7C76D-677D-4241-BE70-D04E3442A75D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {27C7C76D-677D-4241-BE70-D04E3442A75D}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {27C7C76D-677D-4241-BE70-D04E3442A75D}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {27C7C76D-677D-4241-BE70-D04E3442A75D}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {B0A00CFF-E9A2-44C5-BF9D-003466F71975}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {B0A00CFF-E9A2-44C5-BF9D-003466F71975}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {B0A00CFF-E9A2-44C5-BF9D-003466F71975}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {B0A00CFF-E9A2-44C5-BF9D-003466F71975}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {1F3AF3A4-2C4A-4641-A32B-F18F90DB8579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {1F3AF3A4-2C4A-4641-A32B-F18F90DB8579}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {1F3AF3A4-2C4A-4641-A32B-F18F90DB8579}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {1F3AF3A4-2C4A-4641-A32B-F18F90DB8579}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {FAFDA019-9C49-435B-9BB1-51C4C0C756A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {FAFDA019-9C49-435B-9BB1-51C4C0C756A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {FAFDA019-9C49-435B-9BB1-51C4C0C756A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {FAFDA019-9C49-435B-9BB1-51C4C0C756A6}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {2BB73B46-25C3-42A4-8F73-ED4C1C97FD02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {2BB73B46-25C3-42A4-8F73-ED4C1C97FD02}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {2BB73B46-25C3-42A4-8F73-ED4C1C97FD02}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {2BB73B46-25C3-42A4-8F73-ED4C1C97FD02}.Release|Any CPU.Build.0 = Release|Any CPU 75 | {91A064BE-B208-47AF-9B35-CC1A4E873C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 76 | {91A064BE-B208-47AF-9B35-CC1A4E873C40}.Debug|Any CPU.Build.0 = Debug|Any CPU 77 | {91A064BE-B208-47AF-9B35-CC1A4E873C40}.Release|Any CPU.ActiveCfg = Release|Any CPU 78 | {91A064BE-B208-47AF-9B35-CC1A4E873C40}.Release|Any CPU.Build.0 = Release|Any CPU 79 | {5E82FF79-D884-408E-B2ED-B8A8F301FE55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 80 | {5E82FF79-D884-408E-B2ED-B8A8F301FE55}.Debug|Any CPU.Build.0 = Debug|Any CPU 81 | {5E82FF79-D884-408E-B2ED-B8A8F301FE55}.Release|Any CPU.ActiveCfg = Release|Any CPU 82 | {5E82FF79-D884-408E-B2ED-B8A8F301FE55}.Release|Any CPU.Build.0 = Release|Any CPU 83 | {7E103FDF-BA13-424D-BB18-0478E0C1C656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 84 | {7E103FDF-BA13-424D-BB18-0478E0C1C656}.Debug|Any CPU.Build.0 = Debug|Any CPU 85 | {7E103FDF-BA13-424D-BB18-0478E0C1C656}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {7E103FDF-BA13-424D-BB18-0478E0C1C656}.Release|Any CPU.Build.0 = Release|Any CPU 87 | {96FB9D0E-6D8C-4E8E-8B50-10A6A41C267C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 88 | {96FB9D0E-6D8C-4E8E-8B50-10A6A41C267C}.Debug|Any CPU.Build.0 = Debug|Any CPU 89 | {96FB9D0E-6D8C-4E8E-8B50-10A6A41C267C}.Release|Any CPU.ActiveCfg = Release|Any CPU 90 | {96FB9D0E-6D8C-4E8E-8B50-10A6A41C267C}.Release|Any CPU.Build.0 = Release|Any CPU 91 | {95D9510F-17B5-4AD7-AF49-3970B40F24E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 92 | {95D9510F-17B5-4AD7-AF49-3970B40F24E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 93 | {95D9510F-17B5-4AD7-AF49-3970B40F24E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 94 | {95D9510F-17B5-4AD7-AF49-3970B40F24E1}.Release|Any CPU.Build.0 = Release|Any CPU 95 | {CD6E4686-D46F-47F9-812D-7C6457431391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 96 | {CD6E4686-D46F-47F9-812D-7C6457431391}.Debug|Any CPU.Build.0 = Debug|Any CPU 97 | {CD6E4686-D46F-47F9-812D-7C6457431391}.Release|Any CPU.ActiveCfg = Release|Any CPU 98 | {CD6E4686-D46F-47F9-812D-7C6457431391}.Release|Any CPU.Build.0 = Release|Any CPU 99 | EndGlobalSection 100 | GlobalSection(SolutionProperties) = preSolution 101 | HideSolutionNode = FALSE 102 | EndGlobalSection 103 | GlobalSection(ExtensibilityGlobals) = postSolution 104 | SolutionGuid = {2BCF8544-3E6C-4D48-A16F-6636701529CF} 105 | EndGlobalSection 106 | EndGlobal 107 | -------------------------------------------------------------------------------- /RepositoryFramework.AWS.S3/AWSS3Repository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Amazon.S3; 6 | using Amazon.S3.Model; 7 | using RepositoryFramework.Interfaces; 8 | 9 | namespace RepositoryFramework.AWS.S3 10 | { 11 | /// 12 | /// Amazon Web Services (AWS) Simple Storage Service (S3) repository 13 | /// 14 | /// 15 | /// To use this repository you must have a valid AWS account 16 | /// To configure AWS credentials see http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html 17 | /// To configure the Amazon S3 client in .Net Core see http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-netcore.html 18 | /// 19 | public class AWSS3Repository : IBlobRepository 20 | { 21 | private IAmazonS3 s3client = null; 22 | 23 | private string bucketName = null; 24 | 25 | private string bucketLocation = string.Empty; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// AWS S3 client 31 | /// Bucket name 32 | public AWSS3Repository( 33 | IAmazonS3 s3client, 34 | string bucketName) 35 | { 36 | this.s3client = s3client; 37 | this.bucketName = bucketName; 38 | var getBucketLocationTask = 39 | s3client.GetBucketLocationAsync(new GetBucketLocationRequest 40 | { 41 | BucketName = this.bucketName 42 | }); 43 | var doesS3BucketExistTask = s3client.DoesS3BucketExistAsync(this.bucketName); 44 | doesS3BucketExistTask.Wait(); 45 | if (!doesS3BucketExistTask.Result) 46 | { 47 | // Note that CreateBucketRequest does not specify region. So bucket is 48 | // created in the region specified in the client. 49 | s3client.PutBucketAsync(bucketName).Wait(); 50 | } 51 | 52 | getBucketLocationTask.Wait(); 53 | if (getBucketLocationTask.Result != null) 54 | { 55 | bucketLocation = getBucketLocationTask.Result.Location; 56 | } 57 | } 58 | 59 | /// 60 | /// Delete an existing entity 61 | /// 62 | /// Entity 63 | public void Delete(BlobInfo entity) 64 | { 65 | DeleteAsync(entity).WaitSync(); 66 | } 67 | 68 | /// 69 | /// Delete an existing entity 70 | /// 71 | /// Entity 72 | /// Task 73 | public async Task DeleteAsync(BlobInfo entity) 74 | { 75 | DeleteObjectRequest deleteObjectRequest = 76 | new DeleteObjectRequest 77 | { 78 | BucketName = bucketName, 79 | Key = entity.Id 80 | }; 81 | 82 | await s3client.DeleteObjectAsync(deleteObjectRequest); 83 | } 84 | 85 | /// 86 | /// Delete a list of existing entities 87 | /// 88 | /// Entity list 89 | public void DeleteMany(IEnumerable entities) 90 | { 91 | DeleteManyAsync(entities).WaitSync(); 92 | } 93 | 94 | /// 95 | /// Delete a list of existing entities 96 | /// 97 | /// Entity list 98 | /// Task 99 | public async Task DeleteManyAsync(IEnumerable entities) 100 | { 101 | DeleteObjectsRequest multiObjectDeleteRequest = new DeleteObjectsRequest(); 102 | multiObjectDeleteRequest.BucketName = bucketName; 103 | 104 | foreach (var entity in entities) 105 | { 106 | multiObjectDeleteRequest.AddKey(entity.Id, null); 107 | } 108 | 109 | DeleteObjectsResponse response = await s3client.DeleteObjectsAsync(multiObjectDeleteRequest); 110 | } 111 | 112 | /// 113 | /// Gets an entity by id. 114 | /// 115 | /// Filter to find a single item 116 | /// Entity 117 | public BlobInfo GetById(object id) 118 | { 119 | var t = GetByIdAsync(id); 120 | t.WaitSync(); 121 | return t.Result; 122 | } 123 | 124 | /// 125 | /// Gets an entity by id. 126 | /// 127 | /// Filter to find a single item 128 | /// Entity 129 | public async Task GetByIdAsync(object id) 130 | { 131 | var blob = new BlobInfo( 132 | id.ToString(), 133 | -1, 134 | new Uri($"http://{bucketName}.s3-{bucketLocation}.amazonaws.com/{id}")); 135 | 136 | var request = new GetObjectRequest 137 | { 138 | BucketName = bucketName, 139 | Key = id.ToString(), 140 | }; 141 | 142 | try 143 | { 144 | GetObjectResponse response = await s3client.GetObjectAsync(request); 145 | blob.Size = response.ContentLength; 146 | } 147 | catch 148 | { 149 | return null; 150 | } 151 | 152 | return blob; 153 | } 154 | 155 | /// 156 | /// Get a list of entities 157 | /// 158 | /// Query result 159 | public IEnumerable Find() 160 | { 161 | var t = FindAsync(null); 162 | t.WaitSync(); 163 | return t.Result; 164 | } 165 | 166 | /// 167 | /// Get a list of entities 168 | /// 169 | /// Query result 170 | public async Task> FindAsync() 171 | { 172 | return await FindAsync(null); 173 | } 174 | 175 | /// 176 | /// Filters a collection of entities from a filter definition 177 | /// 178 | /// Filter objects by prefix 179 | /// Filtered collection of entities 180 | public IEnumerable Find(string prefix) 181 | { 182 | var t = FindAsync(prefix); 183 | t.WaitSync(); 184 | return t.Result; 185 | } 186 | 187 | /// 188 | /// Filters a collection of entities from a filter definition 189 | /// 190 | /// Filter objects by prefix 191 | /// Filtered collection of entities 192 | public async Task> FindAsync(string prefix) 193 | { 194 | ListObjectsRequest request = new ListObjectsRequest 195 | { 196 | BucketName = bucketName, 197 | MaxKeys = 2, 198 | Prefix = prefix 199 | }; 200 | var blobs = new List(); 201 | 202 | do 203 | { 204 | ListObjectsResponse response = await s3client.ListObjectsAsync(request); 205 | 206 | // Process response. 207 | foreach (S3Object entry in response.S3Objects) 208 | { 209 | blobs.Add(new BlobInfo( 210 | entry.Key, 211 | entry.Size, 212 | new Uri($"http://{bucketName}.s3-{bucketLocation}.amazonaws.com/{entry.Key}"))); 213 | } 214 | 215 | // If response is truncated, set the marker to get the next 216 | // set of keys. 217 | if (response.IsTruncated) 218 | { 219 | request.Marker = response.NextMarker; 220 | } 221 | else 222 | { 223 | request = null; 224 | } 225 | } 226 | while (request != null); 227 | 228 | return blobs; 229 | } 230 | 231 | /// 232 | /// Upload blob 233 | /// 234 | /// Blob info entity 235 | /// Upload stream 236 | public void Upload(BlobInfo entity, Stream stream) 237 | { 238 | UploadAsync(entity, stream).WaitSync(); 239 | } 240 | 241 | /// 242 | /// Upload blob 243 | /// 244 | /// Blob info entity 245 | /// Upload stream 246 | /// Task 247 | public async Task UploadAsync(BlobInfo entity, Stream stream) 248 | { 249 | PutObjectRequest request = new PutObjectRequest() 250 | { 251 | BucketName = bucketName, 252 | Key = entity.Id, 253 | InputStream = stream 254 | }; 255 | await s3client.PutObjectAsync(request); 256 | entity.Uri = new Uri($"http://{bucketName}.s3-{bucketLocation}.amazonaws.com/{entity.Id}"); 257 | } 258 | 259 | /// 260 | /// Download blob payload 261 | /// 262 | /// Blob info entity 263 | /// Download stream 264 | public void Download(BlobInfo entity, Stream stream) 265 | { 266 | DownloadAsync(entity, stream).WaitSync(); 267 | } 268 | 269 | /// 270 | /// Download blob payload 271 | /// 272 | /// Blob info entity 273 | /// Download stream 274 | /// Task 275 | public async Task DownloadAsync(BlobInfo entity, Stream stream) 276 | { 277 | var request = new GetObjectRequest 278 | { 279 | BucketName = bucketName, 280 | Key = entity.Id.ToString(), 281 | }; 282 | 283 | GetObjectResponse response = await s3client.GetObjectAsync(request); 284 | if (response.ResponseStream != null) 285 | { 286 | entity.Size = response.ResponseStream.Length; 287 | response.ResponseStream.CopyTo(stream); 288 | } 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper/StoredProcedureDapperRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Dapper; 9 | using RepositoryFramework.Interfaces; 10 | 11 | namespace RepositoryFramework.Dapper 12 | { 13 | /// 14 | /// A Dapper repository using stored procedures 15 | /// 16 | /// Entity type 17 | public class StoredProcedureDapperRepository : 18 | GenericRepositoryBase, 19 | IStoredProcedureDapperRepository 20 | where TEntity : class 21 | { 22 | /// 23 | /// Initializes a new instance of the class 24 | /// 25 | /// Database connection 26 | /// Id property expression 27 | public StoredProcedureDapperRepository( 28 | IDbConnection connection, 29 | Expression> idProperty = null) 30 | : base(idProperty) 31 | { 32 | Connection = connection; 33 | } 34 | 35 | /// 36 | /// Gets parameters 37 | /// 38 | public IDictionary Parameters { get; private set; } = new Dictionary(); 39 | 40 | /// 41 | /// Gets database connection 42 | /// 43 | protected virtual IDbConnection Connection { get; private set; } 44 | 45 | /// 46 | /// Clear parameters 47 | /// 48 | /// Current instance 49 | IParameterizedRepository IParameterizedRepository.ClearParameters() 50 | { 51 | return ClearParameters(); 52 | } 53 | 54 | /// 55 | /// Clear parameters 56 | /// 57 | /// Current instance 58 | public IStoredProcedureDapperRepository ClearParameters() 59 | { 60 | Parameters.Clear(); 61 | return this; 62 | } 63 | 64 | /// 65 | /// Create a new entity 66 | /// 67 | /// Entity 68 | public virtual void Create(TEntity entity) 69 | { 70 | CreateAsync(entity).WaitSync(); 71 | } 72 | 73 | /// 74 | /// Create a new entity 75 | /// 76 | /// Entity 77 | /// Task 78 | public virtual async Task CreateAsync(TEntity entity) 79 | { 80 | if (Connection.State != ConnectionState.Open) 81 | { 82 | Connection.Open(); 83 | } 84 | 85 | var parameters = EntityColumns 86 | .Where(c => c != IdPropertyName) 87 | .Select(c => $"@{c}=@{c}").ToList(); 88 | 89 | var createCommand = $"EXEC Create{EntityTypeName} {string.Join(",", parameters)}"; 90 | 91 | IEnumerable result = await Connection.QueryAsync( 92 | createCommand, 93 | entity); 94 | 95 | EntityType.GetProperty(IdPropertyName)? 96 | .SetValue(entity, result.First()); 97 | } 98 | 99 | /// 100 | /// Create a list of new entities 101 | /// 102 | /// List of entities 103 | public virtual void CreateMany(IEnumerable entities) 104 | { 105 | CreateManyAsync(entities).WaitSync(); 106 | } 107 | 108 | /// 109 | /// Create a list of new entities 110 | /// 111 | /// List of entities 112 | /// Task 113 | public virtual async Task CreateManyAsync(IEnumerable entities) 114 | { 115 | if (Connection.State != ConnectionState.Open) 116 | { 117 | Connection.Open(); 118 | } 119 | 120 | var parameters = EntityColumns 121 | .Where(c => c != IdPropertyName) 122 | .Select(c => "@" + c).ToList(); 123 | 124 | await Connection.ExecuteAsync( 125 | $"EXEC Create{EntityTypeName} {string.Join(",", parameters)}", 126 | entities); 127 | } 128 | 129 | /// 130 | /// Delete an existing entity 131 | /// 132 | /// Entity 133 | public virtual void Delete(TEntity entity) 134 | { 135 | DeleteAsync(entity).WaitSync(); 136 | } 137 | 138 | /// 139 | /// Delete an existing entity 140 | /// 141 | /// Entity 142 | /// Task 143 | public virtual async Task DeleteAsync(TEntity entity) 144 | { 145 | if (Connection.State != ConnectionState.Open) 146 | { 147 | Connection.Open(); 148 | } 149 | 150 | await Connection.ExecuteAsync( 151 | $"EXEC Delete{EntityTypeName} @{IdPropertyName}", 152 | entity); 153 | } 154 | 155 | /// 156 | /// Delete a list of existing entities 157 | /// 158 | /// Entity list 159 | public virtual void DeleteMany(IEnumerable entities) 160 | { 161 | DeleteManyAsync(entities).WaitSync(); 162 | } 163 | 164 | /// 165 | /// Delete a list of existing entities 166 | /// 167 | /// Entity list 168 | /// Task 169 | public virtual async Task DeleteManyAsync(IEnumerable entities) 170 | { 171 | if (Connection.State != ConnectionState.Open) 172 | { 173 | Connection.Open(); 174 | } 175 | 176 | await Connection.ExecuteAsync( 177 | $@"EXEC Delete{EntityTypeName} @{IdPropertyName}", 178 | entities); 179 | } 180 | 181 | /// 182 | /// Update an existing entity 183 | /// 184 | /// Entity 185 | public virtual void Update(TEntity entity) 186 | { 187 | UpdateAsync(entity).WaitSync(); 188 | } 189 | 190 | /// 191 | /// Update an existing entity 192 | /// 193 | /// Entity 194 | /// Task 195 | public virtual async Task UpdateAsync(TEntity entity) 196 | { 197 | if (Connection.State != ConnectionState.Open) 198 | { 199 | Connection.Open(); 200 | } 201 | 202 | var parameters = EntityColumns.Select(name => $"@{name}=@{name}").ToList(); 203 | 204 | await Connection.ExecuteAsync( 205 | $"EXEC Update{EntityTypeName} {string.Join(",", parameters)}", 206 | entity); 207 | } 208 | 209 | /// 210 | /// Gets an entity by id. 211 | /// 212 | /// Filter 213 | /// Entity 214 | public virtual TEntity GetById(object id) 215 | { 216 | var task = GetByIdAsync(id); 217 | task.WaitSync(); 218 | return task.Result; 219 | } 220 | 221 | /// 222 | /// Gets an entity by id. 223 | /// 224 | /// Filter to find a single item 225 | /// Entity 226 | public virtual async Task GetByIdAsync(object id) 227 | { 228 | if (Connection.State != ConnectionState.Open) 229 | { 230 | Connection.Open(); 231 | } 232 | 233 | IEnumerable result = await Connection.QueryAsync( 234 | $"EXEC Get{EntityTypeName} @{IdPropertyName}", 235 | new { Id = id }); 236 | 237 | return result.FirstOrDefault(); 238 | } 239 | 240 | /// 241 | /// Get a list of entities 242 | /// 243 | /// Query result 244 | public virtual IEnumerable Find() 245 | { 246 | var task = FindAsync(); 247 | task.WaitSync(); 248 | return task.Result; 249 | } 250 | 251 | /// 252 | /// Get a list of entities 253 | /// 254 | /// Query result 255 | public virtual async Task> FindAsync() 256 | { 257 | if (Connection.State != ConnectionState.Open) 258 | { 259 | Connection.Open(); 260 | } 261 | 262 | var parameters = Parameters.Keys 263 | .Select(key => $"@{key}=@{key}"); 264 | 265 | return await Connection.QueryAsync( 266 | $"EXEC Find{EntityTypeName} {string.Join(",", parameters)}", 267 | ToObject(Parameters)); 268 | } 269 | 270 | /// 271 | /// Gets parameter value 272 | /// 273 | /// Parameter name 274 | /// Parameter value 275 | public object GetParameter(string name) 276 | { 277 | return Parameters[name]; 278 | } 279 | 280 | /// 281 | /// Adds a parameter to queries 282 | /// 283 | /// Parameter name 284 | /// Parameter value 285 | /// Current instance 286 | IParameterizedRepository IParameterizedRepository.SetParameter(string name, object value) 287 | { 288 | return SetParameter(name, value); 289 | } 290 | 291 | /// 292 | /// Adds a parameter to queries 293 | /// 294 | /// Parameter name 295 | /// Parameter value 296 | /// Current instance 297 | public IStoredProcedureDapperRepository SetParameter(string name, object value) 298 | { 299 | if (!Parameters.Keys.Contains(name)) 300 | { 301 | Parameters.Add(name, value); 302 | } 303 | else 304 | { 305 | Parameters[name] = value; 306 | } 307 | 308 | return this; 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /RepositoryFramework.Dapper.Test/StoredProcedureRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit; 4 | using RepositoryFramework.Test.Models; 5 | using System; 6 | using RepositoryFramework.Test.Filters; 7 | using System.Data; 8 | using System.Data.SqlClient; 9 | using System.Text.RegularExpressions; 10 | using RepositoryFramework.Interfaces; 11 | 12 | namespace RepositoryFramework.Dapper.Test 13 | { 14 | public class StoredProcedureRepositoryTest 15 | { 16 | [Theory] 17 | [InlineData(100, "Name", "Name 1", 1)] 18 | [InlineData(100, "Description", "Description 1", 10)] 19 | public void FindParameter(int categories, string parameterName, object parameterValue, int expectedRows) 20 | { 21 | using (var connection = CreateConnection()) 22 | { 23 | InitializeDatabase(connection, "RepositoryTest_FindFilter"); 24 | 25 | // Arrange 26 | IParameterizedRepository categoryRepository = CreateCategoryRepository(connection); 27 | for (int i = 0; i < categories; i++) 28 | { 29 | var category = new Category 30 | { 31 | Name = $"Name {i}", 32 | Description = $"Description {i % 10}" 33 | }; 34 | categoryRepository.Create(category); 35 | } 36 | 37 | // Act 38 | var result = categoryRepository 39 | .SetParameter(parameterName, parameterValue) 40 | .Find(); 41 | 42 | // Assert 43 | Assert.NotNull(result); 44 | Assert.Equal(expectedRows, result.Count()); 45 | } 46 | } 47 | 48 | [Fact] 49 | public void GetById() 50 | { 51 | using (var connection = CreateConnection()) 52 | { 53 | InitializeDatabase(connection, "RepositoryTest_Create_And_GetById"); 54 | 55 | // Arrange 56 | var categoryRepository = CreateCategoryRepository(connection); 57 | var category = new Category 58 | { 59 | Name = Guid.NewGuid().ToString(), 60 | Description = Guid.NewGuid().ToString() 61 | }; 62 | 63 | // Act 64 | categoryRepository.Create(category); 65 | 66 | // Assert 67 | var result = categoryRepository.GetById(category.Id.Value); 68 | Assert.NotNull(result); 69 | Assert.Equal(category.Id, result.Id); 70 | Assert.Equal(category.Name, result.Name); 71 | Assert.Equal(category.Description, result.Description); 72 | } 73 | } 74 | 75 | [Fact] 76 | public void CreateMany() 77 | { 78 | using (var connection = CreateConnection()) 79 | { 80 | InitializeDatabase(connection, "RepositoryTest_Delete"); 81 | 82 | // Arrange 83 | var totalCategories = 100; 84 | var createThese = new List(); 85 | var categoryRepository = CreateCategoryRepository(connection); 86 | for (int i = 0; i < totalCategories; i++) 87 | { 88 | var category = new Category 89 | { 90 | Name = i.ToString(), 91 | Description = i.ToString() 92 | }; 93 | createThese.Add(category); 94 | } 95 | 96 | // Act 97 | categoryRepository.CreateMany(createThese); 98 | 99 | // Assert 100 | var result = categoryRepository.Find(); 101 | Assert.NotNull(result); 102 | Assert.Equal(totalCategories, result.Count()); 103 | } 104 | } 105 | 106 | [Fact] 107 | public void Update() 108 | { 109 | using (var connection = CreateConnection()) 110 | { 111 | InitializeDatabase(connection, "RepositoryTest_Update"); 112 | 113 | // Arrange 114 | var categoryRepository = CreateCategoryRepository(connection); 115 | var category = new Category 116 | { 117 | Name = Guid.NewGuid().ToString(), 118 | Description = Guid.NewGuid().ToString() 119 | }; 120 | 121 | // Act 122 | categoryRepository.Create(category); 123 | category.Name = "New Name"; 124 | category.Description = "New Description"; 125 | categoryRepository.Update(category); 126 | 127 | // Assert 128 | var result = categoryRepository.GetById(category.Id.Value); 129 | Assert.NotNull(result); 130 | Assert.Equal(category.Id, result.Id); 131 | Assert.Equal(category.Name, result.Name); 132 | Assert.Equal(category.Description, result.Description); 133 | } 134 | } 135 | 136 | [Fact] 137 | public void Delete() 138 | { 139 | using (var connection = CreateConnection()) 140 | { 141 | InitializeDatabase(connection, "RepositoryTest_Delete"); 142 | 143 | // Arrange 144 | var categoryRepository = CreateCategoryRepository(connection); 145 | var category = new Category 146 | { 147 | Name = Guid.NewGuid().ToString(), 148 | Description = Guid.NewGuid().ToString() 149 | }; 150 | 151 | // Act 152 | categoryRepository.Create(category); 153 | var result = categoryRepository.GetById(category.Id.Value); 154 | Assert.NotNull(result); 155 | categoryRepository.Delete(category); 156 | 157 | // Assert 158 | result = categoryRepository.GetById(category.Id.Value); 159 | Assert.Null(result); 160 | } 161 | } 162 | 163 | [Fact] 164 | public void DeleteMany() 165 | { 166 | using (var connection = CreateConnection()) 167 | { 168 | InitializeDatabase(connection, "RepositoryTest_Delete"); 169 | 170 | // Arrange 171 | var totalCategories = 100; 172 | var deleteThese = new List(); 173 | var categoryRepository = CreateCategoryRepository(connection); 174 | for (int i = 0; i < totalCategories; i++) 175 | { 176 | var category = new Category 177 | { 178 | Name = i.ToString(), 179 | Description = i.ToString() 180 | }; 181 | categoryRepository.Create(category); 182 | if (i % 2 == 0) 183 | { 184 | deleteThese.Add(category); 185 | } 186 | } 187 | 188 | // Act 189 | categoryRepository.DeleteMany(deleteThese); 190 | 191 | // Assert 192 | var result = categoryRepository.Find(); 193 | Assert.NotNull(result); 194 | Assert.Equal(totalCategories / 2, result.Count()); 195 | } 196 | } 197 | 198 | [Theory] 199 | [InlineData(100)] 200 | public void Find(int categories) 201 | { 202 | using (var connection = CreateConnection()) 203 | { 204 | InitializeDatabase(connection, "RepositoryTest_Find"); 205 | 206 | // Arrange 207 | var categoryRepository = CreateCategoryRepository(connection); 208 | for (int i = 0; i < categories; i++) 209 | { 210 | var category = new Category 211 | { 212 | Name = i.ToString(), 213 | Description = i.ToString() 214 | }; 215 | categoryRepository.Create(category); 216 | } 217 | 218 | // Act 219 | var result = categoryRepository.Find(); 220 | 221 | // Assert 222 | Assert.NotNull(result); 223 | Assert.Equal(categories, result.Count()); 224 | } 225 | } 226 | private IStoredProcedureDapperRepository CreateCategoryRepository(IDbConnection connection) 227 | { 228 | return new StoredProcedureDapperRepository(connection); 229 | } 230 | private IDbConnection CreateConnection() 231 | { 232 | return new SqlConnection(@"Server=(LocalDb)\MSSQLLocalDB;Database=master;Trusted_Connection=True;"); 233 | } 234 | 235 | private static void RunScript(IDbConnection connection, string script) 236 | { 237 | Regex regex = new Regex(@"\r{0,1}\nGO\r{0,1}\n"); 238 | string[] commands = regex.Split(script); 239 | 240 | for (int i = 0; i < commands.Length; i++) 241 | { 242 | if (commands[i] != string.Empty) 243 | { 244 | using (var command = connection.CreateCommand()) 245 | { 246 | command.CommandText = commands[i]; 247 | command.ExecuteNonQuery(); 248 | } 249 | } 250 | } 251 | } 252 | 253 | private void InitializeDatabase(IDbConnection connection, string database) 254 | { 255 | if (connection.State != ConnectionState.Open) 256 | { 257 | connection.Open(); 258 | } 259 | 260 | var script = $@" 261 | USE master 262 | IF EXISTS(SELECT * FROM sys.databases WHERE NAME='{database}') 263 | DROP DATABASE [{database}] 264 | GO 265 | 266 | CREATE DATABASE [{database}] 267 | GO 268 | 269 | USE [{database}] 270 | IF OBJECT_ID ('Category') IS NOT NULL 271 | DROP TABLE Category 272 | GO 273 | 274 | CREATE TABLE Category ( 275 | Id INTEGER IDENTITY, 276 | Name NVARCHAR(100), 277 | NullField NVARCHAR(100) DEFAULT NULL, 278 | DateTimeField DATETIME, 279 | Description NVARCHAR(100) 280 | ) 281 | GO 282 | 283 | IF OBJECT_ID ('CreateCategory') IS NOT NULL 284 | DROP PROCEDURE CreateCategory 285 | GO 286 | 287 | CREATE PROCEDURE CreateCategory 288 | @Name NVARCHAR(100) = NULL, 289 | @NullField NVARCHAR(100) = NULL, 290 | @DateTimeField DATETIME = NULL, 291 | @Description NVARCHAR(100) = NULL 292 | AS 293 | BEGIN 294 | INSERT INTO Category (Name, NullField, DateTimeField, Description) 295 | VALUES(@Name, @NullField, @DateTimeField, @Description) 296 | 297 | SELECT @@IDENTITY 298 | END 299 | GO 300 | 301 | IF OBJECT_ID ('GetCategory') IS NOT NULL 302 | DROP PROCEDURE GetCategory 303 | GO 304 | 305 | CREATE PROCEDURE GetCategory 306 | @Id INTEGER 307 | AS 308 | BEGIN 309 | SELECT * 310 | FROM Category 311 | WHERE Id = @Id 312 | END 313 | GO 314 | 315 | IF OBJECT_ID ('FindCategory') IS NOT NULL 316 | DROP PROCEDURE FindCategory 317 | GO 318 | 319 | CREATE PROCEDURE FindCategory 320 | @Name NVARCHAR(100) = NULL, 321 | @Description NVARCHAR(100) = NULL 322 | AS 323 | BEGIN 324 | SELECT * 325 | FROM Category 326 | WHERE (Name = @Name OR @Name IS NULL) 327 | AND (Description = @Description OR @Description IS NULL) 328 | END 329 | GO 330 | 331 | IF OBJECT_ID ('DeleteCategory') IS NOT NULL 332 | DROP PROCEDURE DeleteCategory 333 | GO 334 | 335 | CREATE PROCEDURE DeleteCategory 336 | @Id INTEGER 337 | AS 338 | BEGIN 339 | DELETE 340 | FROM Category 341 | WHERE Id = @Id 342 | END 343 | GO 344 | 345 | IF OBJECT_ID ('UpdateCategory') IS NOT NULL 346 | DROP PROCEDURE UpdateCategory 347 | GO 348 | 349 | CREATE PROCEDURE UpdateCategory 350 | @Id INTEGER, 351 | @Name NVARCHAR(100), 352 | @NullField NVARCHAR(100), 353 | @DateTimeField DATETIME, 354 | @Description NVARCHAR(100) 355 | AS 356 | BEGIN 357 | UPDATE Category 358 | SET Name = @Name, 359 | Description = @Description, 360 | NullField = @NullField, 361 | DateTimeField = @DateTimeField 362 | WHERE Id = @Id 363 | END 364 | "; 365 | 366 | RunScript(connection, script); 367 | } 368 | 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /RepositoryFramework.Api.Test/ApiRepositoryTest.cs: -------------------------------------------------------------------------------- 1 | namespace RepositoryFramework.Api.Test 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using RepositoryFramework.Interfaces; 9 | using Xunit; 10 | 11 | public class ApiRepositoryTest 12 | { 13 | private ApiConfiguration configuration = 14 | new ApiConfiguration 15 | { 16 | AuthenticationType = AuthenticationType.Anonymous 17 | }; 18 | 19 | [Fact] 20 | public async Task Find_Parameter() 21 | { 22 | await Find_Parameter_Test( 23 | async (r) => 24 | { 25 | return await Task.Run(() => r.Find()); 26 | }); 27 | await Find_Parameter_Test( 28 | async (r) => 29 | { 30 | return await r.FindAsync(); 31 | }); 32 | } 33 | 34 | protected virtual async Task Find_Parameter_Test( 35 | Func, Task>> find) 36 | { 37 | // Arrange 38 | IParameterizedRepository apiRepository = new ApiRepository( 39 | configuration, 40 | "https://jsonplaceholder.typicode.com", 41 | "posts") 42 | .SetParameter("UserId", 1); 43 | var result = await find(apiRepository); 44 | 45 | // Act 46 | Assert.True( 47 | result.Count() > 0, 48 | JsonConvert.SerializeObject(result, Formatting.Indented)); 49 | 50 | // Assert 51 | Assert.DoesNotContain(result, p => p.UserId != 1); 52 | } 53 | 54 | [Theory] 55 | [InlineData(2)] 56 | public async Task Find_SubEntities(int postId) 57 | { 58 | await Find_SubEntities_Test(postId, 59 | async (r) => 60 | { 61 | return await Task.Run(() => r.Find()); 62 | }); 63 | await Find_SubEntities_Test(postId, 64 | async (r) => 65 | { 66 | return await r.FindAsync(); 67 | }); 68 | } 69 | 70 | protected virtual async Task Find_SubEntities_Test(int postId, 71 | Func, Task>> find) 72 | { 73 | // Arrange 74 | IApiRepository apiRepository = new ApiRepository( 75 | configuration, 76 | "https://jsonplaceholder.typicode.com", 77 | "posts/{postId}/comments") 78 | .SetParameter("PostId", postId); 79 | 80 | // Act 81 | var result = await find(apiRepository); 82 | 83 | // Assert 84 | Assert.True( 85 | result.Count() > 0, 86 | JsonConvert.SerializeObject(result, Formatting.Indented)); 87 | Assert.DoesNotContain(result, p => p.PostId != postId); 88 | } 89 | 90 | [Fact] 91 | public async Task Find_Infer_Entity_Name_And_Id() 92 | { 93 | await Find_Infer_Entity_Name_And_Id_Test( 94 | async (r) => 95 | { 96 | return await Task.Run(() => r.Find()); 97 | }); 98 | await Find_Infer_Entity_Name_And_Id_Test( 99 | async (r) => 100 | { 101 | return await r.FindAsync(); 102 | }); 103 | } 104 | 105 | protected virtual async Task Find_Infer_Entity_Name_And_Id_Test( 106 | Func, Task>> find) 107 | { 108 | // Arrange 109 | IApiRepository apiRepository = new ApiRepository( 110 | configuration, 111 | "https://jsonplaceholder.typicode.com") 112 | .SetParameter("UserId", 1); 113 | 114 | // Act 115 | var result = await find(apiRepository); 116 | 117 | // Assert 118 | Assert.True( 119 | result.Count() > 0, 120 | JsonConvert.SerializeObject(result, Formatting.Indented)); 121 | Assert.DoesNotContain(result, p => p.UserId != 1); 122 | } 123 | 124 | [Fact] 125 | public async Task Find() 126 | { 127 | await FindTest( 128 | async (r) => 129 | { 130 | return await Task.Run(() => r.Find()); 131 | }); 132 | await FindTest( 133 | async (r) => 134 | { 135 | return await r.FindAsync(); 136 | }); 137 | } 138 | 139 | protected virtual async Task FindTest( 140 | Func, Task>> find) 141 | { 142 | // Arrange 143 | IApiRepository apiRepository = new ApiRepository( 144 | configuration, 145 | "https://jsonplaceholder.typicode.com"); 146 | 147 | // Act 148 | var result = await find(apiRepository); 149 | 150 | // Assert 151 | Assert.True( 152 | result.Count() > 0, 153 | JsonConvert.SerializeObject(result, Formatting.Indented)); 154 | } 155 | 156 | [Fact] 157 | public async Task Find_Invalid_Path() 158 | { 159 | await Find_Invalid_Path_Test( 160 | async (r) => 161 | { 162 | return await Task.Run(() => r.Find()); 163 | }); 164 | await Find_Invalid_Path_Test( 165 | async (r) => 166 | { 167 | return await r.FindAsync(); 168 | }); 169 | } 170 | 171 | protected virtual async Task Find_Invalid_Path_Test( 172 | Func, Task>> find) 173 | { 174 | // Arrange 175 | IApiRepository apiRepository = 176 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com", "bad_path") 177 | .SetParameter("UserId", 1); 178 | try 179 | { 180 | await find(apiRepository); 181 | } 182 | catch (ApiException exc) 183 | { 184 | Assert.Equal("GET", exc.Method); 185 | Assert.Equal("https://jsonplaceholder.typicode.com", exc.BasePath); 186 | Assert.Equal("bad_path", exc.Path); 187 | Assert.Equal(404, exc.ErrorCode); 188 | } 189 | catch(Exception) 190 | { 191 | throw; 192 | } 193 | } 194 | 195 | [Fact] 196 | public async Task GetById() 197 | { 198 | await GetByIdTest( 199 | async (r, id) => 200 | { 201 | return await Task.Run(() => r.GetById(id)); 202 | }); 203 | await GetByIdTest( 204 | async (r, id) => 205 | { 206 | return await r.GetByIdAsync(id); 207 | }); 208 | } 209 | 210 | protected virtual async Task GetByIdTest( 211 | Func, object, Task> getById) 212 | { 213 | // Arrange 214 | IApiRepository apiRepository = 215 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com"); 216 | 217 | // Act 218 | var result = await getById(apiRepository, "1"); 219 | 220 | // Assert 221 | Assert.NotNull(result); 222 | Assert.Equal(1, result.Id); 223 | } 224 | 225 | [Fact] 226 | public async Task Update() 227 | { 228 | await UpdateTest( 229 | async (r, e) => 230 | { 231 | await Task.Run(() => r.Update(e)); 232 | }); 233 | await UpdateTest( 234 | async (r, e) => 235 | { 236 | await r.UpdateAsync(e); 237 | }); 238 | } 239 | 240 | protected virtual async Task UpdateTest( 241 | Func, Post, Task> update) 242 | { 243 | // Arrange 244 | IApiRepository apiRepository = 245 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com"); 246 | 247 | // Act 248 | var post = new Post 249 | { 250 | Id = 1, 251 | UserId = 1, 252 | Title = "New title", 253 | Body = "New body" 254 | }; 255 | await update(apiRepository, post); 256 | 257 | // Assert 258 | Assert.Equal(1, post.Id); 259 | Assert.Equal("New title", post.Title); 260 | } 261 | 262 | [Fact] 263 | public async Task Delete() 264 | { 265 | await DeleteTest( 266 | async (r, e) => 267 | { 268 | await Task.Run(() => r.Delete(e)); 269 | }); 270 | await DeleteTest( 271 | async (r, e) => 272 | { 273 | await r.DeleteAsync(e); 274 | }); 275 | } 276 | 277 | protected virtual async Task DeleteTest( 278 | Func, Post, Task> delete) 279 | { 280 | // Arrange 281 | IApiRepository apiRepository = 282 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com"); 283 | 284 | // Act 285 | var post = await apiRepository.GetByIdAsync(1); 286 | await delete(apiRepository, post); 287 | 288 | // Assert: https://jsonplaceholder.typicode.com returns OK, but does not delete objects 289 | } 290 | 291 | [Fact] 292 | public async Task DeleteMany() 293 | { 294 | await DeleteManyTest( 295 | async (r, list) => 296 | { 297 | await Task.Run(() => r.DeleteMany(list)); 298 | }); 299 | await DeleteManyTest( 300 | async (r, list) => 301 | { 302 | await r.DeleteManyAsync(list); 303 | }); 304 | } 305 | 306 | protected virtual async Task DeleteManyTest( 307 | Func, IEnumerable, Task> deleteMany) 308 | { 309 | // Arrange 310 | IApiRepository apiRepository = 311 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com"); 312 | 313 | // Act 314 | var posts = new List 315 | { 316 | await apiRepository.GetByIdAsync(1), 317 | await apiRepository.GetByIdAsync(2), 318 | }; 319 | await deleteMany(apiRepository, posts); 320 | 321 | // Assert: https://jsonplaceholder.typicode.com returns OK, but does not delete objects 322 | } 323 | 324 | [Fact] 325 | public async Task Create() 326 | { 327 | await CreateTest( 328 | async (r, e) => 329 | { 330 | await Task.Run(() => r.Create(e)); 331 | }); 332 | await CreateTest( 333 | async (r, e) => 334 | { 335 | await r.CreateAsync(e); 336 | }); 337 | } 338 | 339 | protected virtual async Task CreateTest( 340 | Func, Post, Task> create) 341 | { 342 | // Arrange 343 | IApiRepository apiRepository = 344 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com"); 345 | 346 | // Act 347 | var post = new Post 348 | { 349 | UserId = 1, 350 | Title = "New title", 351 | Body = "New body" 352 | }; 353 | await create(apiRepository, post); 354 | 355 | // Assert 356 | Assert.NotEqual(1, post.Id); 357 | } 358 | 359 | [Fact] 360 | public async Task CreateMany() 361 | { 362 | await CreateManyTest( 363 | async (r, list) => 364 | { 365 | await Task.Run(() => r.CreateMany(list)); 366 | }); 367 | await CreateManyTest( 368 | async (r, list) => 369 | { 370 | await r.CreateManyAsync(list); 371 | }); 372 | } 373 | 374 | protected virtual async Task CreateManyTest( 375 | Func, IEnumerable, Task> createMany) 376 | { 377 | // Arrange 378 | IApiRepository apiRepository = 379 | new ApiRepository(configuration, "https://jsonplaceholder.typicode.com"); 380 | 381 | // Act 382 | var posts = new List 383 | { 384 | new Post 385 | { 386 | UserId = 1, 387 | Title = "New title 1", 388 | Body = "New body 1" 389 | }, 390 | new Post 391 | { 392 | UserId = 1, 393 | Title = "New title 2", 394 | Body = "New body 2" 395 | }, 396 | }; 397 | await createMany(apiRepository, posts); 398 | 399 | // Assert 400 | Assert.NotEqual(1, posts[0].Id); 401 | Assert.NotEqual(1, posts[2].Id); 402 | } 403 | } 404 | } 405 | --------------------------------------------------------------------------------