Skip to content

Commit

Permalink
Adds table valued functions support
Browse files Browse the repository at this point in the history
  • Loading branch information
neisbut committed Jul 12, 2016
1 parent c203687 commit 890f3dd
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 1 deletion.
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);
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,
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,
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(
getBlogsFuncModel,
getBlogsFunc,
new FunctionImportResultMapping(),
dbModel.ConceptualToStoreMapping));

var compiledModel = dbModel.Compile();
return compiledModel;
}
Expand Down

0 comments on commit 890f3dd

Please sign in to comment.