Skip to content

Commit 2b487a6

Browse files
authored
Merge pull request #34 from yv989c/feature/custom-sp-support
Feature/custom sp support
2 parents c90d110 + 0d82386 commit 2b487a6

File tree

5 files changed

+251
-57
lines changed

5 files changed

+251
-57
lines changed

.github/workflows/ci-workflow.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
# Adapted from: https://github.com/giraffe-fsharp/Giraffe/blob/master/.github/workflows/build.yml
22
name: CI/CD Workflow
33
on:
4-
push:
5-
branches:
6-
- develop
7-
- 'feature/**'
8-
paths:
9-
- 'src/**'
10-
- 'Version.xml'
114
pull_request:
125
paths:
136
- 'src/**'
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#if EFCORE
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.Diagnostics;
4+
using Microsoft.EntityFrameworkCore.Infrastructure;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
using System;
8+
9+
namespace BlazarTech.QueryableValues
10+
{
11+
/// <summary>
12+
/// Provides extension methods to register core QueryableValues services.
13+
/// </summary>
14+
public static class QueryableValuesServiceCollectionExtensions
15+
{
16+
/// <summary>
17+
/// Adds the services required by QueryableValues for the Microsoft SQL Server database provider.
18+
/// </summary>
19+
/// <remarks>
20+
/// It is only needed when building the internal service provider for use with
21+
/// the <see cref="DbContextOptionsBuilder.UseInternalServiceProvider" /> method.
22+
/// This is not recommend other than for some advanced scenarios.
23+
/// </remarks>
24+
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
25+
/// <returns>The same service collection so that multiple calls can be chained.</returns>
26+
/// <exception cref="ArgumentNullException"></exception>
27+
public static IServiceCollection AddQueryableValuesSqlServer(this IServiceCollection services)
28+
{
29+
if (services is null)
30+
{
31+
throw new ArgumentNullException(nameof(services));
32+
}
33+
34+
for (var index = services.Count - 1; index >= 0; index--)
35+
{
36+
var descriptor = services[index];
37+
if (descriptor.ServiceType != typeof(IModelCustomizer))
38+
{
39+
continue;
40+
}
41+
42+
if (descriptor.ImplementationType is null)
43+
{
44+
continue;
45+
}
46+
47+
// Replace theirs with ours.
48+
services[index] = new ServiceDescriptor(
49+
descriptor.ServiceType,
50+
typeof(ModelCustomizer<>).MakeGenericType(descriptor.ImplementationType),
51+
descriptor.Lifetime
52+
);
53+
54+
// Add theirs as is, so we can inject it into ours.
55+
services.Add(
56+
new ServiceDescriptor(
57+
descriptor.ImplementationType,
58+
descriptor.ImplementationType,
59+
descriptor.Lifetime
60+
)
61+
);
62+
}
63+
64+
services.TryAddSingleton<Serializers.IXmlSerializer, Serializers.XmlSerializer>();
65+
services.TryAddSingleton<Serializers.IJsonSerializer, Serializers.JsonSerializer>();
66+
services.TryAddScoped<SqlServer.XmlQueryableFactory>();
67+
services.TryAddScoped<SqlServer.JsonQueryableFactory>();
68+
services.TryAddScoped<SqlServer.ExtensionOptions>();
69+
services.TryAddScoped<SqlServer.QueryableFactoryFactory>();
70+
services.TryAddScoped<IInterceptor, SqlServer.JsonSupportConnectionInterceptor>();
71+
72+
return services;
73+
}
74+
}
75+
}
76+
#endif

src/QueryableValues.SqlServer/QueryableValuesSqlServerExtension.cs

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#if EFCORE
2-
using Microsoft.EntityFrameworkCore.Diagnostics;
32
using Microsoft.EntityFrameworkCore.Infrastructure;
43
using Microsoft.Extensions.DependencyInjection;
5-
using System;
64
using System.Collections.Generic;
75

86
namespace BlazarTech.QueryableValues
@@ -14,48 +12,7 @@ internal sealed class QueryableValuesSqlServerExtension : IDbContextOptionsExten
1412

