Skip to content

Commit

Permalink
Added entity framework helpers. (#2435)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Oct 15, 2020
1 parent 6d79503 commit 2a0605a
Show file tree
Hide file tree
Showing 17 changed files with 507 additions and 1 deletion.
30 changes: 30 additions & 0 deletions src/HotChocolate/Data/HotChocolate.Data.sln
Expand Up @@ -66,6 +66,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.Sorting.I
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.Sorting.SqlLite.Tests", "test\Data.Sorting.SqlLite.Tests\HotChocolate.Data.Sorting.SqlLite.Tests.csproj", "{D445A9BB-D068-496A-B261-609799F95915}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.EntityFramework", "src\EntityFramework\HotChocolate.Data.EntityFramework.csproj", "{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.EntityFramework.Tests", "test\Data.EntityFramework.Tests\HotChocolate.Data.EntityFramework.Tests.csproj", "{16FB6511-AE94-46C4-9652-2665C6816546}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -102,6 +106,8 @@ Global
{A7269FAC-8C91-46BA-B292-221E8DE32BD3} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F}
{E5E0D684-4FF6-4124-8ADB-9E96A3FDA6FA} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F}
{D445A9BB-D068-496A-B261-609799F95915} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F}
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303} = {91887A91-7B1C-4287-A1E0-BD4E0DAF24C7}
{16FB6511-AE94-46C4-9652-2665C6816546} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D68A0AB9-871A-487B-8D12-1A7544D81B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -380,5 +386,29 @@ Global
{D445A9BB-D068-496A-B261-609799F95915}.Release|x64.Build.0 = Release|Any CPU
{D445A9BB-D068-496A-B261-609799F95915}.Release|x86.ActiveCfg = Release|Any CPU
{D445A9BB-D068-496A-B261-609799F95915}.Release|x86.Build.0 = Release|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Debug|x64.ActiveCfg = Debug|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Debug|x64.Build.0 = Debug|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Debug|x86.ActiveCfg = Debug|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Debug|x86.Build.0 = Debug|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Release|Any CPU.Build.0 = Release|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Release|x64.ActiveCfg = Release|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Release|x64.Build.0 = Release|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Release|x86.ActiveCfg = Release|Any CPU
{0E4E8E6F-A65C-42C0-BE9F-DFA002E59303}.Release|x86.Build.0 = Release|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Debug|x64.ActiveCfg = Debug|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Debug|x64.Build.0 = Debug|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Debug|x86.ActiveCfg = Debug|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Debug|x86.Build.0 = Debug|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Release|Any CPU.Build.0 = Release|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Release|x64.ActiveCfg = Release|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Release|x64.Build.0 = Release|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Release|x86.ActiveCfg = Release|Any CPU
{16FB6511-AE94-46C4-9652-2665C6816546}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
@@ -0,0 +1,60 @@
using System;
using System.Linq;
using HotChocolate.Data;
using HotChocolate.Resolvers;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using static HotChocolate.Resolvers.FieldClassMiddlewareFactory;

namespace HotChocolate.Types
{
public static class EntityFrameworkObjectFieldDescriptorExtensions
{
public static IObjectFieldDescriptor UseDbContext<TDbContext>(
this IObjectFieldDescriptor descriptor)
where TDbContext : DbContext
{
string scopedServiceName = typeof(TDbContext).FullName ?? typeof(TDbContext).Name;
FieldMiddleware placeholder = next => context => throw new NotSupportedException();

descriptor
.Use(next => async context =>
{
await using TDbContext dbContext = context.Services
.GetRequiredService<IDbContextFactory<TDbContext>>()
.CreateDbContext();
try
{
context.SetLocalValue(scopedServiceName, dbContext);
await next(context).ConfigureAwait(false);
}
finally
{
context.RemoveLocalValue(scopedServiceName);
}
})
.Use(placeholder)
.Extend()
.OnBeforeNaming((c, d) =>
{
if (d.ResultType is not null &&
typeof(IQueryable).IsAssignableFrom(d.ResultType) &&
d.ResultType.IsGenericType)
{
Type entity = d.ResultType.GenericTypeArguments[0];
Type middleware = typeof(ToListMiddleware<>).MakeGenericType(entity);
var index = d.MiddlewareComponents.IndexOf(placeholder);
d.MiddlewareComponents[index] = Create(middleware);
}
else
{
d.MiddlewareComponents.Remove(placeholder);
}
});

return descriptor;
}
}
}
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>HotChocolate.Data.EntityFramework</PackageId>
<AssemblyName>HotChocolate.Data</AssemblyName>
<RootNamespace>HotChocolate.Data</RootNamespace>
<NoWarn>$(NoWarn);CA1062</NoWarn>
<TargetFrameworks>netstandard2.1</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Core\src\Core\HotChocolate.Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.0-rc.2.20475.6" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Properties\EntityFrameworkResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>EntityFrameworkResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<Compile Update="Properties\EntityFrameworkResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>EntityFrameworkResources.resx</DependentUpon>
</Compile>
</ItemGroup>

