Skip to content

Drop dependency on EF Core internals #1492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ public void Initialize(DbContext dbContext)
Initialize();
}

private void ScanForeignKeys(IModel entityModel)
private void ScanForeignKeys(IReadOnlyModel entityModel)
{
foreach (RelationshipAttribute relationship in ResourceGraph.GetResourceTypes().SelectMany(resourceType => resourceType.Relationships))
{
IEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
INavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);
IReadOnlyEntityType? leftEntityType = entityModel.FindEntityType(relationship.LeftType.ClrType);
IReadOnlyNavigation? navigation = leftEntityType?.FindNavigation(relationship.Property.Name);

if (navigation != null)
{
Expand All @@ -57,23 +57,23 @@ private void ScanForeignKeys(IModel entityModel)
}
}

private void ScanColumnNullability(IModel entityModel)
private void ScanColumnNullability(IReadOnlyModel entityModel)
{
foreach (ResourceType resourceType in ResourceGraph.GetResourceTypes())
{
ScanColumnNullability(resourceType, entityModel);
}
}

private void ScanColumnNullability(ResourceType resourceType, IModel entityModel)
private void ScanColumnNullability(ResourceType resourceType, IReadOnlyModel entityModel)
{
IEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);
IReadOnlyEntityType? entityType = entityModel.FindEntityType(resourceType.ClrType);