1513
public void ApplyServices(IServiceCollection services)
1614
{
17-
if (services is null)
18-
{
19-
throw new ArgumentNullException(nameof(services));
20-
}
21-
22-
for (var index = services.Count - 1; index >= 0; index--)
23-
{
24-
var descriptor = services[index];
25-
if (descriptor.ServiceType != typeof(IModelCustomizer))
26-
{
27-
continue;
28-
}
29-
30-
if (descriptor.ImplementationType is null)
31-
{
32-
continue;
33-
}
34-
35-
// Replace theirs with ours.
36-
services[index] = new ServiceDescriptor(
37-
descriptor.ServiceType,
38-
typeof(ModelCustomizer<>).MakeGenericType(descriptor.ImplementationType),
39-
descriptor.Lifetime
40-
);
41-
42-
// Add theirs as is, so we can inject it into ours.
43-
services.Add(
44-
new ServiceDescriptor(
45-
descriptor.ImplementationType,
46-
descriptor.ImplementationType,
47-
descriptor.Lifetime
48-
)
49-
);
50-
}
51-
52-
services.AddSingleton<Serializers.IXmlSerializer, Serializers.XmlSerializer>();
53-
services.AddSingleton<Serializers.IJsonSerializer, Serializers.JsonSerializer>();
54-
services.AddScoped<SqlServer.XmlQueryableFactory>();
55-
services.AddScoped<SqlServer.JsonQueryableFactory>();
56-
services.AddScoped<SqlServer.ExtensionOptions>();
57-
services.AddScoped<SqlServer.QueryableFactoryFactory>();
58-
services.AddScoped<IInterceptor, SqlServer.JsonSupportConnectionInterceptor>();
15+
services.AddQueryableValuesSqlServer();
5916
}
6017

6118
public void Validate(IDbContextOptions options)

tests/QueryableValues.SqlServer.Tests/QueryableValuesDbContextOptionsExtensionTests.cs renamed to tests/QueryableValues.SqlServer.Tests/DependencyInjectionTests.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@
77

