Skip to content

Commit 3d59c71

Browse files
authored
Merge pull request #38 from neisbut/dev
Adds table valued functions support
2 parents c203687 + 890f3dd commit 3d59c71

4 files changed

Lines changed: 140 additions & 1 deletion

File tree

src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,15 @@ PendingProjectsNode VisitInputWithBinding(DbExpression expression, string bindin
399399

400400
break;
401401
}
402+
case DbExpressionKind.Function:
403+
{
404+
var function = (DbFunctionExpression)expression;
405+
var input = new InputExpression(
406+
VisitFunction(function.Function, function.Arguments, function.ResultType), bindingName);
407+
408+
n = new PendingProjectsNode(bindingName, input);
409+
break;
410+
}
402411
default:
403412
throw new NotImplementedException();
404413
}

src/EntityFramework6.Npgsql/SqlGenerators/VisitedExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ internal override void WriteSql(StringBuilder sqlText)
526526
}
527527
else
528528
{
529-
var wrap = !(_from is LiteralExpression || _from is ScanExpression);
529+
var wrap = !(_from is LiteralExpression || _from is ScanExpression || _from is FunctionExpression);
530530
if (wrap)
531531
sqlText.Append("(");
532532
_from.WriteSql(sqlText);

test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,5 +719,43 @@ public void Test_issue_27_select_ef_generated_literals_from_inner_select()
719719
Assert.That(administrator.HasBlog, Is.True);
720720
}
721721
}
722+
723+
[Test]
724+
public void TestTableValuedStoredFunctions()
725+
{
726+
using (var context = new BloggingContext(ConnectionString))
727+
{
728+
context.Database.Log = Console.Out.WriteLine;
729+
730+
// Add some data and query it back using Stored Function
731+
context.Blogs.Add(new Blog
732+
{
733+
Name = "Some blog1 name",
734+
Posts = new List<Post>()
735+
});
736+
context.Blogs.Add(new Blog
737+
{
738+
Name = "Some blog2 name",
739+
Posts = new List<Post>()
740+
});
741+
context.SaveChanges();
742+
743+
// Query back
744+
var query = from b in context.GetBlogsByName("blog1")
745+
select b;
746+
var list = query.ToList();
747+
748+
Assert.AreEqual(1, list.Count);
749+
Assert.AreEqual("Some blog1 name", list[0].Name);
750+
751+
// Query with projection
752+
var query2 = from b in context.GetBlogsByName("blog1")
753+
select new { b.Name, Something = 1 };
754+
var list2 = query2.ToList();
755+
Assert.AreEqual(1, list2.Count);
756+
Assert.AreEqual("Some blog1 name", list2[0].Name);
757+
Assert.AreEqual(1, list2[0].Something);
758+
}
759+
}
722760
}
723761
}

test/EntityFramework6.Npgsql.Tests/Support/EntityFrameworkTestBase.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
using System.Linq;
3131
using System.Text;
3232
using System.ComponentModel.DataAnnotations.Schema;
33+
using System.Data.Entity.Core.Mapping;
3334
using System.Data.Entity.Core.Metadata.Edm;
3435
using System.Data.Entity.Core.Objects;
3536
using System.Data.Entity.Infrastructure;
@@ -58,6 +59,7 @@ public abstract class EntityFrameworkTestBase : TestBase
5859
createSequenceConn.ExecuteNonQuery("alter table \"dbo\".\"Posts\" alter column \"VarbitColumn\" type varbit using null");
5960
createSequenceConn.ExecuteNonQuery("CREATE OR REPLACE FUNCTION \"dbo\".\"StoredAddFunction\"(integer, integer) RETURNS integer AS $$ SELECT $1 + $2; $$ LANGUAGE SQL;");
6061
createSequenceConn.ExecuteNonQuery("CREATE OR REPLACE FUNCTION \"dbo\".\"StoredEchoFunction\"(integer) RETURNS integer AS $$ SELECT $1; $$ LANGUAGE SQL;");
62+
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;");
6163
}
6264
}
6365

@@ -144,6 +146,15 @@ public static int StoredEchoFunction(int value)
144146
throw new NotSupportedException();
145147
}
146148

