Skip to content

Commit ae0129e

Browse files
authored
Merge pull request #1492 from json-api-dotnet/readonly-entity-model
Drop dependency on EF Core internals
2 parents 06d6b18 + 3bf9157 commit ae0129e

14 files changed

+95
-96
lines changed

Diff for: src/Examples/DapperExample/TranslationToSql/DataModel/FromEntitiesDataModelService.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ public void Initialize(DbContext dbContext)
3838
Initialize();
3939
}
4040

41-
private void ScanForeignKeys(IModel entityModel)
41+
private void ScanForeignKeys(IReadOnlyModel entityModel)
4242
{
4343
foreach (RelationshipAttribute relationship in ResourceGraph.GetResourceTypes().SelectMany(resourceType => resourceType.Relationships))
4444
{
45-
IEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
46-
INavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
45+
IReadOnlyEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
46+
IReadOnlyNavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
4747

4848
if (navigation != null)
4949
{
@@ -57,23 +57,23 @@ private void ScanForeignKeys(IModel entityModel)
5757
}
5858
}
5959

60-
private void ScanColumnNullability(IModel entityModel)
60+
private void ScanColumnNullability(IReadOnlyModel entityModel)
6161
{
6262
foreach (ResourceType resourceType in ResourceGraph.GetResourceTypes())
6363
{
6464
ScanColumnNullability(resourceType, entityModel);
6565
}
6666
}
6767

68-
private void ScanColumnNullability(ResourceType resourceType, IModel entityModel)
68+
private void ScanColumnNullability(ResourceType resourceType, IReadOnlyModel entityModel)
6969
{
70-
IEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
70+
IReadOnlyEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
7171

7272
if (entityType != null)
7373
{
7474
foreach (AttrAttribute attribute in resourceType.Attributes)
7575
{
76-
IProperty? property = entityType.FindProperty(attribute.Property.Name);
76+
IReadOnlyProperty? property = entityType.FindProperty(attribute.Property.Name);
7777

7878
if (property != null)
7979
{

Diff for: src/Examples/NoEntityFrameworkExample/Data/InMemoryModel.cs

-25
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Reflection;
2+
using JsonApiDotNetCore.Configuration;
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Metadata;
5+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
6+
7+
namespace NoEntityFrameworkExample.Data;
8+
9+
internal static class ResourceGraphExtensions
10+
{
11+
public static IReadOnlyModel ToEntityModel(this IResourceGraph resourceGraph)
12+
{
13+
var modelBuilder = new ModelBuilder();
14+
15+
foreach (ResourceType resourceType in resourceGraph.GetResourceTypes())
16+
{
17+
IncludeResourceType(resourceType, modelBuilder);
18+
}
19+
20+
return modelBuilder.Model;
21+
}
22+
23+
private static void IncludeResourceType(ResourceType resourceType, ModelBuilder builder)
24+
{
25+
EntityTypeBuilder entityTypeBuilder = builder.Entity(resourceType.ClrType);
26+
27+
foreach (PropertyInfo property in resourceType.ClrType.GetProperties())
28+
{
29+
entityTypeBuilder.Property(property.PropertyType, property.Name);
30+
}
31+
}
32+
}

Diff for: src/Examples/NoEntityFrameworkExample/Program.cs

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using JsonApiDotNetCore.Configuration;
22
using NoEntityFrameworkExample;
3+
using NoEntityFrameworkExample.Data;
34

45
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
56

@@ -20,6 +21,12 @@
2021
#endif
2122
}, discovery => discovery.AddCurrentAssembly());
2223

24+
builder.Services.AddSingleton(serviceProvider =>
25+
{
26+
var resourceGraph = serviceProvider.GetRequiredService<IResourceGraph>();
27+
return resourceGraph.ToEntityModel();
28+
});
29+
2330
WebApplication app = builder.Build();
2431

2532
// Configure the HTTP request pipeline.

Diff for: src/Examples/NoEntityFrameworkExample/QueryLayerToLinqConverter.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
namespace NoEntityFrameworkExample;
99

10-
internal sealed class QueryLayerToLinqConverter(IModel model, IQueryableBuilder queryableBuilder)
10+
internal sealed class QueryLayerToLinqConverter(IReadOnlyModel entityModel, IQueryableBuilder queryableBuilder)
1111
{
12-
private readonly IModel _model = model;
12+
private readonly IReadOnlyModel _entityModel = entityModel;
1313
private readonly IQueryableBuilder _queryableBuilder = queryableBuilder;
1414

1515
public IEnumerable<TResource> ApplyQueryLayer<TResource>(QueryLayer queryLayer, IEnumerable<TResource> resources)
@@ -21,7 +21,7 @@ public IEnumerable<TResource> ApplyQueryLayer<TResource>(QueryLayer queryLayer,
2121

2222
// Convert QueryLayer into LINQ expression.
2323
IQueryable source = ((IEnumerable)resources).AsQueryable();
24-
var context = QueryableBuilderContext.CreateRoot(source, typeof(Enumerable), _model, null);
24+
var context = QueryableBuilderContext.CreateRoot(source, typeof(Enumerable), _entityModel, null);
2525
Expression expression = _queryableBuilder.ApplyQuery(queryLayer, context);
2626

2727
// Insert null checks to prevent a NullReferenceException during execution of expressions such as:

Diff for: src/Examples/NoEntityFrameworkExample/Repositories/InMemoryResourceRepository.cs

+5-12
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using JsonApiDotNetCore.Queries.QueryableBuilding;
55
using JsonApiDotNetCore.Repositories;
66
using JsonApiDotNetCore.Resources;
7-
using NoEntityFrameworkExample.Data;
7+
using Microsoft.EntityFrameworkCore.Metadata;
88

99
namespace NoEntityFrameworkExample.Repositories;
1010

@@ -19,19 +19,12 @@ namespace NoEntityFrameworkExample.Repositories;
1919
/// <typeparam name="TId">
2020
/// The resource identifier type.
2121
/// </typeparam>
22-
public abstract class InMemoryResourceRepository<TResource, TId> : IResourceReadRepository<TResource, TId>
22+
public abstract class InMemoryResourceRepository<TResource, TId>(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
23+
: IResourceReadRepository<TResource, TId>
2324
where TResource : class, IIdentifiable<TId>
2425
{
25-
private readonly ResourceType _resourceType;
26-
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter;
27-
28-
protected InMemoryResourceRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
29-
{
30-
_resourceType = resourceGraph.GetResourceType<TResource>();
31-
32-
var model = new InMemoryModel(resourceGraph);
33-
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(model, queryableBuilder);
34-
}
26+
private readonly ResourceType _resourceType = resourceGraph.GetResourceType<TResource>();
27+
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter = new(entityModel, queryableBuilder);
3528

3629
/// <inheritdoc />
3730
public Task<IReadOnlyCollection<TResource>> GetAsync(QueryLayer queryLayer, CancellationToken cancellationToken)

Diff for: src/Examples/NoEntityFrameworkExample/Repositories/PersonRepository.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Queries.QueryableBuilding;
4+
using Microsoft.EntityFrameworkCore.Metadata;
45
using NoEntityFrameworkExample.Data;
56
using NoEntityFrameworkExample.Models;
67

78
namespace NoEntityFrameworkExample.Repositories;
89

910
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
10-
public sealed class PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
11-
: InMemoryResourceRepository<Person, long>(resourceGraph, queryableBuilder)
11+
public sealed class PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
12+
: InMemoryResourceRepository<Person, long>(resourceGraph, queryableBuilder, entityModel)
1213
{
1314
protected override IEnumerable<Person> GetDataSource()
1415
{

Diff for: src/Examples/NoEntityFrameworkExample/Repositories/TagRepository.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Queries.QueryableBuilding;
4+
using Microsoft.EntityFrameworkCore.Metadata;
45
using NoEntityFrameworkExample.Data;
56
using NoEntityFrameworkExample.Models;
67

78
namespace NoEntityFrameworkExample.Repositories;
89

910
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
10-
public sealed class TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
11-
: InMemoryResourceRepository<Tag, long>(resourceGraph, queryableBuilder)
11+
public sealed class TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
12+
: InMemoryResourceRepository<Tag, long>(resourceGraph, queryableBuilder, entityModel)
1213
{
1314
protected override IEnumerable<Tag> GetDataSource()
1415
{

Diff for: src/Examples/NoEntityFrameworkExample/Repositories/TodoItemRepository.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Queries.QueryableBuilding;
4+
using Microsoft.EntityFrameworkCore.Metadata;
45
using NoEntityFrameworkExample.Data;
56
using NoEntityFrameworkExample.Models;
67

78
namespace NoEntityFrameworkExample.Repositories;
89

910
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
10-
public sealed class TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
11-
: InMemoryResourceRepository<TodoItem, long>(resourceGraph, queryableBuilder)
11+
public sealed class TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
12+
: InMemoryResourceRepository<TodoItem, long>(resourceGraph, queryableBuilder, entityModel)
1213
{
1314
protected override IEnumerable<TodoItem> GetDataSource()
1415
{

Diff for: src/Examples/NoEntityFrameworkExample/Services/InMemoryResourceService.cs

+12-25
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using JsonApiDotNetCore.Resources;
88
using JsonApiDotNetCore.Resources.Annotations;
99
using JsonApiDotNetCore.Services;
10-
using NoEntityFrameworkExample.Data;
10+
using Microsoft.EntityFrameworkCore.Metadata;
1111

1212
namespace NoEntityFrameworkExample.Services;
1313

@@ -30,32 +30,19 @@ namespace NoEntityFrameworkExample.Services;
3030
/// <typeparam name="TId">
3131
/// The resource identifier type.
3232
/// </typeparam>
33-
public abstract class InMemoryResourceService<TResource, TId> : IResourceQueryService<TResource, TId>
33+
public abstract class InMemoryResourceService<TResource, TId>(
34+
IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext,
35+
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel,
36+
ILoggerFactory loggerFactory) : IResourceQueryService<TResource, TId>
3437
where TResource : class, IIdentifiable<TId>
3538
{
36-
private readonly IJsonApiOptions _options;
37-
private readonly IQueryLayerComposer _queryLayerComposer;
38-
private readonly IPaginationContext _paginationContext;
39-
private readonly IEnumerable<IQueryConstraintProvider> _constraintProviders;
40-
private readonly ILogger<InMemoryResourceService<TResource, TId>> _logger;
41-
private readonly ResourceType _resourceType;
42-
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter;
43-
44-
protected InMemoryResourceService(IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer,
45-
IPaginationContext paginationContext, IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder,
46-
ILoggerFactory loggerFactory)
47-
{
48-
_options = options;
49-
_queryLayerComposer = queryLayerComposer;
50-
_paginationContext = paginationContext;
51-
_constraintProviders = constraintProviders;
52-
53-
_logger = loggerFactory.CreateLogger<InMemoryResourceService<TResource, TId>>();
54-
_resourceType = resourceGraph.GetResourceType<TResource>();
55-
56-
var model = new InMemoryModel(resourceGraph);
57-
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(model, queryableBuilder);
58-
}
39+
private readonly IJsonApiOptions _options = options;
40+
private readonly IQueryLayerComposer _queryLayerComposer = queryLayerComposer;
41+
private readonly IPaginationContext _paginationContext = paginationContext;
42+
private readonly IEnumerable<IQueryConstraintProvider> _constraintProviders = constraintProviders;
43+
private readonly ILogger<InMemoryResourceService<TResource, TId>> _logger = loggerFactory.CreateLogger<InMemoryResourceService<TResource, TId>>();
44+
private readonly ResourceType _resourceType = resourceGraph.GetResourceType<TResource>();
45+
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter = new(entityModel, queryableBuilder);
5946

6047
/// <inheritdoc />
6148
public Task<IReadOnlyCollection<TResource>> GetAsync(CancellationToken cancellationToken)

Diff for: src/Examples/NoEntityFrameworkExample/Services/TodoItemService.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using JsonApiDotNetCore.Queries;
44
using JsonApiDotNetCore.Queries.QueryableBuilding;
55
using JsonApiDotNetCore.Resources;
6+
using Microsoft.EntityFrameworkCore.Metadata;
67
using NoEntityFrameworkExample.Data;
78
using NoEntityFrameworkExample.Models;
89

@@ -11,8 +12,8 @@ namespace NoEntityFrameworkExample.Services;
1112
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
1213
public sealed class TodoItemService(
1314
IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext,
14-
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, ILoggerFactory loggerFactory)
15-
: InMemoryResourceService<TodoItem, long>(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder,
15+
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel, ILoggerFactory loggerFactory)
16+
: InMemoryResourceService<TodoItem, long>(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder, entityModel,
1617
loggerFactory)
1718
{
1819
protected override IEnumerable<IIdentifiable> GetDataSource(ResourceType resourceType)

Diff for: src/JsonApiDotNetCore/Queries/QueryableBuilding/QueryClauseBuilderContext.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public sealed class QueryClauseBuilderContext
2929
/// <summary>
3030
/// The Entity Framework Core entity model.
3131
/// </summary>
32-
public IModel EntityModel { get; }
32+
public IReadOnlyModel EntityModel { get; }
3333

3434
/// <summary>
3535
/// Used to produce unique names for lambda parameters.
@@ -51,7 +51,7 @@ public sealed class QueryClauseBuilderContext
5151
/// </summary>
5252
public object? State { get; }
5353

54-
public QueryClauseBuilderContext(Expression source, ResourceType resourceType, Type extensionType, IModel entityModel,
54+
public QueryClauseBuilderContext(Expression source, ResourceType resourceType, Type extensionType, IReadOnlyModel entityModel,
5555
LambdaScopeFactory lambdaScopeFactory, LambdaScope lambdaScope, IQueryableBuilder queryableBuilder, object? state)
5656
{
5757
ArgumentGuard.NotNull(source);

Diff for: src/JsonApiDotNetCore/Queries/QueryableBuilding/QueryableBuilderContext.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public sealed class QueryableBuilderContext
2929
/// <summary>
3030
/// The Entity Framework Core entity model.
3131
/// </summary>
32-
public IModel EntityModel { get; }
32+
public IReadOnlyModel EntityModel { get; }
3333

3434
/// <summary>
3535
/// Used to produce unique names for lambda parameters.
@@ -41,7 +41,7 @@ public sealed class QueryableBuilderContext
4141
/// </summary>
4242
public object? State { get; }
4343

44-
public QueryableBuilderContext(Expression source, Type elementType, Type extensionType, IModel entityModel, LambdaScopeFactory lambdaScopeFactory,
44+
public QueryableBuilderContext(Expression source, Type elementType, Type extensionType, IReadOnlyModel entityModel, LambdaScopeFactory lambdaScopeFactory,
4545
object? state)
4646
{
4747
ArgumentGuard.NotNull(source);
@@ -58,15 +58,15 @@ public QueryableBuilderContext(Expression source, Type elementType, Type extensi
5858
State = state;
5959
}
6060

61-
public static QueryableBuilderContext CreateRoot(IQueryable source, Type extensionType, IModel model, object? state)
61+
public static QueryableBuilderContext CreateRoot(IQueryable source, Type extensionType, IReadOnlyModel entityModel, object? state)
6262
{
6363
ArgumentGuard.NotNull(source);
6464
ArgumentGuard.NotNull(extensionType);
65-
ArgumentGuard.NotNull(model);
65+
ArgumentGuard.NotNull(entityModel);
6666

6767
var lambdaScopeFactory = new LambdaScopeFactory();
6868

69-
return new QueryableBuilderContext(source.Expression, source.ElementType, extensionType, model, lambdaScopeFactory, state);
69+
return new QueryableBuilderContext(source.Expression, source.ElementType, extensionType, entityModel, lambdaScopeFactory, state);
7070
}
7171

7272
public QueryClauseBuilderContext CreateClauseContext(IQueryableBuilder queryableBuilder, Expression source, ResourceType resourceType,

0 commit comments

Comments
 (0)