Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,15 @@ PendingProjectsNode VisitInputWithBinding(DbExpression expression, string bindin

break;
}
case DbExpressionKind.Function:
{
var function = (DbFunctionExpression)expression;
var input = new InputExpression(
VisitFunction(function.Function, function.Arguments, function.ResultType), bindingName);

n = new PendingProjectsNode(bindingName, input);
break;
}
default:
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ internal override void WriteSql(StringBuilder sqlText)
}
else
{
var wrap = !(_from is LiteralExpression || _from is ScanExpression);
var wrap = !(_from is LiteralExpression || _from is ScanExpression || _from is FunctionExpression);
Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor Author

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'))

if (wrap)
sqlText.Append("(");
_from.WriteSql(sqlText);
Expand Down
38 changes: 38 additions & 0 deletions test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -719,5 +719,43 @@ public void Test_issue_27_select_ef_generated_literals_from_inner_select()
Assert.That(administrator.HasBlog, Is.True);
}
}

[Test]
public void TestTableValuedStoredFunctions()
{
using (var context = new BloggingContext(ConnectionString))
{
context.Database.Log = Console.Out.WriteLine;

// Add some data and query it back using Stored Function
context.Blogs.Add(new Blog
{
Name = "Some blog1 name",
Posts = new List<Post>()
});
context.Blogs.Add(new Blog
{
Name = "Some blog2 name",
Posts = new List<Post>()
});
context.SaveChanges();

// Query back
var query = from b in context.GetBlogsByName("blog1")
select b;
var list = query.ToList();

Assert.AreEqual(1, list.Count);
Assert.AreEqual("Some blog1 name", list[0].Name);

// Query with projection
var query2 = from b in context.GetBlogsByName("blog1")
select new { b.Name, Something = 1 };
var list2 = query2.ToList();
Assert.AreEqual(1, list2.Count);
Assert.AreEqual("Some blog1 name", list2[0].Name);
Assert.AreEqual(1, list2[0].Something);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;");
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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:
var query2 = from b in context.GetBlogsByName("blog1") select new { b.Name, Something = 1 };

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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same question here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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;
}
Expand Down