diff --git a/404.html b/404.html index 6722a22..d3b890d 100644 --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@ Page Not Found | Entity GraphQL - + diff --git a/assets/js/55c0b115.16657396.js b/assets/js/55c0b115.0e6dc344.js similarity index 64% rename from assets/js/55c0b115.16657396.js rename to assets/js/55c0b115.0e6dc344.js index 0c4d159..e144f4e 100644 --- a/assets/js/55c0b115.16657396.js +++ b/assets/js/55c0b115.0e6dc344.js @@ -1 +1 @@ -"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[3601],{4252:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>r,default:()=>p,frontMatter:()=>i,metadata:()=>l,toc:()=>a});var s=t(5893),o=t(1151);const i={sidebar_position:2},r="Sorting",l={id:"field-extensions/sorting",title:"Sorting",description:"Being able to sort or order you collections as you query them can be very powerful, especially when paired with paging. To easily add sorting functionality to your collection fields use the UseSort() field extension.",source:"@site/docs/field-extensions/sorting.md",sourceDirName:"field-extensions",slug:"/field-extensions/sorting",permalink:"/docs/field-extensions/sorting",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/field-extensions/sorting.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Filtering",permalink:"/docs/field-extensions/filtering"},next:{title:"Paging",permalink:"/docs/field-extensions/paging"}},c={},a=[{value:"Default sort",id:"default-sort",level:2},{value:"Default with multiple field",id:"default-with-multiple-field",level:3},{value:"Choosing the sort fields",id:"choosing-the-sort-fields",level:2}];function d(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",...(0,o.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"sorting",children:"Sorting"}),"\n",(0,s.jsxs)(n.p,{children:["Being able to sort or order you collections as you query them can be very powerful, especially when paired with paging. To easily add sorting functionality to your collection fields use the ",(0,s.jsx)(n.code,{children:"UseSort()"})," field extension."]}),"\n",(0,s.jsxs)(n.p,{children:["Note: When using with one of the paging extensions ensure you call ",(0,s.jsx)(n.code,{children:"UseSort"})," first. If you are using the attribute, then ensure the Sort attribute comes before the paging attribute. If using with the ",(0,s.jsx)(n.code,{children:"UseFilter"})," extensions, call filter first. Filter -> Sort -> Paging."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n .UseSort();\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If you are using the ",(0,s.jsx)(n.code,{children:"SchemaBuilder.FromObject"})," you can use the ",(0,s.jsx)(n.code,{children:"UseSortAttribute"})," on your collection properties."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class DemoContext : DbContext\n{\n [UseSort]\n public DbSet Movies { get; set; }\n [UseSort]\n public DbSet People { get; set; }\n [UseSort]\n public DbSet Actors { get; set; }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This field extension can only be used on a field that has a ",(0,s.jsx)(n.code,{children:"Resolve"})," expression that is assignable to ",(0,s.jsx)(n.code,{children:"IEnumerable"})," - I.e. collections. The extension adds an argument called ",(0,s.jsx)(n.code,{children:"sort: [SortInput]"}),". For example ",(0,s.jsx)(n.code,{children:"PeopleSortInput"}),". The ",(0,s.jsx)(n.code,{children:"SortInput"})," type will have nullable fields for each scalar type in the collection element type. You set which fields you want to use for sorting. Following the above ",(0,s.jsx)(n.code,{children:"people"})," field with the ",(0,s.jsx)(n.code,{children:"Person"})," class defined as:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class Person\n{\n public uint Id { get; set; }\n public string FirstName { get; set; }\n public string LastName { get; set; }\n public DateTime Dob { get; set; }v\n public List ActorIn { get; set; }\n public List WriterOf { get; set; }\n public List DirectorOf { get; set; }\n public DateTime? Died { get; set; }\n public bool IsDeleted { get; set; }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"The GraphQL type will be define like:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"input PeopleSortInput\n{\n\tid: SortDirectionEnum\n\tfirstName: SortDirectionEnum\n\tlastName: SortDirectionEnum\n\tdob: SortDirectionEnum\n\tdied: SortDirectionEnum\n\tisDeleted: SortDirectionEnum\n}\n\nenum SortDirectionEnum {\n\tASC\n\tDESC\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"To sort the collection you set the fields with a direction:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:"{\n people(sort: [{ lastName: DESC }]) {\n lastName\n }\n}\n\n{\n people(sort: [{ dob: ASC }]) {\n lastName\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Multiple fields is supported and are taken as ordered"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:"{\n people(sort: [{ dob: ASC }, { lastName: DESC }, { firstName: ASC }]) {\n lastName\n }\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"default-sort",children:"Default sort"}),"\n",(0,s.jsx)(n.p,{children:"You can set a default sort to be applied if there are no sort arguments passed in the query."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n .UseSort((Person person) => person.Dob, SortDirectionEnum.DESC);\n'})}),"\n",(0,s.jsx)(n.h3,{id:"default-with-multiple-field",children:"Default with multiple field"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n .UseSort(\n new Sort((person) => person.Height, SortDirection.ASC),\n new Sort((person) => person.LastName, SortDirection.ASC)\n );\n'})}),"\n",(0,s.jsx)(n.h2,{id:"choosing-the-sort-fields",children:"Choosing the sort fields"}),"\n",(0,s.jsxs)(n.p,{children:["If you use the ",(0,s.jsx)(n.code,{children:"UseSort()"})," method (not the attribute) you can pass in an expression which tells the extension which fields to set in the input type. Make sure you use the correct type for the fields collection. You can still set the default sort as well."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n // available sort fields\n .UseSort((Person person) => new\n {\n person.Dob,\n person.LastName\n },\n // Default sort\n (Person person) => person.Dob, SortDirectionEnum.DESC);\n'})}),"\n",(0,s.jsx)(n.p,{children:"This will result in only 2 options for sorting."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:"input PeopleSortInput {\n dob: SortDirectionEnum\n lastName: SortDirectionEnum\n}\n"})})]})}function p(e={}){const{wrapper:n}={...(0,o.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},1151:(e,n,t)=>{t.d(n,{Z:()=>l,a:()=>r});var s=t(7294);const o={},i=s.createContext(o);function r(e){const n=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[3601],{4252:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>r,default:()=>p,frontMatter:()=>i,metadata:()=>l,toc:()=>a});var s=t(5893),o=t(1151);const i={sidebar_position:2},r="Sorting",l={id:"field-extensions/sorting",title:"Sorting",description:"Being able to sort or order you collections as you query them can be very powerful, especially when paired with paging. To easily add sorting functionality to your collection fields use the UseSort() field extension.",source:"@site/docs/field-extensions/sorting.md",sourceDirName:"field-extensions",slug:"/field-extensions/sorting",permalink:"/docs/field-extensions/sorting",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/field-extensions/sorting.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2},sidebar:"tutorialSidebar",previous:{title:"Filtering",permalink:"/docs/field-extensions/filtering"},next:{title:"Paging",permalink:"/docs/field-extensions/paging"}},c={},a=[{value:"Default sort",id:"default-sort",level:2},{value:"Default with multiple field",id:"default-with-multiple-field",level:3},{value:"Choosing the sort fields",id:"choosing-the-sort-fields",level:2}];function d(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",...(0,o.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"sorting",children:"Sorting"}),"\n",(0,s.jsxs)(n.p,{children:["Being able to sort or order you collections as you query them can be very powerful, especially when paired with paging. To easily add sorting functionality to your collection fields use the ",(0,s.jsx)(n.code,{children:"UseSort()"})," field extension."]}),"\n",(0,s.jsxs)(n.p,{children:["Note: When using with one of the paging extensions ensure you call ",(0,s.jsx)(n.code,{children:"UseSort"})," first. If you are using the attribute, then ensure the Sort attribute comes before the paging attribute. If using with the ",(0,s.jsx)(n.code,{children:"UseFilter"})," extensions, call filter first. Filter -> Sort -> Paging."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n .UseSort();\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If you are using the ",(0,s.jsx)(n.code,{children:"SchemaBuilder.FromObject"})," you can use the ",(0,s.jsx)(n.code,{children:"UseSortAttribute"})," on your collection properties."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class DemoContext : DbContext\n{\n [UseSort]\n public DbSet Movies { get; set; }\n [UseSort]\n public DbSet People { get; set; }\n [UseSort]\n public DbSet Actors { get; set; }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This field extension can only be used on a field that has a ",(0,s.jsx)(n.code,{children:"Resolve"})," expression that is assignable to ",(0,s.jsx)(n.code,{children:"IEnumerable"})," - I.e. collections. The extension adds an argument called ",(0,s.jsx)(n.code,{children:"sort: [SortInput]"}),". For example ",(0,s.jsx)(n.code,{children:"PeopleSortInput"}),". The ",(0,s.jsx)(n.code,{children:"SortInput"})," type will have nullable fields for each scalar type in the collection element type. You set which fields you want to use for sorting. Following the above ",(0,s.jsx)(n.code,{children:"people"})," field with the ",(0,s.jsx)(n.code,{children:"Person"})," class defined as:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class Person\n{\n public uint Id { get; set; }\n public string FirstName { get; set; }\n public string LastName { get; set; }\n public DateTime Dob { get; set; }v\n public List ActorIn { get; set; }\n public List WriterOf { get; set; }\n public List DirectorOf { get; set; }\n public DateTime? Died { get; set; }\n public bool IsDeleted { get; set; }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"The GraphQL type will be define like:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"input PeopleSortInput\n{\n\tid: SortDirectionEnum\n\tfirstName: SortDirectionEnum\n\tlastName: SortDirectionEnum\n\tdob: SortDirectionEnum\n\tdied: SortDirectionEnum\n\tisDeleted: SortDirectionEnum\n}\n\nenum SortDirectionEnum {\n\tASC\n\tDESC\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"To sort the collection you set the fields with a direction:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:"{\n people(sort: [{ lastName: DESC }]) {\n lastName\n }\n}\n\n{\n people(sort: [{ dob: ASC }]) {\n lastName\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Multiple fields are supported and are taken as ordered"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:"{\n people(sort: [{ dob: ASC }, { lastName: DESC }, { firstName: ASC }]) {\n lastName\n }\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"default-sort",children:"Default sort"}),"\n",(0,s.jsx)(n.p,{children:"You can set a default sort to be applied if there are no sort arguments passed in the query."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n .UseSort((Person person) => person.Dob, SortDirectionEnum.DESC);\n'})}),"\n",(0,s.jsx)(n.h3,{id:"default-with-multiple-field",children:"Default with multiple field"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n .UseSort(\n new Sort((person) => person.Height, SortDirection.ASC),\n new Sort((person) => person.LastName, SortDirection.ASC)\n );\n'})}),"\n",(0,s.jsx)(n.h2,{id:"choosing-the-sort-fields",children:"Choosing the sort fields"}),"\n",(0,s.jsxs)(n.p,{children:["If you use the ",(0,s.jsx)(n.code,{children:"UseSort()"})," method (not the attribute) you can pass in an expression which tells the extension which fields to set in the input type. Make sure you use the correct type for the fields collection. You can still set the default sort as well."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional sorted")\n // available sort fields\n .UseSort((Person person) => new\n {\n person.Dob,\n person.LastName,\n manager = person.Manager.Name\n },\n // Default sort\n (Person person) => person.Dob, SortDirectionEnum.DESC);\n'})}),"\n",(0,s.jsx)(n.p,{children:"This will result in only 2 options for sorting."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:"input PeopleSortInput {\n dob: SortDirectionEnum\n lastName: SortDirectionEnum\n}\n"})})]})}function p(e={}){const{wrapper:n}={...(0,o.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},1151:(e,n,t)=>{t.d(n,{Z:()=>l,a:()=>r});var s=t(7294);const o={},i=s.createContext(o);function r(e){const n=s.useContext(i);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bf0849ee.8d0353cc.js b/assets/js/bf0849ee.8d0353cc.js deleted file mode 100644 index 56b6fbc..0000000 --- a/assets/js/bf0849ee.8d0353cc.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[3469],{573:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>d,frontMatter:()=>r,metadata:()=>l,toc:()=>o});var n=a(5893),s=a(1151);const r={sidebar_position:3},i="Scalar Types",l={id:"schema-creation/scalar-types",title:"Scalar Types",description:"We learnt previously that the GraphQL spec defines the following built in scalar types.",source:"@site/docs/schema-creation/scalar-types.md",sourceDirName:"schema-creation",slug:"/schema-creation/scalar-types",permalink:"/docs/schema-creation/scalar-types",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/schema-creation/scalar-types.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Mutations",permalink:"/docs/schema-creation/mutations"},next:{title:"Enum Types",permalink:"/docs/schema-creation/enum-types"}},c={},o=[];function h(e){const t={a:"a",code:"code",h1:"h1",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.h1,{id:"scalar-types",children:"Scalar Types"}),"\n",(0,n.jsxs)(t.p,{children:["We learnt previously that the ",(0,n.jsx)(t.a,{href:"https://graphql.org/learn/schema/#scalar-types",children:"GraphQL spec"})," defines the following built in scalar types."]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Int"}),": A signed 32\u2010bit integer."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Float"}),": A signed double-precision floating-point value."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"String"}),": A UTF\u20108 character sequence."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Boolean"}),": true or false."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"ID"}),": The ID scalar type represents a unique identifier. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human\u2010readable."]}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"We of course can add our own. Scalar types help you describe the data in you schema. Unlike Object types they don't have fields you can query, they result in data. Ultimately you are likely serializing the data to JSON for transport."}),"\n",(0,n.jsxs)(t.p,{children:["Adding a scalar type tells EntityGraphQL that the object should just be returned. i.e. there is no selection available on it. A good example is ",(0,n.jsx)(t.code,{children:"DateTime"}),". We just want to return the ",(0,n.jsx)(t.code,{children:"DateTime"})," value. Not have it as an Object type where you could select certain properties from it (Although you could set that up)."]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-cs",children:'schema.AddScalarType("DateTime", "Represents a date and time.");\n'})}),"\n",(0,n.jsx)(t.p,{children:"EntityGraphQL by default will set up the follow scalar types on schema creation."}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-cs",children:'new SchemaType(this, "Int", "Int scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "Float", "Float scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "Boolean", "Boolean scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "String", "String scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "ID", "ID scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "Char", "Char scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "Date", "Date with time scalar", null, GqlTypeEnum.Scalar);\nnew SchemaType(this, "DateTimeOffset", "DateTimeOffset scalar", null, GqlTypeEnum.Scalar)\n'})}),"\n",(0,n.jsx)(t.p,{children:"It is best to have scalar types added to the schema before adding other fields that reference them. Otherwise EntityGraphQL doesn't know about the scalar types. You can add you're own or make changes to the default when registering your schema."}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-cs",children:'services.AddGraphQLSchema(options => {\n options.PreBuildSchemaFromContext = schema =>\n {\n // remove and/or add scalar types or mappings here. e.g.\n schema.RemoveType();\n schema.AddScalarType>("StringKeyValuePair", "Represents a pair of strings");\n };\n})\n'})}),"\n",(0,n.jsxs)(t.p,{children:["You can also tell EntityGraphQL to auto-map a dotnet type to a schema type with ",(0,n.jsx)(t.code,{children:"AddTypeMapping(string gqlType)"}),". For example"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-cs",children:'schema.AddTypeMapping("Int");\n'})}),"\n",(0,n.jsxs)(t.p,{children:["By default EntityGraphQL maps these types to GraphQL types (Note ",(0,n.jsx)(t.code,{children:"int"}),", ",(0,n.jsx)(t.code,{children:"bool"}),", etc are not here as they are added as scalar types in the schema above)."]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-cs",children:"sbyte -> Int\nshort -> Int\nushort -> Int\nlong -> Int\nulong -> Int\nbyte -> Int\nuint -> Int\nfloat -> Float\ndecimal -> Float\nbyte[] -> String\n"})})]})}function d(e={}){const{wrapper:t}={...(0,s.a)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},1151:(e,t,a)=>{a.d(t,{Z:()=>l,a:()=>i});var n=a(7294);const s={},r=n.createContext(s);function i(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/bf0849ee.d75d2f69.js b/assets/js/bf0849ee.d75d2f69.js new file mode 100644 index 0000000..46341a5 --- /dev/null +++ b/assets/js/bf0849ee.d75d2f69.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[3469],{573:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>i,default:()=>h,frontMatter:()=>r,metadata:()=>c,toc:()=>o});var n=t(5893),s=t(1151);const r={sidebar_position:3},i="Scalar Types",c={id:"schema-creation/scalar-types",title:"Scalar Types",description:"We learnt previously that the GraphQL spec defines the following built in scalar types.",source:"@site/docs/schema-creation/scalar-types.md",sourceDirName:"schema-creation",slug:"/schema-creation/scalar-types",permalink:"/docs/schema-creation/scalar-types",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/schema-creation/scalar-types.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Mutations",permalink:"/docs/schema-creation/mutations"},next:{title:"Enum Types",permalink:"/docs/schema-creation/enum-types"}},l={},o=[];function d(e){const a={a:"a",code:"code",h1:"h1",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(a.h1,{id:"scalar-types",children:"Scalar Types"}),"\n",(0,n.jsxs)(a.p,{children:["We learnt previously that the ",(0,n.jsx)(a.a,{href:"https://graphql.org/learn/schema/#scalar-types",children:"GraphQL spec"})," defines the following built in scalar types."]}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"Int"}),": A signed 32\u2010bit integer."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"Float"}),": A signed double-precision floating-point value."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"String"}),": A UTF\u20108 character sequence."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"Boolean"}),": true or false."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"ID"}),": The ID scalar type represents a unique identifier. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human\u2010readable."]}),"\n"]}),"\n",(0,n.jsx)(a.p,{children:"We of course can add our own. Scalar types help you describe the data in you schema. Unlike Object types they don't have fields you can query, they result in data. Ultimately you are likely serializing the data to JSON for transport."}),"\n",(0,n.jsxs)(a.p,{children:["Adding a scalar type tells EntityGraphQL that the object should just be returned. i.e. there is no selection available on it. A good example is ",(0,n.jsx)(a.code,{children:"DateTime"}),". We just want to return the ",(0,n.jsx)(a.code,{children:"DateTime"})," value. Not have it as an Object type where you could select certain properties from it (Although you could set that up)."]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-cs",children:'schema.AddScalarType("DateTime", "Represents a date and time.");\n'})}),"\n",(0,n.jsx)(a.p,{children:"EntityGraphQL by default will set up the follow scalar types on schema creation."}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-cs",children:'AddScalarType("Int", "Int scalar");\nAddScalarType("Float", "Float scalar");\nAddScalarType("Boolean", "Boolean scalar");\nAddScalarType("String", "String scalar");\nAddScalarType("ID", "ID scalar");\nAddScalarType("Char", "Char scalar");\n\nAddScalarType("Date", "Date with time scalar");\nAddScalarType("DateTimeOffset", "DateTimeOffset scalar");\nAddScalarType("DateOnly", "Date value only scalar");\nAddScalarType("TimeOnly", "Time value only scalar");\n'})}),"\n",(0,n.jsx)(a.p,{children:"It is best to have scalar types added to the schema before adding other fields that reference them. Otherwise EntityGraphQL doesn't know about the scalar types. You can add you're own or make changes to the default when registering your schema."}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-cs",children:'services.AddGraphQLSchema(options => {\n options.PreBuildSchemaFromContext = schema =>\n {\n // remove and/or add scalar types or mappings here. e.g.\n schema.RemoveType();\n schema.AddScalarType>("StringKeyValuePair", "Represents a pair of strings");\n };\n})\n'})}),"\n",(0,n.jsxs)(a.p,{children:["You can also tell EntityGraphQL to auto-map a dotnet type to a schema type with ",(0,n.jsx)(a.code,{children:"AddTypeMapping(string gqlType)"}),". For example"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-cs",children:'schema.AddTypeMapping("Int");\n'})}),"\n",(0,n.jsxs)(a.p,{children:["By default EntityGraphQL maps these types to GraphQL types (Note ",(0,n.jsx)(a.code,{children:"int"}),", ",(0,n.jsx)(a.code,{children:"bool"}),", etc are not here as they are added as scalar types in the schema above)."]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-cs",children:"sbyte -> Int\nshort -> Int\nushort -> Int\nlong -> Int\nulong -> Int\nbyte -> Int\nuint -> Int\nfloat -> Float\ndecimal -> Float\nbyte[] -> String\n"})})]})}function h(e={}){const{wrapper:a}={...(0,s.a)(),...e.components};return a?(0,n.jsx)(a,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},1151:(e,a,t)=>{t.d(a,{Z:()=>c,a:()=>i});var n=t(7294);const s={},r=n.createContext(s);function i(e){const a=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(a):{...a,...e}}),[a,e])}function c(e){let a;return a=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),n.createElement(r.Provider,{value:a},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/d7ac533f.45e8fbb7.js b/assets/js/d7ac533f.546a32a2.js similarity index 67% rename from assets/js/d7ac533f.45e8fbb7.js rename to assets/js/d7ac533f.546a32a2.js index d6f17ae..93560f6 100644 --- a/assets/js/d7ac533f.45e8fbb7.js +++ b/assets/js/d7ac533f.546a32a2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[7025],{1058:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>r,default:()=>a,frontMatter:()=>l,metadata:()=>c,toc:()=>d});var s=i(5893),t=i(1151);const l={sidebar_position:1},r="Filtering",c={id:"field-extensions/filtering",title:"Filtering",description:"To quickly add filtering capabilities to your collection fields use the UseFilter() field extension.",source:"@site/docs/field-extensions/filtering.md",sourceDirName:"field-extensions",slug:"/field-extensions/filtering",permalink:"/docs/field-extensions/filtering",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/field-extensions/filtering.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Field Extensions",permalink:"/docs/field-extensions/"},next:{title:"Sorting",permalink:"/docs/field-extensions/sorting"}},o={},d=[];function h(e){const n={code:"code",h1:"h1",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"filtering",children:"Filtering"}),"\n",(0,s.jsxs)(n.p,{children:["To quickly add filtering capabilities to your collection fields use the ",(0,s.jsx)(n.code,{children:"UseFilter()"})," field extension."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional filtered")\n .UseFilter();\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If you are using the ",(0,s.jsx)(n.code,{children:"SchemaBuilder.FromObject"})," you can use the ",(0,s.jsx)(n.code,{children:"UseFilterAttribute"})," on your collection properties."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class DemoContext : DbContext\n{\n [UseFilter]\n public DbSet Movies { get; set; }\n [UseFilter]\n public DbSet People { get; set; }\n [UseFilter]\n public DbSet Actors { get; set; }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This field extension can only be used on a field that has a ",(0,s.jsx)(n.code,{children:"Resolve"})," expression that is assignable to ",(0,s.jsx)(n.code,{children:"IEnumerable"})," - I.e. collections. The extension adds an argument called ",(0,s.jsx)(n.code,{children:"filter: String"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Note: When using with the paging or sort extensions ensure you call ",(0,s.jsx)(n.code,{children:"UseFilter"})," before both others. If you are using the attribute, then ensure the Filter attribute comes before the other attributes."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"filter"})," argument takes a string that will be compiled to an expression and inserted into a ",(0,s.jsx)(n.code,{children:"Where()"})," call. The expression is compiled against your schema and the context is the type of elements in the collection."]}),"\n",(0,s.jsxs)(n.p,{children:["For example, given ",(0,s.jsx)(n.code,{children:"ctx => ctx.People"})," returns a ",(0,s.jsx)(n.code,{children:"IEnumerable"})," and ",(0,s.jsx)(n.code,{children:"Person"})," is defined as:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class Person\n{\n public uint Id { get; set; }\n public string FirstName { get; set; }\n public string LastName { get; set; }\n public DateTime Dob { get; set; }\n public List ActorIn { get; set; }\n public List WriterOf { get; set; }\n public List DirectorOf { get; set; }\n public DateTime? Died { get; set; }\n public bool IsDeleted { get; set; }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"We can write some filter expressions like so:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:'{\n people(filter: "id == 12 || id == 10") {\n firstName\n }\n}\n\n{\n deletedPeople: people(filter: "isDeleted == true") {\n firstName\n }\n}\n\n{\n people(filter: "dob > \\"2010-08-11T00:00:00\\" && isDeleted == false") {\n firstName\n }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports the following constants:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Booleans - ",(0,s.jsx)(n.code,{children:"true"})," & ",(0,s.jsx)(n.code,{children:"false"})]}),"\n",(0,s.jsxs)(n.li,{children:["Integers - e.g. ",(0,s.jsx)(n.code,{children:"2"}),", ",(0,s.jsx)(n.code,{children:"-8"})]}),"\n",(0,s.jsxs)(n.li,{children:["Floats - e.g. ",(0,s.jsx)(n.code,{children:"0.2"}),", ",(0,s.jsx)(n.code,{children:"-8.3"})]}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"null"})}),"\n",(0,s.jsxs)(n.li,{children:["Strings - ",(0,s.jsx)(n.code,{children:'"within double quotes"'}),"; when representing a date, use an ISO 8601 format such as ",(0,s.jsx)(n.code,{children:'"2022-07-31T20:00:00"'})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports the following operators:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"-"})," - Subtraction"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"+"})," - Addition"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"*"})," - Multiply"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"/"})," - Divide"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"%"})," - Mod"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"^"})," - Power"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"=="})," - Equals"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"!="})," - Not Equals"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"<="})," - Less than or equal to"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:">="})," - Greater than or equal to"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"<"})," - Less than"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:">"})," - Greater than"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"or"})," or ",(0,s.jsx)(n.code,{children:"||"})," - Or"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"and"})," or ",(0,s.jsx)(n.code,{children:"&&"})," - And"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports the following methods, these are called against fields within the filter context:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.any(filter)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if any of the items in the list match the filter. The filter within ",(0,s.jsx)(n.code,{children:"any"})," is on the context of the list item type. Otherwise ",(0,s.jsx)(n.code,{children:"false"})]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n # In C# - people.Where(p => p.ActorIn.Any(a => a.Name == "Star Wars"))\n people(filter: "actorIn.any(name == \\"Star Wars\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.count(filter?)"})," - Return the count of a list. Optionally counting items that match a filter"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n # No filter - all people that acted in 3 movies\n people(filter: "actorIn.count() == 3") { ... }\n\n\n # Count only those that match the filter - all people that acted in any movie starting with "Star"\n people(filter: "actorIn.count(name.startsWith(\\"Star\\")) > 0") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.first(filter?)"})," - Return the first item from a list. Optionally by a filter"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.last(filter?)"})," - Return the last item from a list. Optionally by a filter"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.take(int)"})," - Return the first ",(0,s.jsx)(n.code,{children:"x"})," items"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.skip(int)"})," - Return the items after ",(0,s.jsx)(n.code,{children:"x"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.orderBy(field)"})," - Order the list by a given field"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.orderByDesc(field)"})," - Order the list in reverse by a given field"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.where(filter)"}),", or ",(0,s.jsx)(n.code,{children:"List.filter(filter)"})," - Filter the list"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.contains(string)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if the specified string occurs in this string instance"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.contains(\\"o\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.startsWith(string)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if the beginning of this string instance matches the specified string"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.startsWith(\\"b\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.endsWith(string)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if the end of this string instance matches the specified string"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.endsWith(\\"b\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.toLower()"})," - Return the string converted to lowercase"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.toLower() == \\"bob\\"") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.toUpper()"})," - Return the string converted to uppercase"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.toUpper() == \\"BOB\\"") { ... }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports ternary and conditional:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"__ ? __ : __"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"if __ then __ else __"})}),"\n"]})]})}function a(e={}){const{wrapper:n}={...(0,t.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},1151:(e,n,i)=>{i.d(n,{Z:()=>c,a:()=>r});var s=i(7294);const t={},l=s.createContext(t);function r(e){const n=s.useContext(l);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(l.Provider,{value:n},e.children)}}}]); \ No newline at end of file +"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[7025],{1058:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>r,default:()=>a,frontMatter:()=>l,metadata:()=>c,toc:()=>d});var s=i(5893),t=i(1151);const l={sidebar_position:1},r="Filtering",c={id:"field-extensions/filtering",title:"Filtering",description:"To quickly add filtering capabilities to your collection fields use the UseFilter() field extension.",source:"@site/docs/field-extensions/filtering.md",sourceDirName:"field-extensions",slug:"/field-extensions/filtering",permalink:"/docs/field-extensions/filtering",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/field-extensions/filtering.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Field Extensions",permalink:"/docs/field-extensions/"},next:{title:"Sorting",permalink:"/docs/field-extensions/sorting"}},o={},d=[];function h(e){const n={code:"code",h1:"h1",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"filtering",children:"Filtering"}),"\n",(0,s.jsxs)(n.p,{children:["To quickly add filtering capabilities to your collection fields use the ",(0,s.jsx)(n.code,{children:"UseFilter()"})," field extension."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:'schema.ReplaceField("people",\n ctx => ctx.People,\n "Return a list of people. Optional filtered")\n .UseFilter();\n'})}),"\n",(0,s.jsxs)(n.p,{children:["If you are using the ",(0,s.jsx)(n.code,{children:"SchemaBuilder.FromObject"})," you can use the ",(0,s.jsx)(n.code,{children:"UseFilterAttribute"})," on your collection properties."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class DemoContext : DbContext\n{\n [UseFilter]\n public DbSet Movies { get; set; }\n [UseFilter]\n public DbSet People { get; set; }\n [UseFilter]\n public DbSet Actors { get; set; }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This field extension can only be used on a field that has a ",(0,s.jsx)(n.code,{children:"Resolve"})," expression that is assignable to ",(0,s.jsx)(n.code,{children:"IEnumerable"})," - I.e. collections. The extension adds an argument called ",(0,s.jsx)(n.code,{children:"filter: String"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Note: When using with the paging or sort extensions ensure you call ",(0,s.jsx)(n.code,{children:"UseFilter"})," before both others. If you are using the attribute, then ensure the Filter attribute comes before the other attributes."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"filter"})," argument takes a string that will be compiled to an expression and inserted into a ",(0,s.jsx)(n.code,{children:"Where()"})," call. The expression is compiled against your schema and the context is the type of elements in the collection."]}),"\n",(0,s.jsxs)(n.p,{children:["For example, given ",(0,s.jsx)(n.code,{children:"ctx => ctx.People"})," returns a ",(0,s.jsx)(n.code,{children:"IEnumerable"})," and ",(0,s.jsx)(n.code,{children:"Person"})," is defined as:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-cs",children:"public class Person\n{\n public uint Id { get; set; }\n public string FirstName { get; set; }\n public string LastName { get; set; }\n public DateTime Dob { get; set; }\n public List ActorIn { get; set; }\n public List WriterOf { get; set; }\n public List DirectorOf { get; set; }\n public DateTime? Died { get; set; }\n public bool IsDeleted { get; set; }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"We can write some filter expressions like so:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-graphql",children:'{\n people(filter: "id == 12 || id == 10") {\n firstName\n }\n}\n\n{\n deletedPeople: people(filter: "isDeleted == true") {\n firstName\n }\n}\n\n{\n people(filter: "dob > \\"2010-08-11T00:00:00\\" && isDeleted == false") {\n firstName\n }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports the following constants:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Booleans - ",(0,s.jsx)(n.code,{children:"true"})," & ",(0,s.jsx)(n.code,{children:"false"})]}),"\n",(0,s.jsxs)(n.li,{children:["Integers - e.g. ",(0,s.jsx)(n.code,{children:"2"}),", ",(0,s.jsx)(n.code,{children:"-8"})]}),"\n",(0,s.jsxs)(n.li,{children:["Floats - e.g. ",(0,s.jsx)(n.code,{children:"0.2"}),", ",(0,s.jsx)(n.code,{children:"-8.3"})]}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"null"})}),"\n",(0,s.jsxs)(n.li,{children:["Strings - ",(0,s.jsx)(n.code,{children:'"within double quotes"'}),"; when representing a date, use an ISO 8601 format such as ",(0,s.jsx)(n.code,{children:'"2022-07-31T20:00:00"'})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports the following operators:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"-"})," - Subtraction"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"+"})," - Addition"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"*"})," - Multiply"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"/"})," - Divide"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"%"})," - Mod"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"^"})," - Power"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"=="})," - Equals"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"!="})," - Not Equals"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"<="})," - Less than or equal to"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:">="})," - Greater than or equal to"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"<"})," - Less than"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:">"})," - Greater than"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"or"})," or ",(0,s.jsx)(n.code,{children:"||"})," - Or"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"and"})," or ",(0,s.jsx)(n.code,{children:"&&"})," - And"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports the following methods, these are called against fields within the filter context:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.any(filter)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if any of the items in the list match the filter. The filter within ",(0,s.jsx)(n.code,{children:"any"})," is on the context of the list item type. Otherwise ",(0,s.jsx)(n.code,{children:"false"})]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n # In C# - people.Where(p => p.ActorIn.Any(a => a.Name == "Star Wars"))\n people(filter: "actorIn.any(name == \\"Star Wars\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.count(filter?)"})," - Return the count of a list. Optionally counting items that match a filter"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n # No filter - all people that acted in 3 movies\n people(filter: "actorIn.count() == 3") { ... }\n\n\n # Count only those that match the filter - all people that acted in any movie starting with "Star"\n people(filter: "actorIn.count(name.startsWith(\\"Star\\")) > 0") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.first(filter?)"})," / ",(0,s.jsx)(n.code,{children:"List.firstOrDefault(filter?)"})," - Return the first item from a list. Optionally by a filter"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.last(filter?)"})," / ",(0,s.jsx)(n.code,{children:"List.lastOrDefault(filter?)"})," - Return the last item from a list. Optionally by a filter"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.take(int)"})," - Return the first ",(0,s.jsx)(n.code,{children:"x"})," items"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.skip(int)"})," - Return the items after ",(0,s.jsx)(n.code,{children:"x"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.orderBy(field)"})," - Order the list by a given field"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.orderByDesc(field)"})," - Order the list in reverse by a given field"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"List.where(filter)"}),", or ",(0,s.jsx)(n.code,{children:"List.filter(filter)"})," - Filter the list"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.contains(string)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if the specified string occurs in this string instance"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.contains(\\"o\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.startsWith(string)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if the beginning of this string instance matches the specified string"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.startsWith(\\"b\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.endsWith(string)"})," - Return ",(0,s.jsx)(n.code,{children:"true"})," if the end of this string instance matches the specified string"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.endsWith(\\"b\\")") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.toLower()"})," - Return the string converted to lowercase"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.toLower() == \\"bob\\"") { ... }\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"string.toUpper()"})," - Return the string converted to uppercase"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-gql",children:'{\n people(filter: "firstName.toUpper() == \\"BOB\\"") { ... }\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"The expression language supports ternary and conditional:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"__ ? __ : __"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"if __ then __ else __"})}),"\n"]})]})}function a(e={}){const{wrapper:n}={...(0,t.a)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},1151:(e,n,i)=>{i.d(n,{Z:()=>c,a:()=>r});var s=i(7294);const t={},l=s.createContext(t);function r(e){const n=s.useContext(l);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(l.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f78bd335.22b7fdcd.js b/assets/js/f78bd335.22b7fdcd.js deleted file mode 100644 index a5503e2..0000000 --- a/assets/js/f78bd335.22b7fdcd.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[5956],{375:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var s=n(5893),i=n(1151);const r={sidebar_position:1},o="Entity Framework",a={id:"library-compatibility/entity-framework",title:"Entity Framework",description:"EntityGraphQL is built to work extremely well with EntityFramework. To see how let's first look at what EntityGraphQL does with GraphQL queries.",source:"@site/docs/library-compatibility/entity-framework.md",sourceDirName:"library-compatibility",slug:"/library-compatibility/entity-framework",permalink:"/docs/library-compatibility/entity-framework",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/library-compatibility/entity-framework.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Extension Attribute",permalink:"/docs/other-extensibility/extension-attribute"},next:{title:"Caching",permalink:"/docs/library-compatibility/caching"}},l={},c=[{value:"How EntityGraphQL handles services / Resolve<TService>()",id:"how-entitygraphql-handles-services--resolvetservice",level:2}];function h(e){const t={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",p:"p",pre:"pre",...(0,i.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.h1,{id:"entity-framework",children:"Entity Framework"}),"\n",(0,s.jsx)(t.p,{children:"EntityGraphQL is built to work extremely well with EntityFramework. To see how let's first look at what EntityGraphQL does with GraphQL queries."}),"\n",(0,s.jsx)(t.h1,{id:"examples",children:"Examples"}),"\n",(0,s.jsxs)(t.p,{children:["Using the ",(0,s.jsx)(t.code,{children:"DemoContext"})," and the schema we created from the Getting Started section, lets look at the sample queries."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-graphql",children:"query {\n movie(id: 11) {\n id\n name\n }\n}\n"})}),"\n",(0,s.jsx)(t.p,{children:"EntityGraphQL parses the GQL document into an internal representation and uses the schema we built to construct a .NET expression. It will look like this."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cs",children:"var expression = (DemoContext ctx, AnonymousType<> args) =>\n ctx.Movies\n .Where(movie => movie.Id == args.id)\n .Select(movie => new {\n id = movie.Id,\n name = movie.Name\n })\n .FirstOrDefault();\n"})}),"\n",(0,s.jsxs)(t.p,{children:["You can see all we need to execute this expression is an instance of ",(0,s.jsx)(t.code,{children:"DemoContext"})," and the ",(0,s.jsx)(t.code,{children:"args"})," object which is built by EntityGraphQL on parsing of the GQL document. Given those things EntityGraphQL can do similar to this."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cs",children:"var results = expression.Compile().DynamicInvoke(demoContextInstance, argInstance);\n"})}),"\n",(0,s.jsxs)(t.p,{children:["Now if your ",(0,s.jsx)(t.code,{children:"DemoContext"})," is built on top of Entity Framework ",(0,s.jsx)(t.code,{children:"DbContext"})," when EntityGraphQL executes the expression EF will take over and do its thing!"]}),"\n",(0,s.jsx)(t.p,{children:"Namely note that EntityGraphQL only selects the fields asked for and therefore EF will also only return the fields we ask for. Meaning no over fetching to the DB either. If your table had many fields and some large ones, they are not selected from the DB unless the API user asks for those fields."}),"\n",(0,s.jsx)(t.p,{children:"Let's look at a more complicated example."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-graphql",children:"{\n movies {\n id\n name\n director {\n name\n }\n writers {\n name\n }\n }\n}\n"})}),"\n",(0,s.jsx)(t.p,{children:"Will result in the following expression."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cs",children:"var expression = (DemoContext ctx) =>\n ctx.Movies\n .Select(movie => new {\n id = movie.Id,\n name = movie.Name,\n director = new {\n name = movie.Director.Name\n },\n writers = movie.Writers.Select(writer => new {\n name = writer.Name\n })\n });\n"})}),"\n",(0,s.jsx)(t.p,{children:"Again, EF will take over and fetch your data for you."}),"\n",(0,s.jsxs)(t.p,{children:["You'll note that EntityGraphQL doesn't care what the context is. It could be a object graph 100% held in memory. What does matter is that when the expression executes and resolves something like ",(0,s.jsx)(t.code,{children:"movie.Writers.Select()"})," that the object has the expected data loaded, or like EF can resolve the data."]}),"\n",(0,s.jsxs)(t.p,{children:["Other ORMs built on top of ",(0,s.jsx)(t.code,{children:"LinqProvider"})," and ",(0,s.jsx)(t.code,{children:"IQueryable"})," should also work although have not been tested."]}),"\n",(0,s.jsx)(t.h2,{id:"how-entitygraphql-handles-services--resolvetservice",children:"How EntityGraphQL handles services / Resolve()"}),"\n",(0,s.jsxs)(t.p,{children:["Since using EntityGraphQL against an Entity Framework Core ",(0,s.jsx)(t.code,{children:"DbContext"})," is supported we handle ",(0,s.jsx)(t.code,{children:"Resolve()"})," in a way that will work with EF Core (and possibly other IQueryable based ORMs) which allows EF to build an optimal SQL statement. EF core 3.1+ will throw an error by default if it can't translate an expression to SQL. It can't translate the services used in ",(0,s.jsx)(t.code,{children:"Resolve()"})," to SQL. To support EF 3.1+ performing optimal queries (and selecting only the fields you request) EntityGraphQL builds and executes the expressions in 2 parts."]}),"\n",(0,s.jsx)(t.p,{children:(0,s.jsxs)(t.em,{children:["This can be disabled by setting the argument ",(0,s.jsx)(t.code,{children:"ExecuteServiceFieldsSeparately"})," when executing to ",(0,s.jsx)(t.code,{children:"false"}),". For example if your core context is an in memory object."]})}),"\n",(0,s.jsxs)(t.p,{children:["If you encounter any issues when using ",(0,s.jsx)(t.code,{children:"Resolve()"})," on fields and EF Core 3.1+ please raise an issue."]}),"\n",(0,s.jsxs)(t.p,{children:["Example of how EntityGraphQL handles ",(0,s.jsx)(t.code,{children:"Resolve()"}),", which can help inform how you build/use other services."]}),"\n",(0,s.jsx)(t.p,{children:"Given the following GQL"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-graphql",children:"{\n people {\n age\n manager {\n name\n }\n }\n}\n"})}),"\n",(0,s.jsxs)(t.p,{children:["Where ",(0,s.jsx)(t.code,{children:"age"})," is defined with a service as"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cs",children:'schema.Type().AddField("age", "Persons age")\n .Resolve((person, ager) => ager.GetAge(person.Birthday));\n'})}),"\n",(0,s.jsxs)(t.p,{children:["EntityGraphQL will build an expression query that first selects everything from the base context (",(0,s.jsx)(t.code,{children:"DemoContext"})," in this case) that EF can execute. Then another expression query that runs on top of that result which includes the ",(0,s.jsx)(t.code,{children:"Resolve()"})," fields. This means EF can optimize your query and return all the data requested (and nothing more) and in memory we then merge that with data from your services."]}),"\n",(0,s.jsx)(t.p,{children:"An example in C# of what this ends up looking like."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cs",children:"var dbResultFunc = (DbContext context) => context.People.Select(p => new {\n p_Birthday = p.Birthday, // extracted from the Resolve expression as it is needed in the in-memory resolution\n manager = new {\n name = p.Manager.Name\n }\n})\n.ToList(); // EF will fetch data\nvar dbResult = dbResultFunc(dbContext); // executes the expression\n\n// note dbResult is an anonymous type known at runtime\nvar resultsFunc = (AnonType dbResult, AgeService ager) => dbResult.Select(p => {\n age = ager.GetAge(p.p_Birthday), // passing in data we selected just for this\n manager = p.manager // simple selection from the previous result\n})\n.ToList();\nvar results = resultsFunc(dbResult, ager); // execute for the final result\n"})}),"\n",(0,s.jsx)(t.p,{children:"This allows EF Core to make its optimizations and prevent over-fetching of data when using EntityGraphQL against an EF DbContext."}),"\n",(0,s.jsxs)(t.p,{children:["As seen above EntityGraphQL will execute 2 expressions. The first with all data on the main query context (in this case the ",(0,s.jsx)(t.code,{children:"DbContext"}),") without the service fields and the second against the result of that query including the service fields."]}),"\n",(0,s.jsx)(t.p,{children:"To do this EntityGraphQL needs to update the service field expressions. It does that by first extracting all the expressions form a service that relate to the main query context. For example"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cs",children:'schema.UpdateType(type => {\n type.AddField("floorUrl", "Current floor url")\n .Resolve((floor, srv) => s.BuildFloorPlanUrl(f.SomeRelation.FirstOrDefault().Id));\n});\n'})}),"\n",(0,s.jsxs)(t.p,{children:["Will extract the ",(0,s.jsx)(t.code,{children:"f.SomeRelation.FirstOrDefault().Id"})," expression. That will be fetched in the first expression execution as ",(0,s.jsx)(t.code,{children:"f.SomeRelation_FirstOrDefault___Id = f.SomeRelation.FirstOrDefault().Id"}),". So when we rebuild the final expression to also execute service fields it will update the expression to be ",(0,s.jsx)(t.code,{children:"s.BuildFloorPlanUrl(f.SomeRelation_FirstOrDefault___Id)"}),"."]}),"\n",(0,s.jsxs)(t.p,{children:["You may encounter some issues with EF depending on how complex you expressions are. For example pre-6.0 ",(0,s.jsx)(t.a,{href:"https://github.com/dotnet/efcore/issues/23205",children:"this issue"})," will be hit if you traverse through a relation."]}),"\n",(0,s.jsx)(t.p,{children:"It is best to keep the expressions used to pass query context data into a service as simple as you can. Remember, your services can also access the DB context or anything else they need via DI."})]})}function d(e={}){const{wrapper:t}={...(0,i.a)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},1151:(e,t,n)=>{n.d(t,{Z:()=>a,a:()=>o});var s=n(7294);const i={},r=s.createContext(i);function o(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f78bd335.5e441950.js b/assets/js/f78bd335.5e441950.js new file mode 100644 index 0000000..176bd98 --- /dev/null +++ b/assets/js/f78bd335.5e441950.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkentity_graphql_docs=self.webpackChunkentity_graphql_docs||[]).push([[5956],{375:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var i=t(5893),s=t(1151);const r={sidebar_position:1},o="Entity Framework",a={id:"library-compatibility/entity-framework",title:"Entity Framework",description:"EntityGraphQL is built to work extremely well with EntityFramework. To see how let's first look at what EntityGraphQL does with GraphQL queries.",source:"@site/docs/library-compatibility/entity-framework.md",sourceDirName:"library-compatibility",slug:"/library-compatibility/entity-framework",permalink:"/docs/library-compatibility/entity-framework",draft:!1,unlisted:!1,editUrl:"https://github.com/EntityGraphQL/EntityGraphQL/tree/master/docs/docs/library-compatibility/entity-framework.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Extension Attribute",permalink:"/docs/other-extensibility/extension-attribute"},next:{title:"Caching",permalink:"/docs/library-compatibility/caching"}},l={},c=[{value:"How EntityGraphQL handles services / Resolve<TService>()",id:"how-entitygraphql-handles-services--resolvetservice",level:2}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",p:"p",pre:"pre",...(0,s.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h1,{id:"entity-framework",children:"Entity Framework"}),"\n",(0,i.jsx)(n.p,{children:"EntityGraphQL is built to work extremely well with EntityFramework. To see how let's first look at what EntityGraphQL does with GraphQL queries."}),"\n",(0,i.jsx)(n.h1,{id:"examples",children:"Examples"}),"\n",(0,i.jsxs)(n.p,{children:["Using the ",(0,i.jsx)(n.code,{children:"DemoContext"})," and the schema we created from the Getting Started section, lets look at the sample queries."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-graphql",children:"query {\n movie(id: 11) {\n id\n name\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"EntityGraphQL parses the GQL document into an internal representation and uses the schema we built to construct a .NET expression. It will look like this."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-cs",children:"var expression = (DemoContext ctx, AnonymousType<> args) =>\n ctx.Movies\n .Where(movie => movie.Id == args.id)\n .Select(movie => new {\n id = movie.Id,\n name = movie.Name\n })\n .FirstOrDefault();\n"})}),"\n",(0,i.jsxs)(n.p,{children:["You can see all we need to execute this expression is an instance of ",(0,i.jsx)(n.code,{children:"DemoContext"})," and the ",(0,i.jsx)(n.code,{children:"args"})," object which is built by EntityGraphQL on parsing of the GQL document. Given those things EntityGraphQL can do similar to this."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-cs",children:"var results = expression.Compile().DynamicInvoke(demoContextInstance, argInstance);\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now if your ",(0,i.jsx)(n.code,{children:"DemoContext"})," is built on top of Entity Framework ",(0,i.jsx)(n.code,{children:"DbContext"})," when EntityGraphQL executes the expression EF will take over and do its thing!"]}),"\n",(0,i.jsx)(n.p,{children:"Namely note that EntityGraphQL only selects the fields asked for and therefore EF will also only return the fields we ask for. Meaning no over fetching to the DB either. If your table had many fields and some large ones, they are not selected from the DB unless the API user asks for those fields."}),"\n",(0,i.jsx)(n.p,{children:"Let's look at a more complicated example."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-graphql",children:"{\n movies {\n id\n name\n director {\n name\n }\n writers {\n name\n }\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"Will result in the following expression."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-cs",children:"var expression = (DemoContext ctx) =>\n ctx.Movies\n .Select(movie => new {\n id = movie.Id,\n name = movie.Name,\n director = new {\n name = movie.Director.Name\n },\n writers = movie.Writers.Select(writer => new {\n name = writer.Name\n })\n });\n"})}),"\n",(0,i.jsx)(n.p,{children:"Again, EF will take over and fetch your data for you."}),"\n",(0,i.jsxs)(n.p,{children:["You'll note that EntityGraphQL doesn't care what the context is. It could be a object graph 100% held in memory. What does matter is that when the expression executes and resolves something like ",(0,i.jsx)(n.code,{children:"movie.Writers.Select()"})," that the object has the expected data loaded, or like EF can resolve the data."]}),"\n",(0,i.jsxs)(n.p,{children:["Other ORMs built on top of ",(0,i.jsx)(n.code,{children:"LinqProvider"})," and ",(0,i.jsx)(n.code,{children:"IQueryable"})," should also work although have not been tested."]}),"\n",(0,i.jsx)(n.h2,{id:"how-entitygraphql-handles-services--resolvetservice",children:"How EntityGraphQL handles services / Resolve()"}),"\n",(0,i.jsxs)(n.p,{children:["Since using EntityGraphQL against an Entity Framework Core ",(0,i.jsx)(n.code,{children:"DbContext"})," is supported we handle ",(0,i.jsx)(n.code,{children:"Resolve()"})," in a way that will work with EF Core (and possibly other IQueryable based ORMs) which allows EF to build an optimal SQL statement. EF core 3.1+ will throw an error by default if it can't translate an expression to SQL. It can't translate the services used in ",(0,i.jsx)(n.code,{children:"Resolve()"})," to SQL. To support EF 3.1+ performing optimal queries (and selecting only the fields you request) EntityGraphQL builds and executes the expressions in 2 parts."]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsxs)(n.em,{children:["This can be disabled by setting the argument ",(0,i.jsx)(n.code,{children:"ExecuteServiceFieldsSeparately"})," when executing to ",(0,i.jsx)(n.code,{children:"false"}),". For example if your core context is an in memory object."]})}),"\n",(0,i.jsxs)(n.p,{children:["If you encounter any issues when using ",(0,i.jsx)(n.code,{children:"Resolve()"})," on fields and EF Core 3.1+ please raise an issue."]}),"\n",(0,i.jsxs)(n.p,{children:["Example of how EntityGraphQL handles ",(0,i.jsx)(n.code,{children:"Resolve()"}),", which can help inform how you build/use other services."]}),"\n",(0,i.jsx)(n.p,{children:"Given the following GQL"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-graphql",children:"{\n people {\n age\n manager {\n name\n }\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Where ",(0,i.jsx)(n.code,{children:"age"})," is defined with a service as"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-cs",children:'schema.Type().AddField("age", "Persons age")\n .Resolve((person, ager) => ager.GetAge(person.Birthday));\n'})}),"\n",(0,i.jsxs)(n.p,{children:["EntityGraphQL will build an expression query that first selects everything from the base context (",(0,i.jsx)(n.code,{children:"DemoContext"})," in this case) that EF can execute. Then another expression query that runs on top of that result which includes the ",(0,i.jsx)(n.code,{children:"Resolve()"})," fields. This means EF can optimize your query and return all the data requested (and nothing more) and in memory we then merge that with data from your services."]}),"\n",(0,i.jsx)(n.p,{children:"An example in C# of what this ends up looking like."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-cs",children:"var dbResultFunc = (DbContext context) => context.People.Select(p => new {\n p_Birthday = p.Birthday, // extracted from the Resolve expression as it is needed in the in-memory resolution\n manager = new {\n name = p.Manager.Name\n }\n})\n.ToList(); // EF will fetch data\nvar dbResult = dbResultFunc(dbContext); // executes the expression\n\n// note dbResult is an anonymous type known at runtime\nvar resultsFunc = (AnonType dbResult, AgeService ager) => dbResult.Select(p => {\n age = ager.GetAge(p.p_Birthday), // passing in data we selected just for this\n manager = p.manager // simple selection from the previous result\n})\n.ToList();\nvar results = resultsFunc(dbResult, ager); // execute for the final result\n"})}),"\n",(0,i.jsx)(n.p,{children:"This allows EF Core to make its optimizations and prevent over-fetching of data when using EntityGraphQL against an EF DbContext."}),"\n",(0,i.jsxs)(n.p,{children:["As seen above EntityGraphQL will execute 2 expressions. The first with all data on the main query context (in this case the ",(0,i.jsx)(n.code,{children:"DbContext"}),") without the service fields and the second against the result of that query including the service fields."]}),"\n",(0,i.jsx)(n.p,{children:"To do this EntityGraphQL needs to update the service field expressions. It does that by first extracting all the expressions form a service that relate to the main query context. For example"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-cs",children:'schema.UpdateType(type => {\n type.AddField("floorUrl", "Current floor url")\n .Resolve((floor, srv) => s.BuildFloorPlanUrl(f.SomeRelation.FirstOrDefault().Id));\n});\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Will extract the ",(0,i.jsx)(n.code,{children:"f.SomeRelation.FirstOrDefault().Id"})," expression. That will be fetched in the first expression execution as ",(0,i.jsx)(n.code,{children:"f.SomeRelation_FirstOrDefault___Id = f.SomeRelation.FirstOrDefault().Id"}),". So when we rebuild the final expression to also execute service fields it will update the expression to be ",(0,i.jsx)(n.code,{children:"s.BuildFloorPlanUrl(f.SomeRelation_FirstOrDefault___Id)"}),"."]}),"\n",(0,i.jsxs)(n.p,{children:["You may encounter some issues with EF depending on how complex you expressions are. For example pre-6.0 ",(0,i.jsx)(n.a,{href:"https://github.com/dotnet/efcore/issues/23205",children:"this issue"})," will be hit if you traverse through a relation."]}),"\n",(0,i.jsx)(n.p,{children:"It is best to keep the expressions used to pass query context data into a service as simple as you can. Remember, your services can also access the DB context or anything else they need via DI."}),"\n",(0,i.jsx)(n.h1,{id:"linking-entityframework-queries-to-graphql-operations",children:"Linking EntityFramework queries to GraphQL operations"}),"\n",(0,i.jsxs)(n.p,{children:["If you want to better understand which EF query is being run from which GraphQL operation you can use the ",(0,i.jsx)(n.code,{children:"ExecutionOptions.BeforeRootFieldExpressionBuild"})," callback to add the EF ",(0,i.jsx)(n.a,{href:"https://docs.microsoft.com/en-us/ef/core/querying/tags",children:(0,i.jsx)(n.code,{children:"TagWith"})})," extension method to the query. Below is an example using the ",(0,i.jsx)(n.code,{children:"MapGraphQL"})," ASP.NET helper."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-c#",children:'app.UseEndpoints(endpoints =>\n{\n endpoints.MapGraphQL(options: new ExecutionOptions\n {\n BeforeRootFieldExpressionBuild = (exp, op, field) =>\n {\n if (exp.Type.IsGenericTypeQueryable())\n return Expression.Call(typeof(EntityFrameworkQueryableExtensions), nameof(EntityFrameworkQueryableExtensions.TagWith), [exp.Type.GetGenericArguments()[0]], exp, Expression.Constant($"GQL op: {op ?? "n/a"}, field: {field}"));\n return exp;\n }\n });\n});\n'})})]})}function d(e={}){const{wrapper:n}={...(0,s.a)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},1151:(e,n,t)=>{t.d(n,{Z:()=>a,a:()=>o});var i=t(7294);const s={},r=i.createContext(s);function o(e){const n=i.useContext(r);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:o(e.components),i.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.a9c5bdb0.js b/assets/js/runtime~main.9a6a948b.js similarity index 96% rename from assets/js/runtime~main.a9c5bdb0.js rename to assets/js/runtime~main.9a6a948b.js index d136133..2ff9b6d 100644 --- a/assets/js/runtime~main.a9c5bdb0.js +++ b/assets/js/runtime~main.9a6a948b.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,t,f,r,c={},d={};function o(e){var a=d[e];if(void 0!==a)return a.exports;var t=d[e]={exports:{}};return c[e].call(t.exports,t,t.exports,o),t.exports}o.m=c,e=[],o.O=(a,t,f,r)=>{if(!t){var c=1/0;for(i=0;i=r)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(d=!1,r0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[t,f,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var c={};a=a||[null,t({}),t([]),t(t)];for(var d=2&f&&e;"object"==typeof d&&!~a.indexOf(d);d=t(d))Object.getOwnPropertyNames(d).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,o.d(r,c),r},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({53:"935f2afb",442:"8ef82481",772:"518b9fff",1039:"c03aac5d",1292:"886e8b41",2981:"ec3cffb5",3039:"a0a062d9",3228:"a9c57e4d",3469:"bf0849ee",3480:"022b91d8",3601:"55c0b115",4121:"02d4e9f5",4167:"031e8579",4195:"c4f5d8e4",4211:"ec7fa3cf",4368:"a94703ab",5075:"def44da2",5167:"f7b6ead6",5956:"f78bd335",6058:"a9bc01a8",6120:"86d58bb8",6404:"ff3f7a37",6425:"e578344f",6495:"4620b467",6872:"80e9d2b0",7025:"d7ac533f",7162:"d589d3a7",7334:"82d7f41a",7541:"0a77e43a",7561:"01c6e266",7872:"4b7fc798",7916:"1813f2fc",7918:"17896441",7920:"1a4e3797",8134:"0c2e262b",8253:"ab12c7ba",8518:"a7bd4aaa",8642:"516f2f25",8745:"9ee1f4e6",9077:"e8fb0f5c",9234:"54866e57",9516:"e8c91c53",9607:"97f1710d",9661:"5e95c892",9671:"0e384e19"}[e]||e)+"."+{53:"a610add1",442:"bb4c337a",772:"57dfac64",1039:"5bc19210",1292:"01f3828a",1426:"79d2c62a",1772:"b9c906bf",2981:"1ce67d65",3039:"46abfecb",3228:"bca953d2",3469:"8d0353cc",3480:"034c18d4",3601:"16657396",4121:"a2386cbb",4167:"2eaf34ee",4195:"972ee624",4211:"341873fb",4368:"474fc3a7",5075:"7f0118f5",5167:"39487b39",5956:"22b7fdcd",6058:"65199095",6120:"b38f421c",6404:"7156b2b6",6425:"389dc00c",6495:"0a2ea2a2",6872:"4364788b",6945:"ca642fab",7025:"45e8fbb7",7162:"76567682",7334:"e72d463b",7541:"4de371a5",7561:"16badbbd",7872:"0f57d206",7916:"c80c6567",7918:"2860669b",7920:"0ce1c3e4",8134:"0863c6a1",8253:"944bbdf6",8518:"1651451b",8642:"0dc11452",8745:"8bb4ce14",8894:"4a68a7ce",9077:"14a702b4",9234:"7b9333aa",9286:"4a250510",9516:"35aa9b60",9607:"e79f19ee",9661:"fdb5898a",9671:"c44656b1"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},r="entity-graphql-docs:",o.l=(e,a,t,c)=>{if(f[e])f[e].push(a);else{var d,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var r=f[e];if(delete f[e],d.parentNode&&d.parentNode.removeChild(d),r&&r.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),b&&document.head.appendChild(d)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"7918","935f2afb":"53","8ef82481":"442","518b9fff":"772",c03aac5d:"1039","886e8b41":"1292",ec3cffb5:"2981",a0a062d9:"3039",a9c57e4d:"3228",bf0849ee:"3469","022b91d8":"3480","55c0b115":"3601","02d4e9f5":"4121","031e8579":"4167",c4f5d8e4:"4195",ec7fa3cf:"4211",a94703ab:"4368",def44da2:"5075",f7b6ead6:"5167",f78bd335:"5956",a9bc01a8:"6058","86d58bb8":"6120",ff3f7a37:"6404",e578344f:"6425","4620b467":"6495","80e9d2b0":"6872",d7ac533f:"7025",d589d3a7:"7162","82d7f41a":"7334","0a77e43a":"7541","01c6e266":"7561","4b7fc798":"7872","1813f2fc":"7916","1a4e3797":"7920","0c2e262b":"8134",ab12c7ba:"8253",a7bd4aaa:"8518","516f2f25":"8642","9ee1f4e6":"8745",e8fb0f5c:"9077","54866e57":"9234",e8c91c53:"9516","97f1710d":"9607","5e95c892":"9661","0e384e19":"9671"}[e]||e,o.p+o.u(e)},(()=>{var e={1303:0,532:0};o.f.j=(a,t)=>{var f=o.o(e,a)?e[a]:void 0;if(0!==f)if(f)t.push(f[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var r=new Promise(((t,r)=>f=e[a]=[t,r]));t.push(f[2]=r);var c=o.p+o.u(a),d=new Error;o.l(c,(t=>{if(o.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var r=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src;d.message="Loading chunk "+a+" failed.\n("+r+": "+c+")",d.name="ChunkLoadError",d.type=r,d.request=c,f[1](d)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var f,r,c=t[0],d=t[1],b=t[2],n=0;if(c.some((a=>0!==e[a]))){for(f in d)o.o(d,f)&&(o.m[f]=d[f]);if(b)var i=b(o)}for(a&&a(t);n{"use strict";var e,a,t,f,r,c={},d={};function o(e){var a=d[e];if(void 0!==a)return a.exports;var t=d[e]={exports:{}};return c[e].call(t.exports,t,t.exports,o),t.exports}o.m=c,e=[],o.O=(a,t,f,r)=>{if(!t){var c=1/0;for(i=0;i=r)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(d=!1,r0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[t,f,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var c={};a=a||[null,t({}),t([]),t(t)];for(var d=2&f&&e;"object"==typeof d&&!~a.indexOf(d);d=t(d))Object.getOwnPropertyNames(d).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,o.d(r,c),r},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({53:"935f2afb",442:"8ef82481",772:"518b9fff",1039:"c03aac5d",1292:"886e8b41",2981:"ec3cffb5",3039:"a0a062d9",3228:"a9c57e4d",3469:"bf0849ee",3480:"022b91d8",3601:"55c0b115",4121:"02d4e9f5",4167:"031e8579",4195:"c4f5d8e4",4211:"ec7fa3cf",4368:"a94703ab",5075:"def44da2",5167:"f7b6ead6",5956:"f78bd335",6058:"a9bc01a8",6120:"86d58bb8",6404:"ff3f7a37",6425:"e578344f",6495:"4620b467",6872:"80e9d2b0",7025:"d7ac533f",7162:"d589d3a7",7334:"82d7f41a",7541:"0a77e43a",7561:"01c6e266",7872:"4b7fc798",7916:"1813f2fc",7918:"17896441",7920:"1a4e3797",8134:"0c2e262b",8253:"ab12c7ba",8518:"a7bd4aaa",8642:"516f2f25",8745:"9ee1f4e6",9077:"e8fb0f5c",9234:"54866e57",9516:"e8c91c53",9607:"97f1710d",9661:"5e95c892",9671:"0e384e19"}[e]||e)+"."+{53:"a610add1",442:"bb4c337a",772:"57dfac64",1039:"5bc19210",1292:"01f3828a",1426:"79d2c62a",1772:"b9c906bf",2981:"1ce67d65",3039:"46abfecb",3228:"bca953d2",3469:"d75d2f69",3480:"034c18d4",3601:"0e6dc344",4121:"a2386cbb",4167:"2eaf34ee",4195:"972ee624",4211:"341873fb",4368:"474fc3a7",5075:"7f0118f5",5167:"39487b39",5956:"5e441950",6058:"65199095",6120:"b38f421c",6404:"7156b2b6",6425:"389dc00c",6495:"0a2ea2a2",6872:"4364788b",6945:"ca642fab",7025:"546a32a2",7162:"76567682",7334:"e72d463b",7541:"4de371a5",7561:"16badbbd",7872:"0f57d206",7916:"c80c6567",7918:"2860669b",7920:"0ce1c3e4",8134:"0863c6a1",8253:"944bbdf6",8518:"1651451b",8642:"0dc11452",8745:"8bb4ce14",8894:"4a68a7ce",9077:"14a702b4",9234:"7b9333aa",9286:"4a250510",9516:"35aa9b60",9607:"e79f19ee",9661:"fdb5898a",9671:"c44656b1"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},r="entity-graphql-docs:",o.l=(e,a,t,c)=>{if(f[e])f[e].push(a);else{var d,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var r=f[e];if(delete f[e],d.parentNode&&d.parentNode.removeChild(d),r&&r.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),b&&document.head.appendChild(d)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"7918","935f2afb":"53","8ef82481":"442","518b9fff":"772",c03aac5d:"1039","886e8b41":"1292",ec3cffb5:"2981",a0a062d9:"3039",a9c57e4d:"3228",bf0849ee:"3469","022b91d8":"3480","55c0b115":"3601","02d4e9f5":"4121","031e8579":"4167",c4f5d8e4:"4195",ec7fa3cf:"4211",a94703ab:"4368",def44da2:"5075",f7b6ead6:"5167",f78bd335:"5956",a9bc01a8:"6058","86d58bb8":"6120",ff3f7a37:"6404",e578344f:"6425","4620b467":"6495","80e9d2b0":"6872",d7ac533f:"7025",d589d3a7:"7162","82d7f41a":"7334","0a77e43a":"7541","01c6e266":"7561","4b7fc798":"7872","1813f2fc":"7916","1a4e3797":"7920","0c2e262b":"8134",ab12c7ba:"8253",a7bd4aaa:"8518","516f2f25":"8642","9ee1f4e6":"8745",e8fb0f5c:"9077","54866e57":"9234",e8c91c53:"9516","97f1710d":"9607","5e95c892":"9661","0e384e19":"9671"}[e]||e,o.p+o.u(e)},(()=>{var e={1303:0,532:0};o.f.j=(a,t)=>{var f=o.o(e,a)?e[a]:void 0;if(0!==f)if(f)t.push(f[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var r=new Promise(((t,r)=>f=e[a]=[t,r]));t.push(f[2]=r);var c=o.p+o.u(a),d=new Error;o.l(c,(t=>{if(o.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var r=t&&("load"===t.type?"missing":t.type),c=t&&t.target&&t.target.src;d.message="Loading chunk "+a+" failed.\n("+r+": "+c+")",d.name="ChunkLoadError",d.type=r,d.request=c,f[1](d)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var f,r,c=t[0],d=t[1],b=t[2],n=0;if(c.some((a=>0!==e[a]))){for(f in d)o.o(d,f)&&(o.m[f]=d[f]);if(b)var i=b(o)}for(a&&a(t);n Authorization | Entity GraphQL - + diff --git a/docs/directives/operation-directives/index.html b/docs/directives/operation-directives/index.html index 3fe85ef..e6d02b0 100644 --- a/docs/directives/operation-directives/index.html +++ b/docs/directives/operation-directives/index.html @@ -4,7 +4,7 @@ Operation Directives | Entity GraphQL - + diff --git a/docs/directives/schema-directives/index.html b/docs/directives/schema-directives/index.html index 843800b..2909df7 100644 --- a/docs/directives/schema-directives/index.html +++ b/docs/directives/schema-directives/index.html @@ -4,7 +4,7 @@ Schema Directives | Entity GraphQL - + diff --git a/docs/field-extensions/custom-extensions/index.html b/docs/field-extensions/custom-extensions/index.html index 81d68fa..05ed195 100644 --- a/docs/field-extensions/custom-extensions/index.html +++ b/docs/field-extensions/custom-extensions/index.html @@ -4,7 +4,7 @@ Custom Extensions | Entity GraphQL - + diff --git a/docs/field-extensions/filtering/index.html b/docs/field-extensions/filtering/index.html index 4c004de..8c09d32 100644 --- a/docs/field-extensions/filtering/index.html +++ b/docs/field-extensions/filtering/index.html @@ -4,7 +4,7 @@ Filtering | Entity GraphQL - + @@ -55,8 +55,8 @@
{
# No filter - all people that acted in 3 movies
people(filter: "actorIn.count() == 3") { ... }


# Count only those that match the filter - all people that acted in any movie starting with "Star"
people(filter: "actorIn.count(name.startsWith(\"Star\")) > 0") { ... }
}
    -
  • List.first(filter?) - Return the first item from a list. Optionally by a filter
  • -
  • List.last(filter?) - Return the last item from a list. Optionally by a filter
  • +
  • List.first(filter?) / List.firstOrDefault(filter?) - Return the first item from a list. Optionally by a filter
  • +
  • List.last(filter?) / List.lastOrDefault(filter?) - Return the last item from a list. Optionally by a filter
  • List.take(int) - Return the first x items
  • List.skip(int) - Return the items after x
  • List.orderBy(field) - Order the list by a given field
  • diff --git a/docs/field-extensions/index.html b/docs/field-extensions/index.html index 47f4cda..c8af158 100644 --- a/docs/field-extensions/index.html +++ b/docs/field-extensions/index.html @@ -4,7 +4,7 @@ Field Extensions | Entity GraphQL - + diff --git a/docs/field-extensions/paging/index.html b/docs/field-extensions/paging/index.html index 894c05a..c33ccf8 100644 --- a/docs/field-extensions/paging/index.html +++ b/docs/field-extensions/paging/index.html @@ -4,7 +4,7 @@ Paging | Entity GraphQL - + diff --git a/docs/field-extensions/sorting/index.html b/docs/field-extensions/sorting/index.html index bf05ce1..af689a2 100644 --- a/docs/field-extensions/sorting/index.html +++ b/docs/field-extensions/sorting/index.html @@ -4,7 +4,7 @@ Sorting | Entity GraphQL - + @@ -20,7 +20,7 @@
    input PeopleSortInput
    {
    id: SortDirectionEnum
    firstName: SortDirectionEnum
    lastName: SortDirectionEnum
    dob: SortDirectionEnum
    died: SortDirectionEnum
    isDeleted: SortDirectionEnum
    }

    enum SortDirectionEnum {
    ASC
    DESC
    }

    To sort the collection you set the fields with a direction:

    {
    people(sort: [{ lastName: DESC }]) {
    lastName
    }
    }

    {
    people(sort: [{ dob: ASC }]) {
    lastName
    }
    }
    -

    Multiple fields is supported and are taken as ordered

    +

    Multiple fields are supported and are taken as ordered

    {
    people(sort: [{ dob: ASC }, { lastName: DESC }, { firstName: ASC }]) {
    lastName
    }
    }

    Default sort​

    You can set a default sort to be applied if there are no sort arguments passed in the query.

    @@ -29,7 +29,7 @@

    schema.ReplaceField("people",
    ctx => ctx.People,
    "Return a list of people. Optional sorted")
    .UseSort(
    new Sort<Person>((person) => person.Height, SortDirection.ASC),
    new Sort<Person>((person) => person.LastName, SortDirection.ASC)
    );

    Choosing the sort fields​

    If you use the UseSort() method (not the attribute) you can pass in an expression which tells the extension which fields to set in the input type. Make sure you use the correct type for the fields collection. You can still set the default sort as well.

    -
    schema.ReplaceField("people",
    ctx => ctx.People,
    "Return a list of people. Optional sorted")
    // available sort fields
    .UseSort((Person person) => new
    {
    person.Dob,
    person.LastName
    },
    // Default sort
    (Person person) => person.Dob, SortDirectionEnum.DESC);
    +
    schema.ReplaceField("people",
    ctx => ctx.People,
    "Return a list of people. Optional sorted")
    // available sort fields
    .UseSort((Person person) => new
    {
    person.Dob,
    person.LastName,
    manager = person.Manager.Name
    },
    // Default sort
    (Person person) => person.Dob, SortDirectionEnum.DESC);

    This will result in only 2 options for sorting.

    input PeopleSortInput {
    dob: SortDirectionEnum
    lastName: SortDirectionEnum
    }
    diff --git a/docs/getting-started/index.html b/docs/getting-started/index.html index ce8b985..bdd9e11 100644 --- a/docs/getting-started/index.html +++ b/docs/getting-started/index.html @@ -4,7 +4,7 @@ Getting Started | Entity GraphQL - + diff --git a/docs/integration/index.html b/docs/integration/index.html index 1533b94..7d86719 100644 --- a/docs/integration/index.html +++ b/docs/integration/index.html @@ -4,7 +4,7 @@ Tool integration | Entity GraphQL - + diff --git a/docs/intro/index.html b/docs/intro/index.html index 4b4c0f4..39b2fc9 100644 --- a/docs/intro/index.html +++ b/docs/intro/index.html @@ -4,7 +4,7 @@ Introduction | Entity GraphQL - + diff --git a/docs/library-compatibility/caching/index.html b/docs/library-compatibility/caching/index.html index a366a43..36e6e4e 100644 --- a/docs/library-compatibility/caching/index.html +++ b/docs/library-compatibility/caching/index.html @@ -4,7 +4,7 @@ Caching | Entity GraphQL - + diff --git a/docs/library-compatibility/entity-framework/index.html b/docs/library-compatibility/entity-framework/index.html index d1eba00..d00b744 100644 --- a/docs/library-compatibility/entity-framework/index.html +++ b/docs/library-compatibility/entity-framework/index.html @@ -4,7 +4,7 @@ Entity Framework | Entity GraphQL - + @@ -44,6 +44,9 @@

    schema.UpdateType<Floor>(type => {
    type.AddField("floorUrl", "Current floor url")
    .Resolve<IFloorUrlService>((floor, srv) => s.BuildFloorPlanUrl(f.SomeRelation.FirstOrDefault().Id));
    });

    Will extract the f.SomeRelation.FirstOrDefault().Id expression. That will be fetched in the first expression execution as f.SomeRelation_FirstOrDefault___Id = f.SomeRelation.FirstOrDefault().Id. So when we rebuild the final expression to also execute service fields it will update the expression to be s.BuildFloorPlanUrl(f.SomeRelation_FirstOrDefault___Id).

    You may encounter some issues with EF depending on how complex you expressions are. For example pre-6.0 this issue will be hit if you traverse through a relation.

    -

    It is best to keep the expressions used to pass query context data into a service as simple as you can. Remember, your services can also access the DB context or anything else they need via DI.

    +

    It is best to keep the expressions used to pass query context data into a service as simple as you can. Remember, your services can also access the DB context or anything else they need via DI.

    +

    Linking EntityFramework queries to GraphQL operations

    +

    If you want to better understand which EF query is being run from which GraphQL operation you can use the ExecutionOptions.BeforeRootFieldExpressionBuild callback to add the EF TagWith extension method to the query. Below is an example using the MapGraphQL ASP.NET helper.

    +
    app.UseEndpoints(endpoints =>
    {
    endpoints.MapGraphQL<DemoContext>(options: new ExecutionOptions
    {
    BeforeRootFieldExpressionBuild = (exp, op, field) =>
    {
    if (exp.Type.IsGenericTypeQueryable())
    return Expression.Call(typeof(EntityFrameworkQueryableExtensions), nameof(EntityFrameworkQueryableExtensions.TagWith), [exp.Type.GetGenericArguments()[0]], exp, Expression.Constant($"GQL op: {op ?? "n/a"}, field: {field}"));
    return exp;
    }
    });
    });
    \ No newline at end of file diff --git a/docs/library-compatibility/expression-optimizers/index.html b/docs/library-compatibility/expression-optimizers/index.html index f813602..2bda986 100644 --- a/docs/library-compatibility/expression-optimizers/index.html +++ b/docs/library-compatibility/expression-optimizers/index.html @@ -4,7 +4,7 @@ Optimizing Expressions | Entity GraphQL - + diff --git a/docs/library-compatibility/expression-reuse/index.html b/docs/library-compatibility/expression-reuse/index.html index e97ab28..28df89e 100644 --- a/docs/library-compatibility/expression-reuse/index.html +++ b/docs/library-compatibility/expression-reuse/index.html @@ -4,7 +4,7 @@ Reusing Linq Expressions | Entity GraphQL - + diff --git a/docs/library-compatibility/newtonsoft-json/index.html b/docs/library-compatibility/newtonsoft-json/index.html index ec08ccb..814b054 100644 --- a/docs/library-compatibility/newtonsoft-json/index.html +++ b/docs/library-compatibility/newtonsoft-json/index.html @@ -4,7 +4,7 @@ NewtonSoft JSON | Entity GraphQL - + diff --git a/docs/other-extensibility/event-system/index.html b/docs/other-extensibility/event-system/index.html index 6dd4d0f..d94c30f 100644 --- a/docs/other-extensibility/event-system/index.html +++ b/docs/other-extensibility/event-system/index.html @@ -4,7 +4,7 @@ Event System | Entity GraphQL - + diff --git a/docs/other-extensibility/extension-attribute/index.html b/docs/other-extensibility/extension-attribute/index.html index 9438152..403a7a6 100644 --- a/docs/other-extensibility/extension-attribute/index.html +++ b/docs/other-extensibility/extension-attribute/index.html @@ -4,7 +4,7 @@ Extension Attribute | Entity GraphQL - + diff --git a/docs/schema-creation/directives/index.html b/docs/schema-creation/directives/index.html index acf6c45..373804b 100644 --- a/docs/schema-creation/directives/index.html +++ b/docs/schema-creation/directives/index.html @@ -4,7 +4,7 @@ Directives | Entity GraphQL - + diff --git a/docs/schema-creation/enum-types/index.html b/docs/schema-creation/enum-types/index.html index 3e08f3c..949698e 100644 --- a/docs/schema-creation/enum-types/index.html +++ b/docs/schema-creation/enum-types/index.html @@ -4,7 +4,7 @@ Enum Types | Entity GraphQL - + diff --git a/docs/schema-creation/fields/index.html b/docs/schema-creation/fields/index.html index 57e76b7..5f92c1b 100644 --- a/docs/schema-creation/fields/index.html +++ b/docs/schema-creation/fields/index.html @@ -4,7 +4,7 @@ Fields | Entity GraphQL - + diff --git a/docs/schema-creation/index.html b/docs/schema-creation/index.html index 829e4da..162fbc4 100644 --- a/docs/schema-creation/index.html +++ b/docs/schema-creation/index.html @@ -4,7 +4,7 @@ Schema Creation | Entity GraphQL - + diff --git a/docs/schema-creation/input-types/index.html b/docs/schema-creation/input-types/index.html index 5d91cb0..228064f 100644 --- a/docs/schema-creation/input-types/index.html +++ b/docs/schema-creation/input-types/index.html @@ -4,7 +4,7 @@ Input Types | Entity GraphQL - + diff --git a/docs/schema-creation/interface-types/index.html b/docs/schema-creation/interface-types/index.html index 775bb10..2c02954 100644 --- a/docs/schema-creation/interface-types/index.html +++ b/docs/schema-creation/interface-types/index.html @@ -4,7 +4,7 @@ Interfaces Types & Implements Keyword | Entity GraphQL - + diff --git a/docs/schema-creation/lists-and-nonnulls/index.html b/docs/schema-creation/lists-and-nonnulls/index.html index ba613b4..3da3eea 100644 --- a/docs/schema-creation/lists-and-nonnulls/index.html +++ b/docs/schema-creation/lists-and-nonnulls/index.html @@ -4,7 +4,7 @@ Lists and Non-Null | Entity GraphQL - + diff --git a/docs/schema-creation/mutations/index.html b/docs/schema-creation/mutations/index.html index 8a3ad2e..be38cbd 100644 --- a/docs/schema-creation/mutations/index.html +++ b/docs/schema-creation/mutations/index.html @@ -4,7 +4,7 @@ Mutations | Entity GraphQL - + diff --git a/docs/schema-creation/other-data-sources/index.html b/docs/schema-creation/other-data-sources/index.html index d903564..0806927 100644 --- a/docs/schema-creation/other-data-sources/index.html +++ b/docs/schema-creation/other-data-sources/index.html @@ -4,7 +4,7 @@ Adding Other Data Sources or Services | Entity GraphQL - + diff --git a/docs/schema-creation/scalar-types/index.html b/docs/schema-creation/scalar-types/index.html index e1592ca..3d065c0 100644 --- a/docs/schema-creation/scalar-types/index.html +++ b/docs/schema-creation/scalar-types/index.html @@ -4,7 +4,7 @@ Scalar Types | Entity GraphQL - + @@ -21,7 +21,7 @@

    Adding a scalar type tells EntityGraphQL that the object should just be returned. i.e. there is no selection available on it. A good example is DateTime. We just want to return the DateTime value. Not have it as an Object type where you could select certain properties from it (Although you could set that up).

    schema.AddScalarType<DateTime>("DateTime", "Represents a date and time.");

    EntityGraphQL by default will set up the follow scalar types on schema creation.

    -
    new SchemaType<int>(this, "Int", "Int scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<double>(this, "Float", "Float scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<bool>(this, "Boolean", "Boolean scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<string>(this, "String", "String scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<Guid>(this, "ID", "ID scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<char>(this, "Char", "Char scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<DateTime>(this, "Date", "Date with time scalar", null, GqlTypeEnum.Scalar);
    new SchemaType<DateTimeOffset>(this, "DateTimeOffset", "DateTimeOffset scalar", null, GqlTypeEnum.Scalar)
    +
    AddScalarType<int>("Int", "Int scalar");
    AddScalarType<double>("Float", "Float scalar");
    AddScalarType<bool>("Boolean", "Boolean scalar");
    AddScalarType<string>("String", "String scalar");
    AddScalarType<Guid>("ID", "ID scalar");
    AddScalarType<char>("Char", "Char scalar");

    AddScalarType<DateTime>("Date", "Date with time scalar");
    AddScalarType<DateTimeOffset>("DateTimeOffset", "DateTimeOffset scalar");
    AddScalarType<DateOnly>("DateOnly", "Date value only scalar");
    AddScalarType<TimeOnly>("TimeOnly", "Time value only scalar");

    It is best to have scalar types added to the schema before adding other fields that reference them. Otherwise EntityGraphQL doesn't know about the scalar types. You can add you're own or make changes to the default when registering your schema.

    services.AddGraphQLSchema<TContext>(options => {
    options.PreBuildSchemaFromContext = schema =>
    {
    // remove and/or add scalar types or mappings here. e.g.
    schema.RemoveType<DateTime>();
    schema.AddScalarType<KeyValuePair<string, string>>("StringKeyValuePair", "Represents a pair of strings");
    };
    })

    You can also tell EntityGraphQL to auto-map a dotnet type to a schema type with AddTypeMapping<TFromType>(string gqlType). For example

    diff --git a/docs/schema-creation/subscriptions/index.html b/docs/schema-creation/subscriptions/index.html index 3cfd035..1d9068f 100644 --- a/docs/schema-creation/subscriptions/index.html +++ b/docs/schema-creation/subscriptions/index.html @@ -4,7 +4,7 @@ Subscriptions | Entity GraphQL - + diff --git a/docs/schema-creation/union-types/index.html b/docs/schema-creation/union-types/index.html index e1363d3..33be04e 100644 --- a/docs/schema-creation/union-types/index.html +++ b/docs/schema-creation/union-types/index.html @@ -4,7 +4,7 @@ Union Types | Entity GraphQL - + diff --git a/docs/serialization-naming/index.html b/docs/serialization-naming/index.html index 7d17cf4..283eb31 100644 --- a/docs/serialization-naming/index.html +++ b/docs/serialization-naming/index.html @@ -4,7 +4,7 @@ Serialization & Field Naming | Entity GraphQL - + diff --git a/docs/serialization-naming/newtonsoft-json/index.html b/docs/serialization-naming/newtonsoft-json/index.html index 2ac5b39..7dbe37c 100644 --- a/docs/serialization-naming/newtonsoft-json/index.html +++ b/docs/serialization-naming/newtonsoft-json/index.html @@ -4,7 +4,7 @@ Using NewtonSoft JSON | Entity GraphQL - + diff --git a/docs/upgrade-4-0/index.html b/docs/upgrade-4-0/index.html index 54d411d..620854c 100644 --- a/docs/upgrade-4-0/index.html +++ b/docs/upgrade-4-0/index.html @@ -4,7 +4,7 @@ Upgrading from 3.x to 4.x | Entity GraphQL - + diff --git a/docs/upgrade-5-0/index.html b/docs/upgrade-5-0/index.html index 3713bc4..150a62d 100644 --- a/docs/upgrade-5-0/index.html +++ b/docs/upgrade-5-0/index.html @@ -4,7 +4,7 @@ Upgrading from 4.x to 5.x | Entity GraphQL - + diff --git a/docs/validation/index.html b/docs/validation/index.html index c9fd185..5b8d90c 100644 --- a/docs/validation/index.html +++ b/docs/validation/index.html @@ -4,7 +4,7 @@ Validation | Entity GraphQL - + diff --git a/index.html b/index.html index 340f62d..1314221 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ Entity GraphQL | Entity GraphQL - + diff --git a/search/index.html b/search/index.html index ac4f82e..d920331 100644 --- a/search/index.html +++ b/search/index.html @@ -4,7 +4,7 @@ Search the documentation | Entity GraphQL - +