-
Notifications
You must be signed in to change notification settings - Fork 54
Adds table valued functions support #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| using System.Linq; | ||
| using System.Text; | ||
| using System.ComponentModel.DataAnnotations.Schema; | ||
| using System.Data.Entity.Core.Mapping; | ||
| using System.Data.Entity.Core.Metadata.Edm; | ||
| using System.Data.Entity.Core.Objects; | ||
| using System.Data.Entity.Infrastructure; | ||
|
|
@@ -58,6 +59,7 @@ public abstract class EntityFrameworkTestBase : TestBase | |
| createSequenceConn.ExecuteNonQuery("alter table \"dbo\".\"Posts\" alter column \"VarbitColumn\" type varbit using null"); | ||
| createSequenceConn.ExecuteNonQuery("CREATE OR REPLACE FUNCTION \"dbo\".\"StoredAddFunction\"(integer, integer) RETURNS integer AS $$ SELECT $1 + $2; $$ LANGUAGE SQL;"); | ||
| createSequenceConn.ExecuteNonQuery("CREATE OR REPLACE FUNCTION \"dbo\".\"StoredEchoFunction\"(integer) RETURNS integer AS $$ SELECT $1; $$ LANGUAGE SQL;"); | ||
| createSequenceConn.ExecuteNonQuery("CREATE OR REPLACE FUNCTION \"dbo\".\"GetBlogsByName\"(text) RETURNS TABLE(\"BlogId\" int, \"Name\" text, \"IntComputedValue\" int) as $$ select \"BlogId\", \"Name\", \"IntComputedValue\" from \"dbo\".\"Blogs\" where \"Name\" ilike '%' || $1 || '%' $$ LANGUAGE SQL;"); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -144,6 +146,15 @@ public static int StoredEchoFunction(int value) | |
| throw new NotSupportedException(); | ||
| } | ||
|
|
||
| [DbFunction("BloggingContext", "GetBlogsByName")] | ||
| public IQueryable<Blog> GetBlogsByName(string name) | ||
| { | ||
| ObjectParameter nameParameter = new ObjectParameter("Name", name); | ||
|
|
||
| return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<Blog>( | ||
| $"[GetBlogsByName](@Name)", nameParameter); | ||
| } | ||
|
|
||
| private static DbCompiledModel CreateModel(NpgsqlConnection connection) | ||
| { | ||
| var dbModelBuilder = new DbModelBuilder(DbModelBuilderVersion.Latest); | ||
|
|
@@ -213,6 +224,87 @@ private static DbCompiledModel CreateModel(NpgsqlConnection connection) | |
| null); | ||
| dbModel.StoreModel.AddItem(echoFunc); | ||
|
|
||
| var stringStoreType = dbModel.ProviderManifest.GetStoreTypes().First(x => x.ClrEquivalentType == typeof(string)); | ||
| var modelBlogStoreType = dbModel.StoreModel.EntityTypes.First(x => x.Name == typeof(Blog).Name); | ||
| var rowType = RowType.Create( | ||
| modelBlogStoreType.Properties.Select(x => | ||
| { | ||
| var clone = EdmProperty.Create(x.Name, x.TypeUsage); | ||
| clone.CollectionKind = x.CollectionKind; | ||
| clone.ConcurrencyMode = x.ConcurrencyMode; | ||
| clone.IsFixedLength = x.IsFixedLength; | ||
| clone.IsMaxLength = x.IsMaxLength; | ||
| clone.IsUnicode = x.IsUnicode; | ||
| clone.MaxLength = x.MaxLength; | ||
| clone.Precision = x.Precision; | ||
| clone.Scale = x.Scale; | ||
| clone.StoreGeneratedPattern = x.StoreGeneratedPattern; | ||
| clone.SetMetadataProperties(x | ||
| .MetadataProperties | ||
| .Where(metadataProerty => !clone | ||
| .MetadataProperties | ||
| .Any(cloneMetadataProperty => cloneMetadataProperty.Name.Equals(metadataProerty.Name)))); | ||
| return clone; | ||
| }), | ||
| null); | ||
|
|
||
| var getBlogsFunc = EdmFunction.Create( | ||
| "StoredGetBlogsFunction", | ||
| "BloggingContext", | ||
| DataSpace.SSpace, | ||
| new EdmFunctionPayload | ||
| { | ||
| ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion, | ||
| Schema = "dbo", | ||
| IsComposable = true, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be IsComposable = false ? Or you can use TVF inside LINQ ? (I'm not familiar with TVFs in general, so I'm just making sure).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rwasef1830, that's right, this flag allows to use this inside of LINQ expressions like this: |
||
| IsNiladic = false, | ||
| IsBuiltIn = false, | ||
| IsAggregate = false, | ||
| StoreFunctionName = "GetBlogsByName", | ||
| ReturnParameters = new[] | ||
| { | ||
| FunctionParameter.Create("ReturnType1", rowType.GetCollectionType(), ParameterMode.ReturnValue) | ||
| }, | ||
| Parameters = new[] | ||
| { | ||
| FunctionParameter.Create("Name", stringStoreType, ParameterMode.In) | ||
| } | ||
| }, | ||
| null); | ||
| dbModel.StoreModel.AddItem(getBlogsFunc); | ||
|
|
||
| var stringPrimitiveType = PrimitiveType.GetEdmPrimitiveTypes().First(x => x.ClrEquivalentType == typeof(string)); | ||
| var modelBlogConceptualType = dbModel.ConceptualModel.EntityTypes.First(x => x.Name == typeof(Blog).Name); | ||
| EdmFunction getBlogsFuncModel = EdmFunction.Create( | ||
| "GetBlogsByName", | ||
| dbModel.ConceptualModel.Container.Name, | ||
| DataSpace.CSpace, | ||
| new EdmFunctionPayload | ||
| { | ||
| IsFunctionImport = true, | ||
| IsComposable = true, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question here
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rwasef1830, yep, needed here as well. |
||
| Parameters = new[] | ||
| { | ||
| FunctionParameter.Create("Name", stringPrimitiveType, ParameterMode.In) | ||
| }, | ||
| ReturnParameters = new[] | ||
| { | ||
| FunctionParameter.Create("ReturnType1", modelBlogConceptualType.GetCollectionType(), ParameterMode.ReturnValue) | ||
| }, | ||
| EntitySets = new[] | ||
| { | ||
| dbModel.ConceptualModel.Container.EntitySets.First(x => x.ElementType == modelBlogConceptualType) | ||
| } | ||
| }, | ||
| null); | ||
| dbModel.ConceptualModel.Container.AddFunctionImport(getBlogsFuncModel); | ||
|
|
||
| dbModel.ConceptualToStoreMapping.AddFunctionImportMapping(new FunctionImportMappingComposable( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rwasef1830, done, refactored long names. |
||
| getBlogsFuncModel, | ||
| getBlogsFunc, | ||
| new FunctionImportResultMapping(), | ||
| dbModel.ConceptualToStoreMapping)); | ||
|
|
||
| var compiledModel = dbModel.Compile(); | ||
| return compiledModel; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is avoiding the wrapping by parens necessary ? I'm not familiar with the pgsql for calling TVF sprocs so just making sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rwasef1830, yes, I think this is necessary because of Postgres syntax. In example, the following works:
select * from "dbo"."GetBlogsByName"('some')but this one does not:
select * from ("dbo"."GetBlogsByName"('some'))