if (entityType != null)
{
foreach (AttrAttribute attribute in resourceType.Attributes)
{
IProperty? property = entityType.FindProperty(attribute.Property.Name);
IReadOnlyProperty? property = entityType.FindProperty(attribute.Property.Name);

if (property != null)
{
Expand Down
25 changes: 0 additions & 25 deletions src/Examples/NoEntityFrameworkExample/Data/InMemoryModel.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Reflection;
using JsonApiDotNetCore.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace NoEntityFrameworkExample.Data;

internal static class ResourceGraphExtensions
{
public static IReadOnlyModel ToEntityModel(this IResourceGraph resourceGraph)
{
var modelBuilder = new ModelBuilder();

foreach (ResourceType resourceType in resourceGraph.GetResourceTypes())
{
IncludeResourceType(resourceType, modelBuilder);
}

return modelBuilder.Model;
}

private static void IncludeResourceType(ResourceType resourceType, ModelBuilder builder)
{
EntityTypeBuilder entityTypeBuilder = builder.Entity(resourceType.ClrType);

foreach (PropertyInfo property in resourceType.ClrType.GetProperties())
{
entityTypeBuilder.Property(property.PropertyType, property.Name);
}
}
}
7 changes: 7 additions & 0 deletions src/Examples/NoEntityFrameworkExample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using JsonApiDotNetCore.Configuration;
using NoEntityFrameworkExample;
using NoEntityFrameworkExample.Data;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

Expand All @@ -20,6 +21,12 @@
#endif
}, discovery => discovery.AddCurrentAssembly());

builder.Services.AddSingleton(serviceProvider =>
{
var resourceGraph = serviceProvider.GetRequiredService<IResourceGraph>();
return resourceGraph.ToEntityModel();
});

WebApplication app = builder.Build();

// Configure the HTTP request pipeline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

namespace NoEntityFrameworkExample;

internal sealed class QueryLayerToLinqConverter(IModel model, IQueryableBuilder queryableBuilder)
internal sealed class QueryLayerToLinqConverter(IReadOnlyModel entityModel, IQueryableBuilder queryableBuilder)
{
private readonly IModel _model = model;
private readonly IReadOnlyModel _entityModel = entityModel;
private readonly IQueryableBuilder _queryableBuilder = queryableBuilder;

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

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

// Insert null checks to prevent a NullReferenceException during execution of expressions such as:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using JsonApiDotNetCore.Queries.QueryableBuilding;
using JsonApiDotNetCore.Repositories;
using JsonApiDotNetCore.Resources;
using NoEntityFrameworkExample.Data;
using Microsoft.EntityFrameworkCore.Metadata;

namespace NoEntityFrameworkExample.Repositories;

Expand All @@ -19,19 +19,12 @@ namespace NoEntityFrameworkExample.Repositories;
/// <typeparam name="TId">
/// The resource identifier type.
/// </typeparam>
public abstract class InMemoryResourceRepository<TResource, TId> : IResourceReadRepository<TResource, TId>
public abstract class InMemoryResourceRepository<TResource, TId>(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
: IResourceReadRepository<TResource, TId>
where TResource : class, IIdentifiable<TId>
{
private readonly ResourceType _resourceType;
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter;

protected InMemoryResourceRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
{
_resourceType = resourceGraph.GetResourceType<TResource>();

var model = new InMemoryModel(resourceGraph);
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(model, queryableBuilder);
}
private readonly ResourceType _resourceType = resourceGraph.GetResourceType<TResource>();
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter = new(entityModel, queryableBuilder);

/// <inheritdoc />
public Task<IReadOnlyCollection<TResource>> GetAsync(QueryLayer queryLayer, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.QueryableBuilding;
using Microsoft.EntityFrameworkCore.Metadata;
using NoEntityFrameworkExample.Data;
using NoEntityFrameworkExample.Models;

namespace NoEntityFrameworkExample.Repositories;

[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
public sealed class PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
: InMemoryResourceRepository<Person, long>(resourceGraph, queryableBuilder)
public sealed class PersonRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
: InMemoryResourceRepository<Person, long>(resourceGraph, queryableBuilder, entityModel)
{
protected override IEnumerable<Person> GetDataSource()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.QueryableBuilding;
using Microsoft.EntityFrameworkCore.Metadata;
using NoEntityFrameworkExample.Data;
using NoEntityFrameworkExample.Models;

namespace NoEntityFrameworkExample.Repositories;

[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
public sealed class TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
: InMemoryResourceRepository<Tag, long>(resourceGraph, queryableBuilder)
public sealed class TagRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
: InMemoryResourceRepository<Tag, long>(resourceGraph, queryableBuilder, entityModel)
{
protected override IEnumerable<Tag> GetDataSource()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using JetBrains.Annotations;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.QueryableBuilding;
using Microsoft.EntityFrameworkCore.Metadata;
using NoEntityFrameworkExample.Data;
using NoEntityFrameworkExample.Models;

namespace NoEntityFrameworkExample.Repositories;

[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
public sealed class TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder)
: InMemoryResourceRepository<TodoItem, long>(resourceGraph, queryableBuilder)
public sealed class TodoItemRepository(IResourceGraph resourceGraph, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel)
: InMemoryResourceRepository<TodoItem, long>(resourceGraph, queryableBuilder, entityModel)
{
protected override IEnumerable<TodoItem> GetDataSource()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;
using JsonApiDotNetCore.Services;
using NoEntityFrameworkExample.Data;
using Microsoft.EntityFrameworkCore.Metadata;

namespace NoEntityFrameworkExample.Services;

Expand All @@ -30,32 +30,19 @@ namespace NoEntityFrameworkExample.Services;
/// <typeparam name="TId">
/// The resource identifier type.
/// </typeparam>
public abstract class InMemoryResourceService<TResource, TId> : IResourceQueryService<TResource, TId>
public abstract class InMemoryResourceService<TResource, TId>(
IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext,
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel,
ILoggerFactory loggerFactory) : IResourceQueryService<TResource, TId>
where TResource : class, IIdentifiable<TId>
{
private readonly IJsonApiOptions _options;
private readonly IQueryLayerComposer _queryLayerComposer;
private readonly IPaginationContext _paginationContext;
private readonly IEnumerable<IQueryConstraintProvider> _constraintProviders;
private readonly ILogger<InMemoryResourceService<TResource, TId>> _logger;
private readonly ResourceType _resourceType;
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter;

protected InMemoryResourceService(IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer,
IPaginationContext paginationContext, IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder,
ILoggerFactory loggerFactory)
{
_options = options;
_queryLayerComposer = queryLayerComposer;
_paginationContext = paginationContext;
_constraintProviders = constraintProviders;

_logger = loggerFactory.CreateLogger<InMemoryResourceService<TResource, TId>>();
_resourceType = resourceGraph.GetResourceType<TResource>();

var model = new InMemoryModel(resourceGraph);
_queryLayerToLinqConverter = new QueryLayerToLinqConverter(model, queryableBuilder);
}
private readonly IJsonApiOptions _options = options;
private readonly IQueryLayerComposer _queryLayerComposer = queryLayerComposer;
private readonly IPaginationContext _paginationContext = paginationContext;
private readonly IEnumerable<IQueryConstraintProvider> _constraintProviders = constraintProviders;
private readonly ILogger<InMemoryResourceService<TResource, TId>> _logger = loggerFactory.CreateLogger<InMemoryResourceService<TResource, TId>>();
private readonly ResourceType _resourceType = resourceGraph.GetResourceType<TResource>();
private readonly QueryLayerToLinqConverter _queryLayerToLinqConverter = new(entityModel, queryableBuilder);

/// <inheritdoc />
public Task<IReadOnlyCollection<TResource>> GetAsync(CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using JsonApiDotNetCore.Queries;
using JsonApiDotNetCore.Queries.QueryableBuilding;
using JsonApiDotNetCore.Resources;
using Microsoft.EntityFrameworkCore.Metadata;
using NoEntityFrameworkExample.Data;
using NoEntityFrameworkExample.Models;

Expand All @@ -11,8 +12,8 @@ namespace NoEntityFrameworkExample.Services;
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
public sealed class TodoItemService(
IJsonApiOptions options, IResourceGraph resourceGraph, IQueryLayerComposer queryLayerComposer, IPaginationContext paginationContext,
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, ILoggerFactory loggerFactory)
: InMemoryResourceService<TodoItem, long>(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder,
IEnumerable<IQueryConstraintProvider> constraintProviders, IQueryableBuilder queryableBuilder, IReadOnlyModel entityModel, ILoggerFactory loggerFactory)
: InMemoryResourceService<TodoItem, long>(options, resourceGraph, queryLayerComposer, paginationContext, constraintProviders, queryableBuilder, entityModel,
loggerFactory)
{
protected override IEnumerable<IIdentifiable> GetDataSource(ResourceType resourceType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public sealed class QueryClauseBuilderContext
/// <summary>
/// The Entity Framework Core entity model.
/// </summary>
public IModel EntityModel { get; }
public IReadOnlyModel EntityModel { get; }

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

public QueryClauseBuilderContext(Expression source, ResourceType resourceType, Type extensionType, IModel entityModel,
public QueryClauseBuilderContext(Expression source, ResourceType resourceType, Type extensionType, IReadOnlyModel entityModel,
LambdaScopeFactory lambdaScopeFactory, LambdaScope lambdaScope, IQueryableBuilder queryableBuilder, object? state)
{
ArgumentGuard.NotNull(source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public sealed class QueryableBuilderContext
/// <summary>
/// The Entity Framework Core entity model.
/// </summary>
public IModel EntityModel { get; }
public IReadOnlyModel EntityModel { get; }

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

public QueryableBuilderContext(Expression source, Type elementType, Type extensionType, IModel entityModel, LambdaScopeFactory lambdaScopeFactory,
public QueryableBuilderContext(Expression source, Type elementType, Type extensionType, IReadOnlyModel entityModel, LambdaScopeFactory lambdaScopeFactory,
object? state)
{
ArgumentGuard.NotNull(source);
Expand All @@ -58,15 +58,15 @@ public QueryableBuilderContext(Expression source, Type elementType, Type extensi
State = state;
}

public static QueryableBuilderContext CreateRoot(IQueryable source, Type extensionType, IModel model, object? state)
public static QueryableBuilderContext CreateRoot(IQueryable source, Type extensionType, IReadOnlyModel entityModel, object? state)
{
ArgumentGuard.NotNull(source);
ArgumentGuard.NotNull(extensionType);
ArgumentGuard.NotNull(model);
ArgumentGuard.NotNull(entityModel);

var lambdaScopeFactory = new LambdaScopeFactory();

return new QueryableBuilderContext(source.Expression, source.ElementType, extensionType, model, lambdaScopeFactory, state);
return new QueryableBuilderContext(source.Expression, source.ElementType, extensionType, entityModel, lambdaScopeFactory, state);
}

public QueryClauseBuilderContext CreateClauseContext(IQueryableBuilder queryableBuilder, Expression source, ResourceType resourceType,
Expand Down
Loading