88
namespace BlazarTech.QueryableValues.SqlServer.Tests
99
{
10-
public class QueryableValuesDbContextOptionsExtensionTests
10+
public class DependencyInjectionTests
1111
{
12+
private void MustReplaceServiceAssertion(IServiceProvider serviceProvider)
13+
{
14+
var ourModelCustomizer = serviceProvider.GetService<IModelCustomizer>();
15+
Assert.IsType<ModelCustomizer<FakeModelCustomizer>>(ourModelCustomizer);
16+
17+
var theirsModelCustomizer = serviceProvider.GetService<FakeModelCustomizer>();
18+
Assert.IsType<FakeModelCustomizer>(theirsModelCustomizer);
19+
}
20+
1221
[Fact]
13-
public void MustReplaceService()
22+
public void MustReplaceServiceViaApplyServices()
1423
{
1524
var services = new ServiceCollection();
1625

@@ -22,11 +31,21 @@ public void MustReplaceService()
2231

2332
var serviceProvider = services.BuildServiceProvider();
2433

25-
var ourModelCustomizer = serviceProvider.GetService<IModelCustomizer>();
26-
Assert.IsType<ModelCustomizer<FakeModelCustomizer>>(ourModelCustomizer);
34+
MustReplaceServiceAssertion(serviceProvider);
35+
}
2736

28-
var theirsModelCustomizer = serviceProvider.GetService<FakeModelCustomizer>();
29-
Assert.IsType<FakeModelCustomizer>(theirsModelCustomizer);
37+
[Fact]
38+
public void MustReplaceServiceViaAddQueryableValuesSqlServer()
39+
{
40+
var services = new ServiceCollection();
41+
42+
services.AddTransient<IModelCustomizer, FakeModelCustomizer>();
43+
44+
services.AddQueryableValuesSqlServer();
45+
46+
var serviceProvider = services.BuildServiceProvider();
47+
48+
MustReplaceServiceAssertion(serviceProvider);
3049
}
3150

3251
private class FakeModelCustomizer : IModelCustomizer
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Threading.Tasks;
7+
using Xunit;
8+
9+
namespace BlazarTech.QueryableValues.SqlServer.Tests.Integration
10+
{
11+
public class CustomServiceProviderTests
12+
{
13+
public class DummyDbContext : DbContext
14+
{
15+
public DummyDbContext(DbContextOptions options) : base(options)
16+
{
17+
}
18+
}
19+
20+
private static IServiceProvider BuildGoodInternalServiceProvider()
21+
{
22+
var services = new ServiceCollection()
23+
.AddEntityFrameworkSqlServer()
24+
.AddQueryableValuesSqlServer();
25+
26+
return services.BuildServiceProvider();
27+
}
28+
29+
private static IServiceProvider BuildBadInternalServiceProvider()
30+
{
31+
var services = new ServiceCollection()
32+
.AddEntityFrameworkSqlServer();
33+
34+
return services.BuildServiceProvider();
35+
}
36+
37+
private static string GetConnectionString()
38+
{
39+
var databaseName = $"DummyDb{Guid.NewGuid():N}";
40+
var databaseFilePath = Path.Combine(Path.GetTempPath(), $"{databaseName}.mdf");
41+
return @$"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Connection Timeout=190;Database={databaseName};AttachDbFileName={databaseFilePath}";
42+
}
43+
44+
private static async Task CleanUpDbAsync(DbContext dbContext)
45+
{
46+
try
47+
{
48+
await dbContext.Database.EnsureDeletedAsync();
49+
}
50+
catch
51+
{
52+
}
53+
}
54+
55+
[Fact]
56+
public async Task BadInternalServiceProvider()
57+
{
58+
var internalServiceProvider = BuildBadInternalServiceProvider();
59+
var connectionString = GetConnectionString();
60+
var services = new ServiceCollection();
61+
62+
services.AddDbContext<DummyDbContext>(builder =>
63+
{
64+
builder
65+
.UseInternalServiceProvider(internalServiceProvider)
66+
.UseSqlServer(connectionString, options =>
67+
{
68+
options.UseQueryableValues();
69+
});
70+
});
71+
72+
var serviceProvider = services.BuildServiceProvider();
73+
var dbContext = serviceProvider.GetRequiredService<DummyDbContext>();
74+
75+
try
76+
{
77+
await dbContext.Database.EnsureCreatedAsync();
78+
79+
var values = new[] { 1, 2, 3 };
80+
81+
var exception = await Assert.ThrowsAnyAsync<InvalidOperationException>(async () =>
82+
{
83+
await dbContext.AsQueryableValues(values).ToListAsync();
84+
});
85+
86+
Assert.StartsWith("QueryableValues have not been configured for ", exception.Message);
87+
}
88+
finally
89+
{
90+
await CleanUpDbAsync(dbContext);
91+
}
92+
}
93+
94+
#if !EFCORE3
95+
[Theory]
96+
[InlineData(SqlServerSerialization.UseJson)]
97+
[InlineData(SqlServerSerialization.UseXml)]
98+
public async Task GoodInternalServiceProviderWithConfiguration(SqlServerSerialization sqlServerSerializationOption)
99+
{
100+
var internalServiceProvider = BuildGoodInternalServiceProvider();
101+
var connectionString = GetConnectionString();
102+
var services = new ServiceCollection();
103+
var logEntries = new List<string>();
104+
105+
services.AddDbContext<DummyDbContext>(builder =>
106+
{
107+
builder
108+
.UseInternalServiceProvider(internalServiceProvider)
109+
.UseSqlServer(connectionString, options =>
110+
{
111+
options.UseQueryableValues(options =>
112+
{
113+
options.Serialization(sqlServerSerializationOption);
114+
});
115+
})
116+
.LogTo(logEntry => { logEntries.Add(logEntry); }, Microsoft.Extensions.Logging.LogLevel.Information);
117+
});
118+
119+
var serviceProvider = services.BuildServiceProvider();
120+
var dbContext = serviceProvider.GetRequiredService<DummyDbContext>();
121+
122+
try
123+
{
124+
await dbContext.Database.EnsureCreatedAsync();
125+
126+
var values = new[] { 1, 2, 3 };
127+
var valuesResult = await dbContext.AsQueryableValues(values).ToListAsync();
128+
Assert.Equal(values, valuesResult);
129+
130+
switch (sqlServerSerializationOption)
131+
{
132+
case SqlServerSerialization.UseJson:
133+
Assert.Contains(logEntries, i => i.Contains("FROM OPENJSON(@p0)"));
134+
break;
135+
case SqlServerSerialization.UseXml:
136+
Assert.Contains(logEntries, i => i.Contains("FROM @p0.nodes"));
137+
break;
138+
default:
139+
throw new NotImplementedException();
140+
}
141+
}
142+
finally
143+
{
144+
await CleanUpDbAsync(dbContext);
145+
}
146+
}
147+
#endif
148+
}
149+
}

0 commit comments

Comments
 (0)