149+
[DbFunction("BloggingContext", "GetBlogsByName")]
150+
public IQueryable<Blog> GetBlogsByName(string name)
151+
{
152+
ObjectParameter nameParameter = new ObjectParameter("Name", name);
153+
154+
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<Blog>(
155+
$"[GetBlogsByName](@Name)", nameParameter);
156+
}
157+
147158
private static DbCompiledModel CreateModel(NpgsqlConnection connection)
148159
{
149160
var dbModelBuilder = new DbModelBuilder(DbModelBuilderVersion.Latest);
@@ -213,6 +224,87 @@ private static DbCompiledModel CreateModel(NpgsqlConnection connection)
213224
null);
214225
dbModel.StoreModel.AddItem(echoFunc);
215226

227+
var stringStoreType = dbModel.ProviderManifest.GetStoreTypes().First(x => x.ClrEquivalentType == typeof(string));
228+
var modelBlogStoreType = dbModel.StoreModel.EntityTypes.First(x => x.Name == typeof(Blog).Name);
229+
var rowType = RowType.Create(
230+
modelBlogStoreType.Properties.Select(x =>
231+
{
232+
var clone = EdmProperty.Create(x.Name, x.TypeUsage);
233+
clone.CollectionKind = x.CollectionKind;
234+
clone.ConcurrencyMode = x.ConcurrencyMode;
235+
clone.IsFixedLength = x.IsFixedLength;
236+
clone.IsMaxLength = x.IsMaxLength;
237+
clone.IsUnicode = x.IsUnicode;
238+
clone.MaxLength = x.MaxLength;
239+
clone.Precision = x.Precision;
240+
clone.Scale = x.Scale;
241+
clone.StoreGeneratedPattern = x.StoreGeneratedPattern;
242+
clone.SetMetadataProperties(x
243+
.MetadataProperties
244+
.Where(metadataProerty => !clone
245+
.MetadataProperties
246+
.Any(cloneMetadataProperty => cloneMetadataProperty.Name.Equals(metadataProerty.Name))));
247+
return clone;
248+
}),
249+
null);
250+
251+
var getBlogsFunc = EdmFunction.Create(
252+
"StoredGetBlogsFunction",
253+
"BloggingContext",
254+
DataSpace.SSpace,
255+
new EdmFunctionPayload
256+
{
257+
ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion,
258+
Schema = "dbo",
259+
IsComposable = true,
260+
IsNiladic = false,
261+
IsBuiltIn = false,
262+
IsAggregate = false,
263+
StoreFunctionName = "GetBlogsByName",
264+
ReturnParameters = new[]
265+
{
266+
FunctionParameter.Create("ReturnType1", rowType.GetCollectionType(), ParameterMode.ReturnValue)
267+
},
268+
Parameters = new[]
269+
{
270+
FunctionParameter.Create("Name", stringStoreType, ParameterMode.In)
271+
}
272+
},
273+
null);
274+
dbModel.StoreModel.AddItem(getBlogsFunc);
275+
276+
var stringPrimitiveType = PrimitiveType.GetEdmPrimitiveTypes().First(x => x.ClrEquivalentType == typeof(string));
277+
var modelBlogConceptualType = dbModel.ConceptualModel.EntityTypes.First(x => x.Name == typeof(Blog).Name);
278+
EdmFunction getBlogsFuncModel = EdmFunction.Create(
279+
"GetBlogsByName",
280+
dbModel.ConceptualModel.Container.Name,
281+
DataSpace.CSpace,
282+
new EdmFunctionPayload
283+
{
284+
IsFunctionImport = true,
285+
IsComposable = true,
286+
Parameters = new[]
287+
{
288+
FunctionParameter.Create("Name", stringPrimitiveType, ParameterMode.In)
289+
},
290+
ReturnParameters = new[]
291+
{
292+
FunctionParameter.Create("ReturnType1", modelBlogConceptualType.GetCollectionType(), ParameterMode.ReturnValue)
293+
},
294+
EntitySets = new[]
295+
{
296+
dbModel.ConceptualModel.Container.EntitySets.First(x => x.ElementType == modelBlogConceptualType)
297+
}
298+
},
299+
null);
300+
dbModel.ConceptualModel.Container.AddFunctionImport(getBlogsFuncModel);
301+
302+
dbModel.ConceptualToStoreMapping.AddFunctionImportMapping(new FunctionImportMappingComposable(
303+
getBlogsFuncModel,
304+
getBlogsFunc,
305+
new FunctionImportResultMapping(),
306+
dbModel.ConceptualToStoreMapping));
307+
216308
var compiledModel = dbModel.Compile();
217309
return compiledModel;
218310
}

0 commit comments

Comments
 (0)