</Project>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>

<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">

</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UseDbContextAttribute_OnConfigure_NoDbContextType" xml:space="preserve">
<value>The `{0}` must inherit from `Microsoft.EntityFrameworkCore`.</value>
</data>
</root>
29 changes: 29 additions & 0 deletions src/HotChocolate/Data/src/EntityFramework/ToListMiddleware.cs
@@ -0,0 +1,29 @@
using System.Linq;
using System.Threading.Tasks;
using HotChocolate.Resolvers;
using Microsoft.EntityFrameworkCore;

namespace HotChocolate.Data
{
internal class ToListMiddleware<TEntity>
{
private readonly FieldDelegate _next;

public ToListMiddleware(FieldDelegate next)
{
_next = next;
}

public async ValueTask InvokeAsync(IMiddlewareContext context)
{
await _next(context).ConfigureAwait(false);

if (context.Result is IQueryable<TEntity> queryable)
{
context.Result = await queryable
.ToListAsync(context.RequestAborted)
.ConfigureAwait(false);
}
}
}
}
44 changes: 44 additions & 0 deletions src/HotChocolate/Data/src/EntityFramework/UseDbContextAttribute.cs
@@ -0,0 +1,44 @@
using System;
using System.Reflection;
using HotChocolate.Data.Properties;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors;
using Microsoft.EntityFrameworkCore;
using static HotChocolate.Data.Properties.EntityFrameworkResources;

namespace HotChocolate.Data
{
public class UseDbContextAttribute : ObjectFieldDescriptorAttribute
{
private static readonly MethodInfo _useDbContext =
typeof(EntityFrameworkObjectFieldDescriptorExtensions)
.GetMethod(nameof(EntityFrameworkObjectFieldDescriptorExtensions.UseDbContext),
BindingFlags.Public | BindingFlags.Static)!;

private readonly Type _dbContext;

public UseDbContextAttribute(Type dbContext)
{
_dbContext = dbContext;
}

public override void OnConfigure(
IDescriptorContext context,
IObjectFieldDescriptor descriptor,
MemberInfo member)
{
if (!typeof(DbContext).IsAssignableFrom(_dbContext))
{
throw new SchemaException(
SchemaErrorBuilder.New()
.SetMessage(
UseDbContextAttribute_OnConfigure_NoDbContextType,
_dbContext.FullName ?? _dbContext.Name)
.SetExtension(nameof(member), member)
.Build());
}

_useDbContext.MakeGenericMethod(_dbContext).Invoke(null, new object?[] { descriptor });
}
}
}
19 changes: 19 additions & 0 deletions src/HotChocolate/Data/test/Data.EntityFramework.Tests/Author.cs
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace HotChocolate.Data
{
public class Author
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

[Required]
public string? Name { get; set; }

public virtual ICollection<Book> Books { get; set; } =
new List<Book>();
}
}
20 changes: 20 additions & 0 deletions src/HotChocolate/Data/test/Data.EntityFramework.Tests/Book.cs
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace HotChocolate.Data
{
public class Book
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

[Required]
public int AuthorId { get; set; }

[Required]
public string? Title { get; set; }

public virtual Author? Author { get; set; }
}
}
@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;

namespace HotChocolate.Data
{
public class BookContext : DbContext
{
public BookContext(DbContextOptions options) : base(options)
{
}

public DbSet<Book> Books { get; set; } = default!;

public DbSet<Author> Authors { get; set; } = default!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>()
.HasMany(t => t.Books)
.WithOne(t => t.Author!)
.HasForeignKey(t => t.AuthorId);
}
}
}
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>HotChocolate.Data</RootNamespace>
<TargetFrameworks>net5.0; netcoreapp3.1</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Core\test\Types.Tests\HotChocolate.Types.Tests.csproj" />
<ProjectReference Include="..\..\src\EntityFramework\HotChocolate.Data.EntityFramework.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0-rc.2.20475.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Update="Properties\EfResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>EfResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>

</Project>

0 comments on commit 2a0605a

Please sign in to comment.