Skip to content
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

Add EF Core based DataLoader sample with queries and mutation #110

Merged
merged 15 commits into from
Oct 23, 2018
Merged
30 changes: 30 additions & 0 deletions samples/DataLoaderWithEFCore/DataLoaderWithEFCore.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2046
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataLoaderWithEFCore", "DataLoaderWithEFCore\DataLoaderWithEFCore.csproj", "{0FF0D230-70D4-49DC-8545-0B120F534EFC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B32B920C-751D-4C3F-B326-329A17E19B56}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0FF0D230-70D4-49DC-8545-0B120F534EFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FF0D230-70D4-49DC-8545-0B120F534EFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FF0D230-70D4-49DC-8545-0B120F534EFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FF0D230-70D4-49DC-8545-0B120F534EFC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {11561377-1EAE-4B0F-B09C-D5F3C051F341}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;

namespace DataLoaderWithEFCore.Data.Models
{
public class Actor
{
[Key]
public Guid Id { get; set; }

[MaxLength(200), Required]
public string Name { get; set; }

[MaxLength(2), Required]
public string CountryCode { get; set; }

[Required]
public Guid MovieId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;

namespace DataLoaderWithEFCore.Data.Models
{
public class Country
{
[Key, MaxLength(2)]
public string Code { get; set; }

public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.ComponentModel.DataAnnotations;

namespace DataLoaderWithEFCore.Data.Models
{
public class Movie
{
[Key]
public Guid Id { get; set; }

[MaxLength(500), Required]
public string Title { get; set; }

[MaxLength(50), Required]
public string Genre { get; set; }

[Required]
public DateTime ReleaseDateUtc { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using DataLoaderWithEFCore.Data.Models;
using Microsoft.EntityFrameworkCore;

namespace DataLoaderWithEFCore.Data
{
public class MovieDbContext : DbContext
{
public MovieDbContext(DbContextOptions<MovieDbContext> options)
: base(options)
{
}

public virtual DbSet<Actor> Actors { get; set; }

public virtual DbSet<Country> Countries { get; set; }

public virtual DbSet<Movie> Movies { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

var movie1 = new Movie { Id = Guid.NewGuid(), Title = "Johnny English Strikes Again", Genre = "Action/Adventure", ReleaseDateUtc = DateTime.Parse("10/12/2018 00:00:00Z") };
var movie2 = new Movie { Id = Guid.NewGuid(), Title = "A Star Is Born", Genre = "Drama/Romance", ReleaseDateUtc = DateTime.Parse("10/04/2018 00:00:00Z") };

modelBuilder.Entity<Actor>().HasData(
new Actor { Id = Guid.NewGuid(), CountryCode = "UK", Name = "Rowan Atkinson", MovieId = movie1.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "FR", Name = "Olga Kurylenko", MovieId = movie1.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Jake Lacy", MovieId = movie1.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Bradley Cooper", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Lady Gaga", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Sam Elliott", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Andrew Dice Clay", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Dave Chappelle", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Rebecca Field", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Michael Harney", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Rafi Gavron", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Willam Belli", MovieId = movie2.Id },
new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Halsey", MovieId = movie2.Id });

modelBuilder.Entity<Country>().HasData(
new Country { Code = "UK", Name = "United Kingdom" },
new Country { Code = "FR", Name = "France" },
new Country { Code = "US", Name = "United States" });

modelBuilder.Entity<Movie>().HasData(movie1, movie2);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DataLoaderWithEFCore.Data.Models;
using Microsoft.EntityFrameworkCore;

namespace DataLoaderWithEFCore.Data.Repositories
{
public interface IActorRepository
{
Task<ILookup<Guid, Actor>> GetActorsPerMovie(IEnumerable<Guid> movieIds);
}

public class ActorRepository : IActorRepository
{
private readonly MovieDbContext _context;

public ActorRepository(MovieDbContext context)
{
_context = context;
}

public async Task<ILookup<Guid, Actor>> GetActorsPerMovie(IEnumerable<Guid> movieIds)
=> (await _context.Actors
.AsNoTracking()
.Where(x => movieIds.Contains(x.MovieId))
.ToArrayAsync())
.ToLookup(x => x.MovieId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DataLoaderWithEFCore.Data.Models;
using Microsoft.EntityFrameworkCore;

namespace DataLoaderWithEFCore.Data.Repositories
{
public interface ICountryRepository
{
Task<IDictionary<string, Country>> GetCountries(IEnumerable<string> countryCodes);
}

public class CountryRepository : ICountryRepository
{
private readonly MovieDbContext _context;

public CountryRepository(MovieDbContext context)
{
_context = context;
}

public async Task<IDictionary<string, Country>> GetCountries(IEnumerable<string> countryCodes)
=> await _context.Countries
.AsNoTracking()
.Where(x => countryCodes.Contains(x.Code))
.ToDictionaryAsync(x => x.Code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Threading.Tasks;
using DataLoaderWithEFCore.Data.Models;
using Microsoft.EntityFrameworkCore;

namespace DataLoaderWithEFCore.Data.Repositories
{
public interface IMovieRepository
{
Task<Movie> FindMovie(Guid id);

Task<Movie[]> GetMovies();

Task<Movie> UpdateMovieTitle(Guid id, string newTitle);
}

public class MovieRepository : IMovieRepository
{
private readonly MovieDbContext _context;

public MovieRepository(MovieDbContext context)
{
_context = context;
}

public async Task<Movie> FindMovie(Guid id)
=> await _context.Movies.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);

public async Task<Movie[]> GetMovies()
=> await _context.Movies.AsNoTracking().ToArrayAsync();

public async Task<Movie> UpdateMovieTitle(Guid id, string newTitle)
{
var movie = await FindMovie(id);
if (movie == null)
throw new InvalidOperationException($"Movie with id {id} not found");

movie.Title = newTitle;
_context.Movies.Update(movie);
await _context.SaveChangesAsync();
return movie;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Compile Remove="wwwroot\**" />
<Content Remove="wwwroot\**" />
<EmbeddedResource Remove="wwwroot\**" />
<None Remove="wwwroot\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="7.0.1" />
<PackageReference Include="GraphQL.Conventions" Version="2.0.1" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could potentially use a project reference here?

Copy link
Contributor Author

@huysentruitw huysentruitw Oct 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want me to include the sample project in the main .sln file? (Only in that case a project reference would be better)

<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.4" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.5" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Conventions;
using Microsoft.AspNetCore.Mvc;

namespace DataLoaderWithEFCore.GraphApi
{
[ApiController]
[Route("api/graph")]
public class GraphController : ControllerBase
{
private readonly GraphQLEngine _engine;
private readonly IUserContext _userContext;
private readonly IDependencyInjector _injector;

public GraphController(GraphQLEngine engine, IUserContext userContext, IDependencyInjector injector)
{
_engine = engine;
_userContext = userContext;
_injector = injector;
}

[HttpPost]
public async Task<IActionResult> Post()
{
string requestBody;
using (var reader = new StreamReader(Request.Body))
requestBody = await reader.ReadToEndAsync();

ExecutionResult result = await _engine
.NewExecutor()
.WithUserContext(_userContext)
.WithDependencyInjector(_injector)
.WithRequest(requestBody)
.Execute();

var responseBody = _engine.SerializeResult(result);

HttpStatusCode statusCode = HttpStatusCode.OK;

if (result.Errors?.Any() ?? false)
{
statusCode = HttpStatusCode.InternalServerError;
if (result.Errors.Any(x => x.Code == "VALIDATION_ERROR"))
statusCode = HttpStatusCode.BadRequest;
else if (result.Errors.Any(x => x.Code == "UNAUTHORIZED_ACCESS"))
statusCode = HttpStatusCode.Forbidden;
}

return new ContentResult
{
Content = responseBody,
ContentType = "application/json; charset=utf-8",
StatusCode = (int)statusCode
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Reflection;
using GraphQL.Conventions;

namespace DataLoaderWithEFCore.GraphApi
{
public class Injector : IDependencyInjector
{
private readonly IServiceProvider _serviceProvider;

public Injector(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public object Resolve(TypeInfo typeInfo)
{
return _serviceProvider.GetService(typeInfo);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using AutoMapper;
using Models = DataLoaderWithEFCore.Data.Models;

namespace DataLoaderWithEFCore.GraphApi
{
public class Mappings : Profile
{
public Mappings()
{
CreateMap<Models.Actor, Schema.Actor>();
CreateMap<Models.Country, Schema.Country>();
CreateMap<Models.Movie, Schema.Movie>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using GraphQL.Conventions;

namespace DataLoaderWithEFCore.GraphApi.Schema
{
[InputType]
public class UpdateMovieTitleParams
{
public Guid Id { get; set; }

public string NewTitle { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading.Tasks;
using AutoMapper;
using DataLoaderWithEFCore.Data.Repositories;
using GraphQL.Conventions;

namespace DataLoaderWithEFCore.GraphApi.Schema
{
public sealed class Mutation
{
public async Task<Movie> UpdateMovieTitle([Inject] IMovieRepository movieRepository, UpdateMovieTitleParams @params)
=> Mapper.Map<Movie>(await movieRepository.UpdateMovieTitle(@params.Id, @params.NewTitle));
}
}
Loading