diff --git a/Ardalis.Specification.sln b/Ardalis.Specification.sln index 01b56ed9..96d0d522 100644 --- a/Ardalis.Specification.sln +++ b/Ardalis.Specification.sln @@ -44,6 +44,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specification.EntityFramewo EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specification.EntityFramework6", "Specification.EntityFramework6", "{327AEBD6-C8A6-4851-BA42-632F8014CFC5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ardalis.Specification.EntityFrameworkCore.UnitTests", "Specification.EntityFrameworkCore\tests\Ardalis.Specification.EntityFrameworkCore.UnitTests\Ardalis.Specification.EntityFrameworkCore.UnitTests.csproj", "{53E4FFB4-CAC0-482D-B714-FA657C3244C9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -86,6 +88,10 @@ Global {4BEB4DC4-DE33-4DF1-8A2F-CE76C1D72A4A}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BEB4DC4-DE33-4DF1-8A2F-CE76C1D72A4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BEB4DC4-DE33-4DF1-8A2F-CE76C1D72A4A}.Release|Any CPU.Build.0 = Release|Any CPU + {53E4FFB4-CAC0-482D-B714-FA657C3244C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53E4FFB4-CAC0-482D-B714-FA657C3244C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53E4FFB4-CAC0-482D-B714-FA657C3244C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53E4FFB4-CAC0-482D-B714-FA657C3244C9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -100,6 +106,7 @@ Global {5AFD1454-E625-451D-A615-CEB7BB09AA65} = {B19F2F64-4B22-48C2-B2F8-7672F84F758D} {37EC09C7-702D-4539-B98D-F67B15E1E6CE} = {327AEBD6-C8A6-4851-BA42-632F8014CFC5} {4BEB4DC4-DE33-4DF1-8A2F-CE76C1D72A4A} = {327AEBD6-C8A6-4851-BA42-632F8014CFC5} + {53E4FFB4-CAC0-482D-B714-FA657C3244C9} = {B19F2F64-4B22-48C2-B2F8-7672F84F758D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C153A625-42F7-49A7-B99A-6A78B4B866B2} diff --git a/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/Ardalis.Specification.EntityFrameworkCore.csproj b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/Ardalis.Specification.EntityFrameworkCore.csproj index b7852993..b310acc6 100644 --- a/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/Ardalis.Specification.EntityFrameworkCore.csproj +++ b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/Ardalis.Specification.EntityFrameworkCore.csproj @@ -1,7 +1,7 @@  - net6.0;netstandard2.1;netstandard2.0 + net6.0;netstandard2.1 Ardalis.Specification.EntityFrameworkCore Ardalis.Specification.EntityFrameworkCore true diff --git a/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/ContextFactoryRepositoryBaseOfT.cs b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/ContextFactoryRepositoryBaseOfT.cs new file mode 100644 index 00000000..0c10ec74 --- /dev/null +++ b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/ContextFactoryRepositoryBaseOfT.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace Ardalis.Specification.EntityFrameworkCore +{ + public abstract class ContextFactoryRepositoryBaseOfT : IRepositoryBase + where TEntity : class + where TContext : DbContext + { + private IDbContextFactory dbContextFactory; + private ISpecificationEvaluator specificationEvaluator; + + public ContextFactoryRepositoryBaseOfT(IDbContextFactory dbContextFactory) + : this(dbContextFactory, SpecificationEvaluator.Default) + { + } + + public ContextFactoryRepositoryBaseOfT(IDbContextFactory dbContextFactory, + ISpecificationEvaluator specificationEvaluator) + { + this.dbContextFactory = dbContextFactory; + this.specificationEvaluator = specificationEvaluator; + } + + /// + public async Task GetByIdAsync(TId id, CancellationToken cancellationToken = default) where TId : notnull + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await dbContext.Set().FindAsync(new object[] { id }, cancellationToken: cancellationToken); + } + + /// + public async Task GetBySpecAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext).FirstOrDefaultAsync(cancellationToken); + } + + /// + public async Task GetBySpecAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext).FirstOrDefaultAsync(cancellationToken); + } + + /// + public async Task FirstOrDefaultAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext).FirstOrDefaultAsync(cancellationToken); + } + + /// + public async Task FirstOrDefaultAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext).FirstOrDefaultAsync(cancellationToken); + } + + /// + public async Task SingleOrDefaultAsync(ISingleResultSpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext).FirstOrDefaultAsync(cancellationToken); + } + + /// + public async Task SingleOrDefaultAsync(ISingleResultSpecification specification, + CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext).FirstOrDefaultAsync(cancellationToken); + } + + /// + public async Task> ListAsync(CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await dbContext.Set().ToListAsync(cancellationToken); + } + + /// + public async Task> ListAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + var queryResult = await ApplySpecification(specification, dbContext).ToListAsync(cancellationToken); + + return specification.PostProcessingAction == null ? queryResult : specification.PostProcessingAction(queryResult).ToList(); + } + + /// + public async Task> ListAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + var queryResult = await ApplySpecification(specification, dbContext).ToListAsync(cancellationToken); + + return specification.PostProcessingAction == null ? queryResult : specification.PostProcessingAction(queryResult).ToList(); + } + + /// + public async Task CountAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext, true).CountAsync(cancellationToken); + } + + /// + public async Task CountAsync(CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await dbContext.Set().CountAsync(cancellationToken); + } + + /// + public async Task AnyAsync(ISpecification specification, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await ApplySpecification(specification, dbContext, true).AnyAsync(cancellationToken); + } + + /// + public async Task AnyAsync(CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + return await dbContext.Set().AnyAsync(cancellationToken); + } + + /// + public async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + dbContext.Set().Add(entity); + + await SaveChangesAsync(dbContext, cancellationToken); + + return entity; + } + + /// + public async Task> AddRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + dbContext.Set().AddRange(entities); + + await SaveChangesAsync(dbContext, cancellationToken); + + return entities; + } + + /// + public async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + dbContext.Set().Update(entity); + + await SaveChangesAsync(dbContext, cancellationToken); + } + + /// + public async Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + dbContext.Set().UpdateRange(entities); + + await SaveChangesAsync(dbContext, cancellationToken); + } + + /// + public async Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + dbContext.Set().Remove(entity); + + await SaveChangesAsync(dbContext, cancellationToken); + } + + /// + public async Task DeleteRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default) + { + await using var dbContext = this.dbContextFactory.CreateDbContext(); + dbContext.Set().RemoveRange(entities); + + await SaveChangesAsync(dbContext, cancellationToken); + } + + /// + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + throw new InvalidOperationException(); + } + + public async Task SaveChangesAsync(TContext dbContext, CancellationToken cancellationToken = default) + { + return await dbContext.SaveChangesAsync(cancellationToken); + } + + /// + /// Filters the entities of , to those that match the encapsulated query logic of the + /// . + /// + /// The encapsulated query logic. + /// The filtered entities as an . + protected virtual IQueryable ApplySpecification(ISpecification specification, TContext dbContext, bool evaluateCriteriaOnly = false) + { + return specificationEvaluator.GetQuery(dbContext.Set().AsQueryable(), specification, evaluateCriteriaOnly); + } + + /// + /// Filters all entities of , that matches the encapsulated query logic of the + /// , from the database. + /// + /// Projects each entity into a new form, being . + /// + /// + /// The type of the value returned by the projection. + /// The encapsulated query logic. + /// The filtered projected entities as an . + protected virtual IQueryable ApplySpecification(ISpecification specification, TContext dbContext) + { + return specificationEvaluator.GetQuery(dbContext.Set().AsQueryable(), specification); + } + } +} diff --git a/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/EFRepositoryFactory.cs b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/EFRepositoryFactory.cs new file mode 100644 index 00000000..dba7bc13 --- /dev/null +++ b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/EFRepositoryFactory.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore; + +namespace Ardalis.Specification.EntityFrameworkCore +{ + /// + /// + /// + /// The Interface of the repository created by this Factory + /// + /// The Concrete implementation of the repository interface to create + /// + /// The DbContext derived class to support the concrete repository + public class EFRepositoryFactory : IRepositoryFactory + where TConcreteRepository : TRepository + where TContext : DbContext + { + private IDbContextFactory dbContextFactory; + + /// + /// Initialises a new instance of the EFRepositoryFactory + /// + /// The IDbContextFactory to use to generate the TContext + public EFRepositoryFactory(IDbContextFactory dbContextFactory) + { + this.dbContextFactory = dbContextFactory; + } + + /// + public TRepository CreateRepository() + { + var args = new object[] { dbContextFactory.CreateDbContext() }; + return (TRepository)Activator.CreateInstance(typeof(TConcreteRepository), args); + } + } +} diff --git a/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/IRepositoryFactory.cs b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/IRepositoryFactory.cs new file mode 100644 index 00000000..257b9d27 --- /dev/null +++ b/Specification.EntityFrameworkCore/src/Ardalis.Specification.EntityFrameworkCore/IRepositoryFactory.cs @@ -0,0 +1,18 @@ +namespace Ardalis.Specification.EntityFrameworkCore +{ + /// + /// Generates new instances of to encapsulate the 'Unit of Work' pattern + /// in scenarios where injected types may be long-lived (e.g. Blazor) + /// + /// + /// The Interface of the Repository to be generated. + /// + public interface IRepositoryFactory + { + /// + /// Generates a new repository instance + /// + /// The generated repository instance + public TRepository CreateRepository(); + } +} diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/ContextFactoryRepositoryBaseOfTTests.cs b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/ContextFactoryRepositoryBaseOfTTests.cs new file mode 100644 index 00000000..c98c4c4e --- /dev/null +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/ContextFactoryRepositoryBaseOfTTests.cs @@ -0,0 +1,150 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ardalis.Specification.EntityFrameworkCore.IntegrationTests.Fixture; +using Ardalis.Specification.UnitTests.Fixture.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Ardalis.Specification.EntityFrameworkCore.IntegrationTests +{ + public class ContextFactoryRepositoryBaseOfTTests : IClassFixture + { + protected TestDbContext dbContext; + protected IServiceProvider serviceProvider; + protected ContextFactoryRepository repository; + + public ContextFactoryRepositoryBaseOfTTests(SharedDatabaseFixture fixture) + { + dbContext = fixture.CreateContext(); + + serviceProvider = new ServiceCollection() + .AddDbContextFactory((builder => builder.UseSqlServer(fixture.Connection)), + ServiceLifetime.Transient).BuildServiceProvider(); + + var contextFactory = serviceProvider.GetService>(); + repository = new ContextFactoryRepository(contextFactory); + } + + [Fact] + public async Task Saves_new_entity() + { + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company(); + company.Name = "Test save new company name"; + company.CountryId = country.Id; + + await repository.AddAsync(company); + Assert.NotEqual(0, company.Id); + } + + [Fact] + public async Task Updates_existing_entity() + { + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update existing company name", CountryId = country.Id }; + await repository.AddAsync(company); + + var existingCompany = await repository.GetByIdAsync(company.Id); + existingCompany.Name = "Updated company name"; + await repository.UpdateAsync(existingCompany); + + var validationCompany = await dbContext.Companies.FirstOrDefaultAsync(x => x.Id == company.Id); + Assert.Equal(validationCompany.Name, existingCompany.Name); + } + + [Fact] + public async Task Updates_existing_entity_across_context_instances() + { + var contextFactory = serviceProvider.GetService>(); + var companyRetrievalRepository = new ContextFactoryRepository(contextFactory); + var companySaveRepository = new ContextFactoryRepository(contextFactory); + + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update existing company name", CountryId = country.Id }; + await repository.AddAsync(company); + + var existingCompany = await companyRetrievalRepository.GetByIdAsync(company.Id); + existingCompany.Name = "Updated company name"; + await companySaveRepository.UpdateAsync(existingCompany); + + var validationCompany = await dbContext.Companies.FirstOrDefaultAsync(x => x.Id == company.Id); + Assert.Equal(validationCompany.Name, existingCompany.Name); + } + + [Fact] + public async Task Updates_graph() + { + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update graph", CountryId = country.Id }; + var store = new Store { Name = "Store Number 1" }; + company.Stores.Add(store); + + await repository.AddAsync(company); + + var spec = new GetCompanyWithStoresSpec(company.Id); + var existingCompany = await repository.FirstOrDefaultAsync(spec); + existingCompany.Name = "Updated company name"; + var existingStore = existingCompany.Stores.FirstOrDefault(); + existingStore.Name = "Updated Store Name"; + + await repository.UpdateAsync(existingCompany); + + var validationCompany = await dbContext.Companies.FirstOrDefaultAsync(x => x.Id == company.Id); + Assert.Equal(validationCompany.Name, existingCompany.Name); + + var validationStore = await dbContext.Stores.FirstOrDefaultAsync(x => x.CompanyId == company.Id); + Assert.Equal(validationStore.Name, existingStore.Name); + } + + [Fact] + public async Task Updates_graph_across_context_instances() + { + var contextFactory = serviceProvider.GetService>(); + var companyRetrievalRepository = new ContextFactoryRepository(contextFactory); + var companySaveRepository = new ContextFactoryRepository(contextFactory); + + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update graph", CountryId = country.Id }; + var store = new Store { Name = "Store Number 1" }; + company.Stores.Add(store); + + await repository.AddAsync(company); + + var spec = new GetCompanyWithStoresSpec(company.Id); + var existingCompany = await companyRetrievalRepository.FirstOrDefaultAsync(spec); + existingCompany.Name = "Updated company name"; + var existingStore = existingCompany.Stores.FirstOrDefault(); + existingStore.Name = "Updated Store Name"; + + await companySaveRepository.UpdateAsync(existingCompany); + + var validationCompany = await dbContext.Companies.FirstOrDefaultAsync(x => x.Id == company.Id); + Assert.Equal(validationCompany.Name, existingCompany.Name); + + var validationStore = await dbContext.Stores.FirstOrDefaultAsync(x => x.CompanyId == company.Id); + Assert.Equal(validationStore.Name, existingStore.Name); + } + + [Fact] + public async Task Deletes_entity() + { + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update graph", CountryId = country.Id }; + await repository.AddAsync(company); + + var companyId = company.Id; + await repository.DeleteAsync(company); + + var validationCompany = await repository.GetByIdAsync(companyId); + Assert.Null(validationCompany); + } + } +} diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/EFRepositoryFactoryTests.cs b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/EFRepositoryFactoryTests.cs new file mode 100644 index 00000000..c30df303 --- /dev/null +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/EFRepositoryFactoryTests.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ardalis.Specification.EntityFrameworkCore.IntegrationTests.Fixture; +using Ardalis.Specification.UnitTests.Fixture.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Ardalis.Specification.EntityFrameworkCore.IntegrationTests +{ + public class EFRepositoryFactoryTests : IClassFixture + { + protected TestDbContext dbContext; + protected IServiceProvider serviceProvider; + protected IRepositoryFactory> repositoryFactory; + protected IDbContextFactory contextFactory; + + public EFRepositoryFactoryTests(SharedDatabaseFixture fixture) + { + dbContext = fixture.CreateContext(); + + serviceProvider = new ServiceCollection() + .AddDbContextFactory((builder => builder.UseSqlServer(fixture.Connection)), + ServiceLifetime.Transient).BuildServiceProvider(); + + contextFactory = serviceProvider.GetService>(); + repositoryFactory = + new EFRepositoryFactory, Repository, TestDbContext>(contextFactory); + } + + [Fact] + public async Task Saves_new_entity() + { + var repository = repositoryFactory.CreateRepository(); + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company(); + company.Name = "Test save new company name"; + company.CountryId = country.Id; + + await repository.AddAsync(company); + Assert.NotEqual(0, company.Id); + } + + [Fact] + public async Task Updates_existing_entity() + { + var repository = repositoryFactory.CreateRepository(); + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update existing company name", CountryId = country.Id }; + await repository.AddAsync(company); + + var existingCompany = await repository.GetByIdAsync(company.Id); + existingCompany.Name = "Updated company name"; + await repository.UpdateAsync(existingCompany); + + var validationCompany = await dbContext.Companies.FirstOrDefaultAsync(x => x.Id == company.Id); + Assert.Equal(validationCompany.Name, existingCompany.Name); + } + + [Fact] + public async Task Updates_graph() + { + var repository = repositoryFactory.CreateRepository(); + var country = await dbContext.Countries.FirstOrDefaultAsync(); + + var company = new Company { Name = "Test update graph", CountryId = country.Id }; + var store = new Store { Name = "Store Number 1" }; + company.Stores.Add(store); + + await repository.AddAsync(company); + + var spec = new GetCompanyWithStoresSpec(company.Id); + var existingCompany = await repository.FirstOrDefaultAsync(spec); + existingCompany.Name = "Updated company name"; + var existingStore = existingCompany.Stores.FirstOrDefault(); + existingStore.Name = "Updated Store Name"; + + await repository.UpdateAsync(existingCompany); + + var validationCompany = await dbContext.Companies.FirstOrDefaultAsync(x => x.Id == company.Id); + Assert.Equal(validationCompany.Name, existingCompany.Name); + + var validationStore = await dbContext.Stores.FirstOrDefaultAsync(x => x.CompanyId == company.Id); + Assert.Equal(validationStore.Name, existingStore.Name); + } + } +} diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/ContextFactoryRepositoryOfT.cs b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/ContextFactoryRepositoryOfT.cs new file mode 100644 index 00000000..80a4fd19 --- /dev/null +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/ContextFactoryRepositoryOfT.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; + +namespace Ardalis.Specification.EntityFrameworkCore.IntegrationTests.Fixture +{ + public class ContextFactoryRepository : ContextFactoryRepositoryBaseOfT + where T : class where TContext : DbContext + { + public ContextFactoryRepository(IDbContextFactory dbContextFactory) : base(dbContextFactory) + { + } + + public ContextFactoryRepository(IDbContextFactory dbContextFactory, + ISpecificationEvaluator specificationEvaluator) : base(dbContextFactory, specificationEvaluator) + { + } + } +} diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/GetCompanyWithStoresSpec.cs b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/GetCompanyWithStoresSpec.cs new file mode 100644 index 00000000..e4560e12 --- /dev/null +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/GetCompanyWithStoresSpec.cs @@ -0,0 +1,12 @@ +using Ardalis.Specification.UnitTests.Fixture.Entities; + +namespace Ardalis.Specification.EntityFrameworkCore.IntegrationTests.Fixture +{ + public class GetCompanyWithStoresSpec : Specification, ISingleResultSpecification + { + public GetCompanyWithStoresSpec(int companyId) + { + this.Query.Where(x => x.Id == companyId).Include(x => x.Stores); + } + } +} diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/RepositoryOfT.cs b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/RepositoryOfT.cs index 191ad62c..cc82da8a 100644 --- a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/RepositoryOfT.cs +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.IntegrationTests/Fixture/RepositoryOfT.cs @@ -5,6 +5,10 @@ public class Repository : RepositoryBase where T : class { protected readonly TestDbContext dbContext; + public Repository(TestDbContext dbContext) : this(dbContext, SpecificationEvaluator.Default) + { + } + public Repository(TestDbContext dbContext, ISpecificationEvaluator specificationEvaluator) : base(dbContext, specificationEvaluator) { this.dbContext = dbContext; diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.UnitTests/Ardalis.Specification.EntityFrameworkCore.UnitTests.csproj b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.UnitTests/Ardalis.Specification.EntityFrameworkCore.UnitTests.csproj new file mode 100644 index 00000000..059f6c6c --- /dev/null +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.UnitTests/Ardalis.Specification.EntityFrameworkCore.UnitTests.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.UnitTests/EFRepositoryFactoryTests.cs b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.UnitTests/EFRepositoryFactoryTests.cs new file mode 100644 index 00000000..07d50d64 --- /dev/null +++ b/Specification.EntityFrameworkCore/tests/Ardalis.Specification.EntityFrameworkCore.UnitTests/EFRepositoryFactoryTests.cs @@ -0,0 +1,27 @@ +using Ardalis.SampleApp.Core.Entities.CustomerAggregate; +using Ardalis.SampleApp.Core.Interfaces; +using Ardalis.SampleApp.Infrastructure.Data; +using Ardalis.SampleApp.Infrastructure.DataAccess; +using Microsoft.EntityFrameworkCore; +using Moq; +using Xunit; + +namespace Ardalis.Specification.EntityFrameworkCore.UnitTests; + +public class EFRepositoryFactoryTests +{ + [Fact] + public void CorrectlyInstantiatesRepository() + { + var mockContextFactory = new Mock>(); + mockContextFactory.Setup(x => x.CreateDbContext()) + .Returns(() => new SampleDbContext(new DbContextOptions())); + + var repositoryFactory = + new EFRepositoryFactory, MyRepository, SampleDbContext>(mockContextFactory + .Object); + + var repository = repositoryFactory.CreateRepository(); + Assert.IsType>(repository); + } +}