├── .gitignore ├── CodeFirstStoreFunctions.sln ├── License.txt ├── README.md ├── samples └── CodeFirstStoreFunctionsSamples │ ├── App.config │ ├── CodeFirstStoreFunctionsSamples.csproj │ ├── MultipleResultSetsSample.cs │ ├── Program.cs │ └── ScalarFunctionSample.cs ├── src └── CodeFirstStoreFunctions │ ├── App.config │ ├── CodeFirstStoreFunctions.csproj │ ├── DbFunctionDetailsAttribute.cs │ ├── FunctionDescriptor.cs │ ├── FunctionDiscovery.cs │ ├── FunctionsConvention.cs │ ├── FunctionsConvention`1.cs │ ├── ParameterDescriptor.cs │ ├── ParameterTypeAttribute.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── StoreFunctionBuilder.cs │ ├── StoreFunctionKind.cs │ └── Tools.cs └── test └── CodeFirstStoreFunctionsTests ├── App.config ├── CodeFirstStoreFunctionsTests.csproj ├── DbFunctionDetailsAttributeTests.cs ├── E2ETests.cs ├── FunctionDescriptorTests.cs ├── FunctionDiscoveryTests.cs ├── ParameterDescriptorTests.cs ├── ParameterTypeAttributeTests.cs └── StoreFunctionBuilderTests.cs /.gitignore: -------------------------------------------------------------------------------- 1 | _ReSharper* 2 | bin 3 | Bin 4 | obj 5 | App_Data 6 | TestResults 7 | *.user 8 | *.patch 9 | *.sln.cache 10 | *.orig 11 | *.suo 12 | *.log 13 | *.exe 14 | *.dll 15 | *.resources 16 | *.snk 17 | packages 18 | .vs/ -------------------------------------------------------------------------------- /CodeFirstStoreFunctions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29326.143 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeFirstStoreFunctions", "src\CodeFirstStoreFunctions\CodeFirstStoreFunctions.csproj", "{6912CA14-D068-4131-97A5-053530B4F58D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeFirstStoreFunctionsTests", "test\CodeFirstStoreFunctionsTests\CodeFirstStoreFunctionsTests.csproj", "{D5570D23-4D7B-4508-9941-E4CAC960A12F}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeFirstStoreFunctionsSamples", "samples\CodeFirstStoreFunctionsSamples\CodeFirstStoreFunctionsSamples.csproj", "{66B0B268-55B9-463D-80C7-AE5990D74346}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {6912CA14-D068-4131-97A5-053530B4F58D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {6912CA14-D068-4131-97A5-053530B4F58D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {6912CA14-D068-4131-97A5-053530B4F58D}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {6912CA14-D068-4131-97A5-053530B4F58D}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {D5570D23-4D7B-4508-9941-E4CAC960A12F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {D5570D23-4D7B-4508-9941-E4CAC960A12F}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {D5570D23-4D7B-4508-9941-E4CAC960A12F}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {D5570D23-4D7B-4508-9941-E4CAC960A12F}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {66B0B268-55B9-463D-80C7-AE5990D74346}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {66B0B268-55B9-463D-80C7-AE5990D74346}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {66B0B268-55B9-463D-80C7-AE5990D74346}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {66B0B268-55B9-463D-80C7-AE5990D74346}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {AC5F0616-0499-4F08-9829-5A35572BE604} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | 3 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | 7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 8 | 9 | A "contribution" is the original software, or any additions or changes to the software. 10 | 11 | A "contributor" is any person that distributes its contribution under this license. 12 | 13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 14 | 15 | 2. Grant of Rights 16 | 17 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 18 | 19 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 20 | 21 | 3. Conditions and Limitations 22 | 23 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 24 | 25 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 26 | 27 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 28 | 29 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 30 | 31 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Store Functions for EntityFramework CodeFirst 2 | 3 | Currently Entity Framework Code First approach does not natively support store functions (Table Valued Functions (TVFs) and Stored procedures). However opening the mapping API (and a few other minor changes) made it possible to use TVFs and stored procedures with Code First. This project uses the basic building blocks to build end to end experience allowing using TVFs in Linq queries and invoking stroed procedures without having to drop down to SQL. 4 | 5 | #### This project was moved from https://codefirstfunctions.codeplex.com 6 | 7 | You may still find some useful information there: 8 | 9 | - Old discussion board - https://codefirstfunctions.codeplex.com/discussions 10 | - Issues - https://codefirstfunctions.codeplex.com/workitem/list/basic 11 | 12 | # How to get it 13 | 14 | You can get it from NuGet - just install the [EntityFramework.CodeFirstStoreFunctions NuGet package](http://www.nuget.org/packages/EntityFramework.CodeFirstStoreFunctions) 15 | 16 | # How to use it 17 | 18 | - [See what's new in Beta](http://blog.3d-logic.com/2014/08/11/the-beta-version-of-store-functions-for-entityframework-6-1-1-code-first-available) 19 | - [The 1.0.0 version released](https://blog.3d-logic.com/2014/10/18/the-final-version-of-the-store-functions-for-entityframework-6-1-1-code-first-convention-released) 20 | 21 | The project uses a custom convention to discover TVF and sored proc stub functions which are then mapped to corresponding store functions. This blog post describes in more details how to use the convnention [Support for Store Functions (TVFs and Stored Procs) in Entity Framework 6.1](http://blog.3d-logic.com/2014/04/09/support-for-store-functions-tvfs-and-stored-procs-in-entity-framework-6-1/). Below you can find an example that uses the convention to map a method to a TVF and to a stored proc. Note that the code below only shows store functions that return entities but it is also possible to use coplex or scalar types. These scenarios are covered by [End-to-end tests](https://github.com/moozzyk/CodeFirstFunctions/blob/master/CodeFirstStoreFunctionsTests/E2ETests.cs). 22 | 23 | ```C# 24 | public class Customer 25 | { 26 | public int Id { get; set; } 27 | 28 | public string Name { get; set; } 29 | 30 | public string ZipCode { get; set; } 31 | } 32 | 33 | public class MyContext : DbContext 34 | { 35 | static MyContext() 36 | { 37 | Database.SetInitializer(new MyContextInitializer()); 38 | } 39 | 40 | public DbSet Customers { get; set; } 41 | 42 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 43 | { 44 | modelBuilder.Conventions.Add(new FunctionsConvention("dbo")); 45 | } 46 | 47 | [DbFunction("MyContext", "CustomersByZipCode")] 48 | public IQueryable CustomersByZipCode(string zipCode) 49 | { 50 | var zipCodeParameter = zipCode != null ? 51 | new ObjectParameter("ZipCode", zipCode) : 52 | new ObjectParameter("ZipCode", typeof(string)); 53 | 54 | return ((IObjectContextAdapter)this).ObjectContext 55 | .CreateQuery( 56 | string.Format("[{0}].{1}", GetType().Name, 57 | "[CustomersByZipCode](@ZipCode)"), zipCodeParameter); 58 | } 59 | 60 | public ObjectResult GetCustomersByName(string name) 61 | { 62 | var nameParameter = name != null ? 63 | new ObjectParameter("Name", name) : 64 | new ObjectParameter("Name", typeof(string)); 65 | 66 | return ((IObjectContextAdapter)this).ObjectContext. 67 | ExecuteFunction("GetCustomersByName", nameParameter); 68 | } 69 | } 70 | 71 | public class MyContextInitializer : DropCreateDatabaseAlways 72 | { 73 | public override void InitializeDatabase(MyContext context) 74 | { 75 | base.InitializeDatabase(context); 76 | 77 | context.Database.ExecuteSqlCommand( 78 | "CREATE PROCEDURE [dbo].[GetCustomersByName] @Name nvarchar(max) AS " + 79 | "SELECT [Id], [Name], [ZipCode] " + 80 | "FROM [dbo].[Customers] " + 81 | "WHERE [Name] LIKE (@Name)"); 82 | 83 | context.Database.ExecuteSqlCommand( 84 | "CREATE FUNCTION [dbo].[CustomersByZipCode](@ZipCode nchar(5)) " + 85 | "RETURNS TABLE " + 86 | "RETURN " + 87 | "SELECT [Id], [Name], [ZipCode] " + 88 | "FROM [dbo].[Customers] " + 89 | "WHERE [ZipCode] = @ZipCode"); 90 | } 91 | 92 | protected override void Seed(MyContext context) 93 | { 94 | context.Customers.Add(new Customer {Name = "John", ZipCode = "98052"}); 95 | context.Customers.Add(new Customer { Name = "Natasha", ZipCode = "98210" }); 96 | context.Customers.Add(new Customer { Name = "Lin", ZipCode = "98052" }); 97 | context.Customers.Add(new Customer { Name = "Josh", ZipCode = "90210" }); 98 | context.Customers.Add(new Customer { Name = "Maria", ZipCode = "98074" }); 99 | context.SaveChanges(); 100 | } 101 | } 102 | 103 | class Program 104 | { 105 | static void Main() 106 | { 107 | using (var ctx = new MyContext()) 108 | { 109 | const string zipCode = "98052"; 110 | var q = ctx.CustomersByZipCode(zipCode) 111 | .Where(c => c.Name.Length > 3); 112 | //Console.WriteLine(((ObjectQuery)q).ToTraceString()); 113 | Console.WriteLine("TVF: CustomersByZipCode('{0}')", zipCode); 114 | foreach (var customer in q) 115 | { 116 | Console.WriteLine("Id: {0}, Name: {1}, ZipCode: {2}", 117 | customer.Id, customer.Name, customer.ZipCode); 118 | } 119 | 120 | const string name = "Jo%"; 121 | Console.WriteLine("\nStored procedure: GetCustomersByName '{0}'", name); 122 | foreach (var customer in ctx.GetCustomersByName(name)) 123 | { 124 | Console.WriteLine("Id: {0}, Name: {1}, ZipCode: {2}", 125 | customer.Id, customer.Name, customer.ZipCode); 126 | } 127 | } 128 | } 129 | } 130 | ``` 131 | 132 | -------------------------------------------------------------------------------- /samples/CodeFirstStoreFunctionsSamples/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/CodeFirstStoreFunctionsSamples/CodeFirstStoreFunctionsSamples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452;netcoreapp3.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/CodeFirstStoreFunctionsSamples/MultipleResultSetsSample.cs: -------------------------------------------------------------------------------- 1 | namespace CodeFirstStoreFunctionsSamples 2 | { 3 | using System; 4 | using System.Data.Entity; 5 | using System.Linq; 6 | using CodeFirstStoreFunctions; 7 | using System.Collections.Generic; 8 | using System.Data.Entity.Core.Objects; 9 | using System.Data.Entity.Infrastructure; 10 | 11 | internal class MultupleResultSetsContextInitializer : DropCreateDatabaseAlways 12 | { 13 | public override void InitializeDatabase(MultipleResultSetsContext context) 14 | { 15 | base.InitializeDatabase(context); 16 | 17 | context.Database.ExecuteSqlCommand( 18 | "CREATE PROCEDURE [dbo].[CustomersOrdersAndAnswer] @Answer int OUT AS " + 19 | "SET @Answer = 42 " + 20 | "SELECT [Id], [Name] FROM [dbo].[Customers] " + 21 | "SELECT [Id], [Customer_Id], [Description] FROM [dbo].[Orders] " + 22 | "SELECT -42 AS [Answer]"); 23 | } 24 | 25 | protected override void Seed(MultipleResultSetsContext ctx) 26 | { 27 | ctx.Customers.Add(new Customer 28 | { 29 | Name = "ALFKI", 30 | Orders = new List 31 | { 32 | new Order {Description = "Pens"}, 33 | new Order {Description = "Folders"} 34 | } 35 | }); 36 | 37 | ctx.Customers.Add(new Customer 38 | { 39 | Name = "WOLZA", 40 | Orders = new List { new Order { Description = "Tofu" } } 41 | }); 42 | } 43 | } 44 | 45 | public class Customer 46 | { 47 | public int Id { get; set; } 48 | public string Name { get; set; } 49 | public virtual ICollection Orders { get; set; } 50 | } 51 | 52 | public class Order 53 | { 54 | public int Id { get; set; } 55 | public string Description { get; set; } 56 | public virtual Customer Customer { get; set; } 57 | } 58 | 59 | public class MultipleResultSetsContext : DbContext 60 | { 61 | static MultipleResultSetsContext() 62 | { 63 | Database.SetInitializer(new MultupleResultSetsContextInitializer()); 64 | } 65 | 66 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 67 | { 68 | modelBuilder.Conventions.Add(new FunctionsConvention("dbo")); 69 | } 70 | 71 | public DbSet Customers { get; set; } 72 | public DbSet Orders { get; set; } 73 | 74 | [DbFunction("MultipleResultSetsContext", "CustomersOrdersAndAnswer")] 75 | [DbFunctionDetails(ResultTypes = new[] { typeof(Customer), typeof(Order), typeof(int) })] 76 | public virtual ObjectResult MultipleResultSets([ParameterType(typeof(int))] ObjectParameter answer) 77 | { 78 | return ((IObjectContextAdapter)this).ObjectContext 79 | .ExecuteFunction("CustomersOrdersAndAnswer", answer); 80 | } 81 | } 82 | 83 | class MultipleResultSetsSample 84 | { 85 | public void Run() 86 | { 87 | using (var ctx = new MultipleResultSetsContext()) 88 | { 89 | var answerParam = new ObjectParameter("Answer", typeof (int)); 90 | 91 | var result1 = ctx.MultipleResultSets(answerParam); 92 | 93 | Console.WriteLine("Customers:"); 94 | foreach (var c in result1) 95 | { 96 | Console.WriteLine("Id: {0}, Name: {1}", c.Id, c.Name); 97 | } 98 | 99 | var result2 = result1.GetNextResult(); 100 | 101 | Console.WriteLine("Orders:"); 102 | foreach (var e in result2) 103 | { 104 | Console.WriteLine("Id: {0}, Description: {1}, Customer Name {2}", e.Id, e.Description, e.Customer.Name); 105 | } 106 | 107 | var result3 = result2.GetNextResult(); 108 | Console.WriteLine("Wrong Answer: {0}", result3.Single()); 109 | 110 | Console.WriteLine("Correct answer from output parameter: {0}", answerParam.Value); 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /samples/CodeFirstStoreFunctionsSamples/Program.cs: -------------------------------------------------------------------------------- 1 | namespace CodeFirstStoreFunctionsSamples 2 | { 3 | class Program 4 | { 5 | static void Main(string[] args) 6 | { 7 | new MultipleResultSetsSample().Run(); 8 | new ScalarFunctionSample().Run(); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/CodeFirstStoreFunctionsSamples/ScalarFunctionSample.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace CodeFirstStoreFunctionsSamples 3 | { 4 | using System; 5 | using System.Data.Entity; 6 | using System.Linq; 7 | using CodeFirstStoreFunctions; 8 | 9 | internal class ScalarFunctionContextInitializer : DropCreateDatabaseAlways 10 | { 11 | public override void InitializeDatabase(ScalarFunctionContext context) 12 | { 13 | base.InitializeDatabase(context); 14 | 15 | context.Database.ExecuteSqlCommand( 16 | "CREATE FUNCTION [dbo].[DateTimeToString] (@value datetime) RETURNS nvarchar(26) AS " + 17 | "BEGIN RETURN CONVERT(nvarchar(26), @value, 109) END"); 18 | } 19 | 20 | protected override void Seed(ScalarFunctionContext ctx) 21 | { 22 | ctx.People.AddRange(new[] 23 | { 24 | new Person {Name = "John", DateOfBirth = new DateTime(1954, 12, 15, 23, 37, 0)}, 25 | new Person {Name = "Madison", DateOfBirth = new DateTime(1994, 7, 3, 11, 42, 0)}, 26 | new Person {Name = "Bronek", DateOfBirth = new DateTime(1923, 1, 26, 17, 11, 0)} 27 | }); 28 | } 29 | } 30 | 31 | public class Person 32 | { 33 | public int Id { get; set; } 34 | public string Name { get; set; } 35 | public DateTime DateOfBirth { get; set; } 36 | } 37 | 38 | internal class ScalarFunctionContext : DbContext 39 | { 40 | static ScalarFunctionContext() 41 | { 42 | Database.SetInitializer(new ScalarFunctionContextInitializer()); 43 | } 44 | 45 | public DbSet People { get; set; } 46 | 47 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 48 | { 49 | modelBuilder.Conventions.Add(new FunctionsConvention("dbo", typeof (Functions))); 50 | } 51 | } 52 | 53 | internal static class Functions 54 | { 55 | [DbFunction("CodeFirstDatabaseSchema", "DateTimeToString")] 56 | public static string DateTimeToString(DateTime date) 57 | { 58 | throw new NotSupportedException(); 59 | } 60 | } 61 | 62 | internal class ScalarFunctionSample 63 | { 64 | public void Run() 65 | { 66 | using (var ctx = new ScalarFunctionContext()) 67 | { 68 | Console.WriteLine("Query:"); 69 | 70 | var bornAfterNoon = 71 | ctx.People.Where( 72 | p => Functions.DateTimeToString(p.DateOfBirth).EndsWith("PM")); 73 | 74 | Console.WriteLine(bornAfterNoon.ToString()); 75 | 76 | Console.WriteLine("People born after noon:"); 77 | 78 | foreach (var person in bornAfterNoon) 79 | { 80 | Console.WriteLine("Name {0}, Date of birth: {1}", 81 | person.Name, person.DateOfBirth); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/CodeFirstStoreFunctions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | CodeFirstStoreFunctions 4 | net45;net40;netstandard2.1 5 | 1.2.0.0 6 | true 7 | $(KeyFile) 8 | true 9 | $(DefineConstants);INTERNALSVISIBLETOENABLED 10 | 11 | 12 | 13 | EntityFramework.CodeFirstStoreFunctions 14 | 1.2.0 15 | Pawel "moozzyk" Kadluczka 16 | License.txt 17 | Support for store functions for Entity Framework 6.3.0+ Code First. 18 | Support for store functions (table valued functions, scalar user defined functions and stored procedures) for Entity Framework 6.3.0+ Code First. 19 | Support for store functions (table valued functions, scalar user defined functions and stored procedures) for Entity Framework 6.3.0+ Code First. 20 | https://github.com/moozzyk/CodeFirstFunctions 21 | en-US 22 | EF6 EF6.1 EF Entity Framework Entity-Framework EntityFramework 6.1 Store Functions TVFs TVF table valued functions stored procedure sprocs stored proc scalar functions UDF UDFs user defined functions Code First CodeFirst moozzyk blog.3d-logic.com 23 | false 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/DbFunctionDetailsAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | 7 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] 8 | public class DbFunctionDetailsAttribute : Attribute 9 | { 10 | private bool _isBuiltIn; 11 | private bool _isNiladic; 12 | 13 | /// 14 | /// Gets or sets the name of the database schema of the store function. 15 | /// 16 | public string DatabaseSchema { get; set; } 17 | 18 | /// 19 | /// Gets or sets the name of the name of the the column returned by a function mapped to a collection of scalar results. 20 | /// 21 | public string ResultColumnName { get; set; } 22 | 23 | /// 24 | /// Gets or sets the types returned by a function mapped to a stored procedure returning multuple resultsets. 25 | /// 26 | public Type[] ResultTypes { get; set; } 27 | 28 | /// 29 | /// Gets or sets whether the function is a niladic function. 30 | /// 31 | public bool IsNiladic 32 | { 33 | get 34 | { 35 | return _isNiladic; 36 | } 37 | 38 | set 39 | { 40 | _isNiladic = value; 41 | IsNiladicPropertySet = true; 42 | } 43 | } 44 | 45 | /// 46 | /// Gets or sets whether the function is a built in function. 47 | /// 48 | public bool IsBuiltIn 49 | { 50 | get 51 | { 52 | return _isBuiltIn; 53 | } 54 | 55 | set 56 | { 57 | _isBuiltIn = value; 58 | IsBuiltInPropertySet = true; 59 | } 60 | } 61 | 62 | internal bool IsBuiltInPropertySet { get; private set; } 63 | internal bool IsNiladicPropertySet { get; private set; } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/FunctionDescriptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System.Collections.Generic; 6 | using System.Data.Entity.Core.Metadata.Edm; 7 | using System.Diagnostics; 8 | using System.Linq; 9 | 10 | internal class FunctionDescriptor 11 | { 12 | private readonly string _name; 13 | private readonly EdmType[] _returnTypes; 14 | private readonly ParameterDescriptor[] _parameters; 15 | private readonly string _resultColumnName; 16 | private readonly string _databaseSchema; 17 | private readonly StoreFunctionKind _storeFunctionKind; 18 | private readonly bool? _isBuiltIn; 19 | private readonly bool? _isNiladic; 20 | 21 | public FunctionDescriptor(string name, IEnumerable parameters, 22 | EdmType[] returnTypes, string resultColumnName, string databaseSchema, StoreFunctionKind storeFunctionKind, bool? isBuiltIn, bool? isNiladic) 23 | { 24 | Debug.Assert(!string.IsNullOrWhiteSpace(name), "invalid name"); 25 | Debug.Assert(parameters != null, "parameters is null"); 26 | Debug.Assert(parameters.All(p => p.EdmType != null), "invalid parameter type"); 27 | Debug.Assert(returnTypes != null && returnTypes.Length > 0, "returnTypes array is null or empty"); 28 | Debug.Assert(storeFunctionKind == StoreFunctionKind.StoredProcedure|| returnTypes.Length == 1, "multiple return types for non-sproc"); 29 | 30 | _name = name; 31 | _returnTypes = returnTypes; 32 | _parameters = parameters.ToArray(); 33 | _resultColumnName = resultColumnName; 34 | _databaseSchema = databaseSchema; 35 | _storeFunctionKind = storeFunctionKind; 36 | _isBuiltIn = isBuiltIn; 37 | _isNiladic = isNiladic; 38 | } 39 | 40 | public string Name 41 | { 42 | get { return _name; } 43 | } 44 | 45 | public EdmType[] ReturnTypes 46 | { 47 | get { return _returnTypes; } 48 | } 49 | 50 | public IEnumerable Parameters 51 | { 52 | get { return _parameters; } 53 | } 54 | 55 | public string ResultColumnName 56 | { 57 | get { return _resultColumnName; } 58 | } 59 | 60 | public string DatabaseSchema 61 | { 62 | get { return _databaseSchema; } 63 | } 64 | 65 | public StoreFunctionKind StoreFunctionKind 66 | { 67 | get { return _storeFunctionKind; } 68 | } 69 | 70 | public bool? IsBuiltIn 71 | { 72 | get { return _isBuiltIn; } 73 | } 74 | 75 | public bool? IsNiladic 76 | { 77 | get { return _isNiladic; } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/FunctionDiscovery.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Data.Entity; 8 | using System.Data.Entity.Core.Metadata.Edm; 9 | using System.Data.Entity.Core.Objects; 10 | using System.Data.Entity.Infrastructure; 11 | using System.Diagnostics; 12 | using System.Linq; 13 | using System.Reflection; 14 | using System.Runtime.CompilerServices; 15 | 16 | internal class FunctionDiscovery 17 | { 18 | private readonly DbModel _model; 19 | private readonly Type _type; 20 | 21 | public FunctionDiscovery(DbModel model, Type type) 22 | { 23 | Debug.Assert(model != null, "model is null"); 24 | Debug.Assert(type != null, "type is null"); 25 | 26 | _model = model; 27 | _type = type; 28 | } 29 | 30 | public IEnumerable FindFunctions() 31 | { 32 | const BindingFlags bindingFlags = 33 | BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.InvokeMethod | 34 | BindingFlags.Static | BindingFlags.Instance; 35 | 36 | foreach (var method in _type.GetMethods(bindingFlags)) 37 | { 38 | var functionDescriptor = CreateFunctionDescriptor(method); 39 | if (functionDescriptor != null) 40 | { 41 | yield return functionDescriptor; 42 | } 43 | } 44 | } 45 | 46 | private FunctionDescriptor CreateFunctionDescriptor(MethodInfo method) 47 | { 48 | var functionAttribute = (DbFunctionAttribute)Attribute.GetCustomAttribute(method, typeof(DbFunctionAttribute)); 49 | var returnGenericTypeDefinition = method.ReturnType.IsGenericType 50 | ? method.ReturnType.GetGenericTypeDefinition() 51 | : null; 52 | 53 | if(functionAttribute != null || // TVF, scalar UDF or StoreProc 54 | returnGenericTypeDefinition == typeof (ObjectResult<>)) // StoredProc without DbFunction attribute 55 | { 56 | var functionDetailsAttr = 57 | Attribute.GetCustomAttribute(method, typeof(DbFunctionDetailsAttribute)) as DbFunctionDetailsAttribute; 58 | 59 | var storeFunctionKind = 60 | returnGenericTypeDefinition == typeof (IQueryable<>) 61 | ? StoreFunctionKind.TableValuedFunction 62 | : returnGenericTypeDefinition == typeof (ObjectResult<>) 63 | ? StoreFunctionKind.StoredProcedure 64 | : StoreFunctionKind.ScalarUserDefinedFunction; 65 | 66 | if (storeFunctionKind == StoreFunctionKind.ScalarUserDefinedFunction && 67 | (functionAttribute == null || functionAttribute.NamespaceName != "CodeFirstDatabaseSchema")) 68 | { 69 | throw new InvalidOperationException( 70 | $"Scalar store functions must be decorated with the 'DbFunction' attribute with the 'CodeFirstDatabaseSchema' namespace. Function: '{method.Name}'"); 71 | } 72 | 73 | var unwrapperReturnType = 74 | storeFunctionKind == StoreFunctionKind.ScalarUserDefinedFunction 75 | ? method.ReturnType 76 | : method.ReturnType.GetGenericArguments()[0]; 77 | 78 | return new FunctionDescriptor( 79 | (functionAttribute != null ? functionAttribute.FunctionName : null) ?? method.Name, 80 | GetParameters(method, storeFunctionKind), 81 | GetReturnTypes(method.Name, unwrapperReturnType, functionDetailsAttr, storeFunctionKind), 82 | functionDetailsAttr != null ? functionDetailsAttr.ResultColumnName : null, 83 | functionDetailsAttr != null ? functionDetailsAttr.DatabaseSchema : null, 84 | storeFunctionKind, 85 | GetBuiltInOption(functionDetailsAttr), 86 | GetNiladicOption(functionDetailsAttr)); 87 | } 88 | 89 | return null; 90 | } 91 | 92 | private IEnumerable GetParameters(MethodInfo method, StoreFunctionKind storeFunctionKind) 93 | { 94 | Debug.Assert(method != null, "method is null"); 95 | 96 | foreach (var parameter in method.GetParameters()) 97 | { 98 | if (method.IsDefined(typeof(ExtensionAttribute), false) && parameter.Position == 0) 99 | { 100 | continue; 101 | } 102 | 103 | if (parameter.IsOut || parameter.ParameterType.IsByRef) 104 | { 105 | throw new InvalidOperationException( 106 | $"The parameter '{parameter.Name}' of function '{method.Name}' is an out or ref parameter. To map Input/Output database parameters use the 'ObjectParameter' as the parameter type."); 107 | } 108 | 109 | var paramTypeAttribute = 110 | (ParameterTypeAttribute)Attribute.GetCustomAttribute(parameter, typeof(ParameterTypeAttribute)); 111 | 112 | var parameterType = parameter.ParameterType; 113 | 114 | var isObjectParameter = parameter.ParameterType == typeof (ObjectParameter); 115 | 116 | if (isObjectParameter) 117 | { 118 | if (paramTypeAttribute == null) 119 | { 120 | throw new InvalidOperationException( 121 | $"Cannot infer type for parameter '{parameter.Name}' of funtion '{method.Name}'. All ObjectParameter parameters must be decorated with the ParameterTypeAttribute."); 122 | } 123 | 124 | parameterType = paramTypeAttribute.Type; 125 | } 126 | 127 | var unwrappedParameterType = Nullable.GetUnderlyingType(parameterType) ?? parameterType; 128 | 129 | var parameterEdmType = 130 | unwrappedParameterType.IsEnum 131 | ? FindEnumType(unwrappedParameterType) 132 | : GetEdmPrimitiveTypeForClrType(unwrappedParameterType); 133 | 134 | if (parameterEdmType == null) 135 | { 136 | throw new InvalidOperationException( 137 | $"The type '{unwrappedParameterType.FullName}' of the parameter '{parameter.Name}' of function '{method.Name}' is invalid. Parameters can only be of a type that can be converted to an Edm scalar type"); 138 | } 139 | 140 | if (storeFunctionKind == StoreFunctionKind.ScalarUserDefinedFunction && 141 | parameterEdmType.BuiltInTypeKind != BuiltInTypeKind.PrimitiveType) 142 | { 143 | throw new InvalidOperationException( 144 | $"The parameter '{parameter.Name}' of function '{method.Name}' is of the '{parameterEdmType}' type which is not an Edm primitive type. Types of parameters of store scalar functions must be Edm primitive types."); 145 | } 146 | 147 | yield return new ParameterDescriptor(parameter.Name, parameterEdmType, 148 | paramTypeAttribute != null ? paramTypeAttribute.StoreType : null, isObjectParameter); 149 | } 150 | } 151 | 152 | private EdmType[] GetReturnTypes(string methodName, Type methodReturnType, 153 | DbFunctionDetailsAttribute functionDetailsAttribute, StoreFunctionKind storeFunctionKind) 154 | { 155 | Debug.Assert(methodReturnType != null, "methodReturnType is null"); 156 | 157 | var resultTypes = functionDetailsAttribute?.ResultTypes; 158 | 159 | if (storeFunctionKind != StoreFunctionKind.StoredProcedure && resultTypes != null) 160 | { 161 | throw new InvalidOperationException( 162 | $"The DbFunctionDetailsAttribute.ResultTypes property should be used only for stored procedures returning multiple resultsets and must be null for composable function imports. Function: '{methodName}'"); 163 | } 164 | 165 | resultTypes = resultTypes == null || resultTypes.Length == 0 ? null : resultTypes; 166 | 167 | if (resultTypes != null && resultTypes[0] != methodReturnType) 168 | { 169 | throw new InvalidOperationException( 170 | $"The ObjectResult item type returned by the function '{methodName}' is '{methodReturnType.FullName}' but the first type specified in the `DbFunctionDetailsAttribute.ResultTypes` is '{resultTypes[0].FullName}'. The ObjectResult item type must match the first type from the `DbFunctionDetailsAttribute.ResultTypes` array."); 171 | } 172 | 173 | var edmResultTypes = (resultTypes ?? new[] {methodReturnType}).Select(GetReturnEdmItemType).ToArray(); 174 | 175 | if (storeFunctionKind == StoreFunctionKind.ScalarUserDefinedFunction && 176 | edmResultTypes[0].BuiltInTypeKind != BuiltInTypeKind.PrimitiveType) 177 | { 178 | throw new InvalidOperationException( 179 | $"The type '{methodReturnType.FullName}' returned by the function '{methodName}' cannot be mapped to an Edm primitive type. Scalar user defined functions have to return types that can be mapped to Edm primitive types."); 180 | } 181 | 182 | return edmResultTypes; 183 | } 184 | 185 | private EdmType GetReturnEdmItemType(Type type) 186 | { 187 | var unwrappedType = Nullable.GetUnderlyingType(type) ?? type; 188 | 189 | var edmType = GetEdmPrimitiveTypeForClrType(unwrappedType); 190 | if (edmType != null) 191 | { 192 | return edmType; 193 | } 194 | 195 | if (unwrappedType.IsEnum) 196 | { 197 | if ((edmType = FindEnumType(unwrappedType)) != null) 198 | { 199 | return edmType; 200 | } 201 | } 202 | else 203 | { 204 | if ((edmType = FindStructuralType(unwrappedType)) != null) 205 | { 206 | return edmType; 207 | } 208 | } 209 | 210 | throw new InvalidOperationException($"No EdmType found for type '{type.FullName}'."); 211 | } 212 | 213 | private static EdmType GetEdmPrimitiveTypeForClrType(Type clrType) 214 | { 215 | return PrimitiveType 216 | .GetEdmPrimitiveTypes() 217 | .FirstOrDefault(t => t.ClrEquivalentType == clrType); 218 | } 219 | 220 | private EdmType FindStructuralType(Type type) 221 | { 222 | return 223 | ((IEnumerable)_model.ConceptualModel.EntityTypes) 224 | .Concat(_model.ConceptualModel.ComplexTypes) 225 | .FirstOrDefault(t => t.Name == type.Name); 226 | } 227 | 228 | private EdmType FindEnumType(Type type) 229 | { 230 | return _model.ConceptualModel.EnumTypes.FirstOrDefault(t => t.Name == type.Name); 231 | } 232 | 233 | private bool? GetBuiltInOption(DbFunctionDetailsAttribute functionDetailsAttribute) 234 | { 235 | return (functionDetailsAttribute != null && functionDetailsAttribute.IsBuiltInPropertySet) 236 | ? functionDetailsAttribute.IsBuiltIn 237 | : (bool?)null; 238 | } 239 | 240 | private bool? GetNiladicOption(DbFunctionDetailsAttribute functionDetailsAttribute) 241 | { 242 | return (functionDetailsAttribute != null && functionDetailsAttribute.IsNiladicPropertySet) 243 | ? functionDetailsAttribute.IsNiladic 244 | : (bool?)null; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/FunctionsConvention.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | using System.Data.Entity.Core.Mapping; 7 | using System.Data.Entity.Core.Metadata.Edm; 8 | using System.Data.Entity.Infrastructure; 9 | using System.Data.Entity.ModelConfiguration.Conventions; 10 | using System.Diagnostics; 11 | using System.Linq; 12 | 13 | public class FunctionsConvention : IStoreModelConvention 14 | { 15 | private readonly string _defaultSchema; 16 | private readonly Type _methodClassType; 17 | 18 | public FunctionsConvention(string defaultSchema, Type methodClassType) 19 | { 20 | _defaultSchema = defaultSchema; 21 | _methodClassType = methodClassType; 22 | } 23 | 24 | public void Apply(EntityContainer item, DbModel model) 25 | { 26 | var functionDescriptors = new FunctionDiscovery(model, _methodClassType).FindFunctions(); 27 | var storeFunctionBuilder = new StoreFunctionBuilder(model, _defaultSchema); 28 | 29 | foreach (var functionDescriptor in functionDescriptors) 30 | { 31 | var storeFunctionDefinition = storeFunctionBuilder.Create(functionDescriptor); 32 | model.StoreModel.AddItem(storeFunctionDefinition); 33 | 34 | if (functionDescriptor.StoreFunctionKind != StoreFunctionKind.ScalarUserDefinedFunction) 35 | { 36 | var functionImportDefinition = CreateFunctionImport(model, functionDescriptor); 37 | model.ConceptualModel.Container.AddFunctionImport(functionImportDefinition); 38 | 39 | if (functionImportDefinition.IsComposableAttribute) 40 | { 41 | model.ConceptualToStoreMapping.AddFunctionImportMapping( 42 | new FunctionImportMappingComposable( 43 | functionImportDefinition, 44 | storeFunctionDefinition, 45 | new FunctionImportResultMapping(), 46 | model.ConceptualToStoreMapping)); 47 | } 48 | else 49 | { 50 | model.ConceptualToStoreMapping.AddFunctionImportMapping( 51 | new FunctionImportMappingNonComposable( 52 | functionImportDefinition, 53 | storeFunctionDefinition, 54 | new FunctionImportResultMapping[0], 55 | model.ConceptualToStoreMapping)); 56 | } 57 | } 58 | } 59 | 60 | // TODO: model defined functions? 61 | } 62 | 63 | private static EdmFunction CreateFunctionImport(DbModel model, FunctionDescriptor functionImport) 64 | { 65 | EntitySet[] entitySets; 66 | FunctionParameter[] returnParameters; 67 | CreateReturnParameters(model, functionImport, out returnParameters, out entitySets); 68 | 69 | var functionPayload = 70 | new EdmFunctionPayload 71 | { 72 | Parameters = 73 | functionImport 74 | .Parameters 75 | .Select( 76 | p => FunctionParameter.Create(p.Name, p.EdmType, 77 | p.IsOutParam ? ParameterMode.InOut : ParameterMode.In)) 78 | .ToList(), 79 | ReturnParameters = returnParameters, 80 | IsComposable = functionImport.StoreFunctionKind == StoreFunctionKind.TableValuedFunction, 81 | IsFunctionImport = true, 82 | EntitySets = entitySets 83 | }; 84 | 85 | return EdmFunction.Create( 86 | functionImport.Name, 87 | model.ConceptualModel.Container.Name, 88 | DataSpace.CSpace, 89 | functionPayload, 90 | null); 91 | } 92 | 93 | private static void CreateReturnParameters(DbModel model, FunctionDescriptor functionImport, 94 | out FunctionParameter[] returnParameters, out EntitySet[] entitySets) 95 | { 96 | var resultCount = functionImport.ReturnTypes.Count(); 97 | entitySets = new EntitySet[resultCount]; 98 | returnParameters = new FunctionParameter[resultCount]; 99 | 100 | for (int i = 0; i < resultCount; i++) 101 | { 102 | var returnType = functionImport.ReturnTypes[i]; 103 | 104 | if (returnType.BuiltInTypeKind == BuiltInTypeKind.EntityType) 105 | { 106 | var types = Tools.GetTypeHierarchy(returnType); 107 | 108 | var matchingEntitySets = 109 | model.ConceptualModel.Container.EntitySets 110 | .Where(s => types.Contains(s.ElementType)) 111 | .ToArray(); 112 | 113 | if (matchingEntitySets.Length == 0) 114 | { 115 | throw new InvalidOperationException( 116 | $"The model does not contain EntitySet for the '{returnType.FullName}' entity type."); 117 | } 118 | 119 | Debug.Assert(matchingEntitySets.Length == 1, "Invalid model (MEST)"); 120 | 121 | entitySets[i] = matchingEntitySets[0]; 122 | } 123 | 124 | returnParameters[i] = FunctionParameter.Create( 125 | "ReturnParam" + i, 126 | returnType.GetCollectionType(), 127 | ParameterMode.ReturnValue); 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/FunctionsConvention`1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | using System.Data.Entity; 7 | 8 | public class FunctionsConvention : FunctionsConvention 9 | where T : DbContext 10 | { 11 | public FunctionsConvention(string defaultSchema) 12 | : base(defaultSchema, typeof(T)) 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/ParameterDescriptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System.Data.Entity.Core.Metadata.Edm; 6 | using System.Diagnostics; 7 | 8 | internal class ParameterDescriptor 9 | { 10 | private readonly string _name; 11 | private readonly EdmType _edmType; 12 | private readonly string _storeType; 13 | private readonly bool _isOutParam; 14 | 15 | public ParameterDescriptor(string name, EdmType edmType, string storeType, bool isOutParam) 16 | { 17 | Debug.Assert(name != null, "name is null"); 18 | Debug.Assert(edmType != null, "edmType is null"); 19 | 20 | _name = name; 21 | _edmType = edmType; 22 | _isOutParam = isOutParam; 23 | _storeType = storeType; 24 | } 25 | 26 | public string Name 27 | { 28 | get { return _name; } 29 | } 30 | 31 | public EdmType EdmType 32 | { 33 | get { return _edmType; } 34 | } 35 | 36 | public string StoreType 37 | { 38 | get { return _storeType; } 39 | } 40 | 41 | public bool IsOutParam 42 | { 43 | get { return _isOutParam; } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/ParameterTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | 7 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 8 | public class ParameterTypeAttribute : Attribute 9 | { 10 | public ParameterTypeAttribute() 11 | { 12 | } 13 | 14 | public ParameterTypeAttribute(Type type) 15 | { 16 | Type = type; 17 | } 18 | 19 | public Type Type { get; set; } 20 | 21 | public string StoreType { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System.Runtime.CompilerServices; 4 | 5 | #if INTERNALSVISIBLETOENABLED 6 | 7 | [assembly: InternalsVisibleTo("CodeFirstStoreFunctionsTests")] 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/StoreFunctionBuilder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Data.Entity.Core.Mapping; 8 | using System.Data.Entity.Core.Metadata.Edm; 9 | using System.Data.Entity.Infrastructure; 10 | using System.Diagnostics; 11 | using System.Linq; 12 | 13 | internal class StoreFunctionBuilder 14 | { 15 | private readonly DbModel _model; 16 | private readonly string _schema; 17 | private readonly string _namespace; 18 | 19 | public StoreFunctionBuilder(DbModel model, string schema, string @namespace = null) 20 | { 21 | Debug.Assert(model != null, "model is null"); 22 | 23 | _model = model; 24 | _schema = schema; 25 | 26 | // CodeFirstDatabaseSchema is what EF CodeFirst model builder uses for store model 27 | _namespace = @namespace ?? "CodeFirstDatabaseSchema"; 28 | } 29 | 30 | public EdmFunction Create(FunctionDescriptor functionDescriptor) 31 | { 32 | Debug.Assert(functionDescriptor != null, "functionDescriptor is null"); 33 | 34 | if (_schema == null && functionDescriptor.DatabaseSchema == null) 35 | { 36 | throw new InvalidOperationException( 37 | $"Database schema is not defined for function '{functionDescriptor.Name}'. Either set a default database schema or use the DbFunctionEx attribute with non-null DatabaseSchema value."); 38 | } 39 | 40 | var functionPayload = 41 | new EdmFunctionPayload 42 | { 43 | Parameters = functionDescriptor 44 | .Parameters 45 | .Select( 46 | p => FunctionParameter.Create( 47 | p.Name, 48 | GetStorePrimitiveType(p), 49 | p.IsOutParam 50 | ? ParameterMode.InOut 51 | : ParameterMode.In)).ToArray(), 52 | 53 | ReturnParameters = CreateFunctionReturnParameters(functionDescriptor), 54 | IsComposable = functionDescriptor.StoreFunctionKind != StoreFunctionKind.StoredProcedure, 55 | Schema = functionDescriptor.DatabaseSchema ?? _schema, 56 | IsBuiltIn = functionDescriptor.IsBuiltIn, 57 | IsNiladic = functionDescriptor.IsNiladic 58 | }; 59 | 60 | return EdmFunction.Create( 61 | functionDescriptor.Name, 62 | _namespace, 63 | DataSpace.SSpace, 64 | functionPayload, 65 | null); 66 | } 67 | 68 | private List CreateFunctionReturnParameters(FunctionDescriptor functionDescriptor) 69 | { 70 | var returnParameters = new List(); 71 | 72 | EdmType returnEdmType = null; 73 | switch (functionDescriptor.StoreFunctionKind) 74 | { 75 | case StoreFunctionKind.TableValuedFunction: 76 | Debug.Assert(functionDescriptor.ReturnTypes.Length == 1, "Expected only one returnType for composable functions"); 77 | returnEdmType = 78 | CreateReturnRowType(functionDescriptor.ResultColumnName, functionDescriptor.ReturnTypes[0]) 79 | .GetCollectionType(); 80 | break; 81 | case StoreFunctionKind.ScalarUserDefinedFunction: 82 | var returnPrimtiveType = functionDescriptor.ReturnTypes[0].BuiltInTypeKind == BuiltInTypeKind.EnumType 83 | ? ((EnumType) functionDescriptor.ReturnTypes[0]).UnderlyingType 84 | : functionDescriptor.ReturnTypes[0]; 85 | returnEdmType = GetStorePrimitiveType(returnPrimtiveType); 86 | break; 87 | } 88 | 89 | if (returnEdmType != null) 90 | { 91 | returnParameters.Add( 92 | FunctionParameter.Create( 93 | "ReturnParam", 94 | returnEdmType, 95 | ParameterMode.ReturnValue)); 96 | } 97 | 98 | return returnParameters; 99 | } 100 | 101 | private EdmType CreateReturnRowType(string propertyName, EdmType edmType) 102 | { 103 | if (edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType) 104 | { 105 | var propertyToSoreTypeUsage = FindStoreTypeUsages((EntityType)edmType); 106 | return 107 | RowType.Create( 108 | ((EntityType) edmType).Properties.Select( 109 | m => EdmProperty.Create(m.Name, propertyToSoreTypeUsage[m])), null); 110 | } 111 | 112 | if (edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType) 113 | { 114 | return 115 | RowType.Create( 116 | ((StructuralType) edmType).Members.Select( 117 | m => m.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.EnumType ? 118 | EdmProperty.Create(m.Name, GetStorePrimitiveTypeUsage(TypeUsage.CreateDefaultTypeUsage(((EnumType)m.TypeUsage.EdmType).UnderlyingType))) : 119 | EdmProperty.Create(m.Name, GetStorePrimitiveTypeUsage(m.TypeUsage))), null); 120 | } 121 | 122 | if (edmType.BuiltInTypeKind == BuiltInTypeKind.EnumType) 123 | { 124 | return RowType.Create(new[] 125 | { 126 | EdmProperty.Create(propertyName, GetStorePrimitiveTypeUsage(TypeUsage.CreateDefaultTypeUsage(((EnumType)edmType).UnderlyingType))) 127 | }, null); 128 | } 129 | 130 | return 131 | RowType.Create( 132 | new[] 133 | { 134 | EdmProperty.Create(propertyName, GetStorePrimitiveTypeUsage(TypeUsage.CreateDefaultTypeUsage(edmType))) 135 | }, null); 136 | } 137 | 138 | private Dictionary FindStoreTypeUsages(EntityType entityType) 139 | { 140 | Debug.Assert(entityType != null, "entityType == null"); 141 | 142 | var propertyToStoreTypeUsage = new Dictionary(); 143 | 144 | var types = Tools.GetTypeHierarchy(entityType); 145 | var entityTypeMappings = 146 | _model.ConceptualToStoreMapping.EntitySetMappings 147 | .SelectMany(s => s.EntityTypeMappings) 148 | .Where(t => types.Contains(t.EntityType)) 149 | .ToArray(); 150 | 151 | foreach (var property in entityType.Properties) 152 | { 153 | foreach (var entityTypeMapping in entityTypeMappings) 154 | { 155 | var propertyMapping = 156 | (ScalarPropertyMapping)entityTypeMapping.Fragments.SelectMany(f => f.PropertyMappings) 157 | .FirstOrDefault(p => p.Property == property); 158 | 159 | if (propertyMapping != null) 160 | { 161 | Debug.Assert(!propertyToStoreTypeUsage.ContainsKey(property), "Property already in dictionary"); 162 | 163 | propertyToStoreTypeUsage[property] = TypeUsage.Create( 164 | propertyMapping.Column.TypeUsage.EdmType, 165 | propertyMapping.Column.TypeUsage.Facets.Where( 166 | f => f.Name != "StoreGeneratedPattern" && f.Name != "ConcurrencyMode")); 167 | 168 | break; 169 | } 170 | } 171 | } 172 | 173 | return propertyToStoreTypeUsage; 174 | } 175 | 176 | private TypeUsage GetStorePrimitiveTypeUsage(TypeUsage typeUsage) 177 | { 178 | Debug.Assert(typeUsage != null, "typeUsage is null"); 179 | Debug.Assert(typeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType, "expected primitive type"); 180 | 181 | return _model.ProviderManifest.GetStoreType(typeUsage); 182 | } 183 | 184 | private EdmType GetStorePrimitiveType(ParameterDescriptor parameterDescriptor) 185 | { 186 | if (parameterDescriptor.StoreType != null) 187 | { 188 | var type = 189 | _model.ProviderManifest.GetStoreTypes() 190 | .SingleOrDefault(t => t.Name == parameterDescriptor.StoreType); 191 | 192 | if (type == null) 193 | { 194 | throw new InvalidOperationException( 195 | $"No store EdmType with the name '{parameterDescriptor.StoreType}' could be found."); 196 | } 197 | 198 | return type; 199 | } 200 | 201 | return GetStorePrimitiveType( 202 | parameterDescriptor.EdmType.BuiltInTypeKind == BuiltInTypeKind.EnumType 203 | ? ((EnumType) parameterDescriptor.EdmType).UnderlyingType 204 | : parameterDescriptor.EdmType); 205 | } 206 | 207 | private EdmType GetStorePrimitiveType(EdmType edmType) 208 | { 209 | Debug.Assert(edmType != null, "edmType is null"); 210 | Debug.Assert(edmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType, "expected primitive type"); 211 | 212 | return _model.ProviderManifest.GetStoreType(TypeUsage.CreateDefaultTypeUsage(edmType)).EdmType; 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/StoreFunctionKind.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | internal enum StoreFunctionKind 6 | { 7 | StoredProcedure, 8 | TableValuedFunction, 9 | ScalarUserDefinedFunction 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/CodeFirstStoreFunctions/Tools.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System.Collections.Generic; 6 | using System.Data.Entity.Core.Metadata.Edm; 7 | using System.Diagnostics; 8 | 9 | internal static class Tools 10 | { 11 | internal static List GetTypeHierarchy(EdmType edmType) 12 | { 13 | Debug.Assert(edmType != null, "edmType is null"); 14 | Debug.Assert(edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "entity type expected"); 15 | 16 | var types = new List {edmType}; 17 | while (edmType.BaseType != null) 18 | { 19 | edmType = edmType.BaseType; 20 | types.Add(edmType); 21 | } 22 | 23 | return types; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/CodeFirstStoreFunctionsTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452;netcoreapp3.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/DbFunctionDetailsAttributeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | 5 | namespace CodeFirstStoreFunctions 6 | { 7 | using Xunit; 8 | 9 | public class DbFunctionDetailsAttributeTests 10 | { 11 | [Fact] 12 | public void Can_set_get_schema() 13 | { 14 | var attr = new DbFunctionDetailsAttribute(); 15 | 16 | Assert.Null(attr.DatabaseSchema); 17 | 18 | attr.DatabaseSchema = "dbo"; 19 | 20 | Assert.Equal("dbo", attr.DatabaseSchema); 21 | } 22 | 23 | [Fact] 24 | public void Can_set_get_ResultColumnName() 25 | { 26 | var attr = new DbFunctionDetailsAttribute(); 27 | 28 | Assert.Null(attr.ResultColumnName); 29 | 30 | attr.ResultColumnName = "column"; 31 | 32 | Assert.Equal("column", attr.ResultColumnName); 33 | } 34 | 35 | [Fact] 36 | public void Can_get_set_ResultTypes() 37 | { 38 | var resultTypes = new Type[0]; 39 | var attr = new DbFunctionDetailsAttribute(); 40 | 41 | Assert.Null(attr.ResultTypes); 42 | 43 | attr.ResultTypes = resultTypes; 44 | 45 | Assert.Same(resultTypes, attr.ResultTypes); 46 | } 47 | 48 | [Fact] 49 | public void Is_BuiltIn_false_by_default_and_not_marked_as_set() 50 | { 51 | var attr = new DbFunctionDetailsAttribute(); 52 | Assert.False(attr.IsBuiltIn); 53 | Assert.False(attr.IsBuiltInPropertySet); 54 | } 55 | 56 | [Fact] 57 | public void Is_BuiltIn_false_when_set_to_false_and_marked_as_set() 58 | { 59 | var attr = new DbFunctionDetailsAttribute(); 60 | attr.IsBuiltIn = false; 61 | Assert.False(attr.IsBuiltIn); 62 | Assert.True(attr.IsBuiltInPropertySet); 63 | } 64 | 65 | [Fact] 66 | public void Is_BuiltIn_true_when_set_to_true_and_marked_as_set() 67 | { 68 | var attr = new DbFunctionDetailsAttribute(); 69 | attr.IsBuiltIn = true; 70 | Assert.True(attr.IsBuiltIn); 71 | Assert.True(attr.IsBuiltInPropertySet); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/E2ETests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | using System.ComponentModel.DataAnnotations; 7 | using System.Data.Entity; 8 | using System.Data.Entity.Core.Objects; 9 | using System.Data.Entity.Infrastructure; 10 | using System.Linq; 11 | using Xunit; 12 | 13 | public enum AirportType { Regional, International } 14 | 15 | public class Airport 16 | { 17 | [Key] 18 | public string IATACode { get; set; } 19 | public string CityCode { get; set; } 20 | public string CountryCode { get; set; } 21 | public string Name { get; set; } 22 | public byte TerminalCount { get; set; } 23 | public AirportType Type { get; set; } 24 | } 25 | 26 | public class Airport_ResultType 27 | { 28 | public string IATACode { get; set; } 29 | public string CityCode { get; set; } 30 | public string CountryCode { get; set; } 31 | public string Name { get; set; } 32 | public byte TerminalCount { get; set; } 33 | public AirportType Type { get; set; } 34 | } 35 | 36 | public class Vehicle 37 | { 38 | public int Id { get; set; } 39 | 40 | public DateTime ProductionDate { get; set; } 41 | } 42 | 43 | public class Aircraft : Vehicle 44 | { 45 | public string Code { get; set; } 46 | } 47 | 48 | public class MyContext : DbContext 49 | { 50 | static MyContext() 51 | { 52 | Database.SetInitializer(new MyContextInitializer()); 53 | } 54 | 55 | public MyContext() 56 | : base( 57 | #if NETCOREAPP3_0 58 | "MyContext_NetCoreApp" 59 | #else 60 | "MyContext" 61 | #endif 62 | ) { 63 | } 64 | 65 | public DbSet Airports { get; set; } 66 | 67 | public DbSet Vehicles { get; set; } 68 | 69 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 70 | { 71 | modelBuilder.Entity().Property(a => a.IATACode).IsFixedLength().IsUnicode().HasMaxLength(3); 72 | modelBuilder.Entity().Property(a => a.CityCode).IsFixedLength().IsUnicode().HasMaxLength(3); 73 | modelBuilder.Entity().Property(a => a.CountryCode).IsFixedLength().IsUnicode().HasMaxLength(2); 74 | 75 | modelBuilder.ComplexType(); 76 | 77 | modelBuilder.Conventions.Add(new FunctionsConvention("dbo")); 78 | } 79 | 80 | [DbFunctionDetails(ResultColumnName = "TerminalCount")] 81 | [DbFunction("CodeFirstStoreFunctions", "GetUniqueTerminalCount")] 82 | public virtual IQueryable GetUniqueTerminalCount() 83 | { 84 | return ((IObjectContextAdapter)this).ObjectContext 85 | .CreateQuery( 86 | string.Format("[{0}].{1}", GetType().Name, "[GetUniqueTerminalCount]()")); 87 | } 88 | 89 | [DbFunctionDetails(ResultColumnName = "Type")] 90 | [DbFunction("CodeFirstStoreFunctions", "GetAirportType")] 91 | public virtual IQueryable GetAirportType(AirportType airportType) 92 | { 93 | var airportTypeParameter = new ObjectParameter("AirportType", airportType); 94 | 95 | return ((IObjectContextAdapter)this).ObjectContext 96 | .CreateQuery( 97 | string.Format("[{0}].{1}", GetType().Name, "[GetAirportType](@AirportType)"), 98 | airportTypeParameter); 99 | } 100 | 101 | [DbFunctionDetails(ResultColumnName = "Type")] 102 | [DbFunction("CodeFirstStoreFunctions", "GetPassedAirportType")] 103 | public virtual IQueryable GetPassedAirportType(int? airportType) 104 | { 105 | var airportTypeParameter = 106 | airportType.HasValue 107 | ? new ObjectParameter("AirportType", airportType) 108 | : new ObjectParameter("AirportType", typeof (int?)); 109 | 110 | return ((IObjectContextAdapter)this).ObjectContext 111 | .CreateQuery( 112 | string.Format("[{0}].{1}", GetType().Name, "[GetPassedAirportType](@AirportType)"), 113 | airportTypeParameter); 114 | } 115 | 116 | [DbFunctionDetails(ResultColumnName = "Type")] 117 | [DbFunction("CodeFirstStoreFunctions", "GetPassedAirportTypeEnum")] 118 | public virtual IQueryable GetPassedAirportTypeEnum(AirportType? airportType) 119 | { 120 | var airportTypeParameter = 121 | airportType.HasValue 122 | ? new ObjectParameter("AirportType", airportType) 123 | : new ObjectParameter("AirportType", typeof(AirportType?)); 124 | 125 | return ((IObjectContextAdapter)this).ObjectContext 126 | .CreateQuery( 127 | string.Format("[{0}].{1}", GetType().Name, "[GetPassedAirportTypeEnum](@AirportType)"), 128 | airportTypeParameter); 129 | } 130 | 131 | [DbFunction("CodeFirstStoreFunctions", "GetAirports_ComplexType")] 132 | public virtual IQueryable GetAirports_ComplexType(string countryCode) 133 | { 134 | var countryCodeParameter = countryCode != null ? 135 | new ObjectParameter("CountryCode", countryCode) : 136 | new ObjectParameter("CountryCode", typeof(string)); 137 | 138 | return ((IObjectContextAdapter)this).ObjectContext 139 | .CreateQuery( 140 | string.Format("[{0}].{1}", GetType().Name, "[GetAirports_ComplexType](@CountryCode)"), 141 | countryCodeParameter); 142 | } 143 | 144 | [DbFunction("MyContext", "GetAirports")] 145 | public virtual IQueryable GetAirports(string countryCode) 146 | { 147 | var countryCodeParameter = countryCode != null ? 148 | new ObjectParameter("CountryCode", countryCode) : 149 | new ObjectParameter("CountryCode", typeof(string)); 150 | 151 | return ((IObjectContextAdapter)this).ObjectContext 152 | .CreateQuery( 153 | string.Format("[{0}].{1}", GetType().Name, "[GetAirports](@CountryCode)"), 154 | countryCodeParameter); 155 | } 156 | 157 | [DbFunction("MyContext", "GetAircraft")] 158 | public virtual IQueryable GetAircraft() 159 | { 160 | return ((IObjectContextAdapter)this).ObjectContext 161 | .CreateQuery( 162 | string.Format("[{0}].{1}", GetType().Name, "[GetAircraft]()")); 163 | } 164 | 165 | [DbFunction("MyContext", "MyCustomTVF")] 166 | [DbFunctionDetails(ResultColumnName = "Number")] 167 | public virtual IQueryable FunctionWhoseNameIsDifferentThenTVFName() 168 | { 169 | return ((IObjectContextAdapter)this).ObjectContext 170 | .CreateQuery( 171 | string.Format("[{0}].{1}", GetType().Name, "[MyCustomTVF]()")); 172 | } 173 | 174 | [DbFunction("MyContext", "GetUniqueTerminalCountSP")] 175 | public virtual ObjectResult GetUniqueTerminalCountSProc() 176 | { 177 | return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GetUniqueTerminalCountSP"); 178 | } 179 | 180 | public virtual ObjectResult GetAirports_ComplexTypeSP(string countryCode) 181 | { 182 | var countryCodeParameter = countryCode != null ? 183 | new ObjectParameter("CountryCode", countryCode) : 184 | new ObjectParameter("CountryCode", typeof(string)); 185 | 186 | return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GetAirports_ComplexTypeSP", countryCodeParameter); 187 | } 188 | 189 | public ObjectResult GetAirportsSP(string countryCode) 190 | { 191 | var countryCodeParameter = countryCode != null ? 192 | new ObjectParameter("CountryCode", countryCode) : 193 | new ObjectParameter("CountryCode", typeof(string)); 194 | 195 | return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GetAirportsSP", countryCodeParameter); 196 | } 197 | 198 | public virtual ObjectResult GetAirportTypeSP(AirportType airportType) 199 | { 200 | var airportTypeParameter = new ObjectParameter("AirportType", airportType); 201 | 202 | return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GetAirportTypeSP", airportTypeParameter); 203 | } 204 | 205 | public virtual ObjectResult GetAircraftSP() 206 | { 207 | return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GetAircraftSP"); 208 | } 209 | 210 | [DbFunctionDetails( 211 | ResultTypes = new[] {typeof (Airport_ResultType), typeof (Airport), typeof (Aircraft), typeof (int)})] 212 | public virtual ObjectResult MultipleResultSets() 213 | { 214 | return ((IObjectContextAdapter) this).ObjectContext 215 | .ExecuteFunction("MultipleResultSets"); 216 | } 217 | 218 | public ObjectResult GetAirportTypesWithOutputParameter( 219 | [ParameterType(typeof(AirportType?))] ObjectParameter airportType) 220 | { 221 | return ((IObjectContextAdapter)this).ObjectContext 222 | .ExecuteFunction("GetAirportTypesWithOutputParameter", airportType); 223 | } 224 | 225 | [DbFunction("CodeFirstDatabaseSchema", "EchoNumber")] 226 | public static int? ScalarFuncEchoNumber(int? number) 227 | { 228 | throw new NotSupportedException(); 229 | } 230 | 231 | [DbFunctionDetails(ResultColumnName = "Number")] 232 | [DbFunction("MyContext", "GetXmlInfo")] 233 | public virtual ObjectResult GetXmlInfo([ParameterType(typeof(string), StoreType = "xml")] ObjectParameter xml) 234 | { 235 | return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("GetXmlInfo", xml); 236 | } 237 | 238 | [DbFunction("CodeFirstDatabaseSchema", "SQUARE")] 239 | [DbFunctionDetails(IsBuiltIn = true)] 240 | public static float Square(float number) 241 | { 242 | throw new NotSupportedException(); 243 | } 244 | 245 | [DbFunction("CodeFirstDatabaseSchema", "FORMAT")] 246 | [DbFunctionDetails(IsBuiltIn = true)] 247 | public static string Format(DateTime dateTime, string format, string culture) 248 | { 249 | throw new NotSupportedException(); 250 | } 251 | 252 | [DbFunction("CodeFirstDatabaseSchema", "CURRENT_TIMESTAMP")] 253 | [DbFunctionDetails(IsBuiltIn = true, IsNiladic = true)] 254 | public static DateTime? CurrentTimestamp() 255 | { 256 | throw new NotSupportedException(); 257 | } 258 | } 259 | 260 | #region initializer 261 | 262 | public class MyContextInitializer : DropCreateDatabaseIfModelChanges 263 | // public class MyContextInitializer : DropCreateDatabaseAlways 264 | { 265 | protected override void Seed(MyContext context) 266 | { 267 | context.Airports.Add( 268 | new Airport 269 | { 270 | IATACode = "WRO", 271 | CityCode = "WRO", 272 | CountryCode = "PL", 273 | Name = "Wroclaw Copernicus Airport", 274 | TerminalCount = 2, 275 | Type = AirportType.International 276 | }); 277 | 278 | context.Airports.Add( 279 | new Airport 280 | { 281 | IATACode = "LHR", 282 | CityCode = "LON", 283 | CountryCode = "GB", 284 | Name = "London Heathrow Airport", 285 | TerminalCount = 5, 286 | Type = AirportType.International 287 | }); 288 | 289 | context.Airports.Add( 290 | new Airport 291 | { 292 | IATACode = "LTN", 293 | CityCode = "LON", 294 | CountryCode = "GB", 295 | Name = "London Luton Airport", 296 | TerminalCount = 1, 297 | Type = AirportType.International 298 | }); 299 | 300 | context.Airports.Add( 301 | new Airport 302 | { 303 | IATACode = "SEA", 304 | CityCode = "SEA", 305 | CountryCode = "US", 306 | Name = "Seattle-Tacoma International Airport", 307 | TerminalCount = 1, 308 | Type = AirportType.International 309 | }); 310 | 311 | context.Airports.Add( 312 | new Airport 313 | { 314 | IATACode = "OLM", 315 | CityCode = "OLM", 316 | CountryCode = "US", 317 | Name = "Olympia Regional Airport", 318 | TerminalCount = 1, 319 | Type = AirportType.Regional 320 | }); 321 | 322 | context.Vehicles.Add(new Aircraft 323 | { 324 | Code = "AT7", 325 | ProductionDate = new DateTime(1929, 12, 7) 326 | }); 327 | 328 | context.SaveChanges(); 329 | 330 | context.Database.ExecuteSqlCommand( 331 | "CREATE FUNCTION [dbo].[GetAirports] " + 332 | " (@CountryCode nchar(3)) " + 333 | "RETURNS TABLE " + 334 | "RETURN " + 335 | "SELECT [IATACode], " + 336 | " [CityCode], " + 337 | " [CountryCode], " + 338 | " [Name], " + 339 | " [TerminalCount], " + 340 | " [Type] " + 341 | "FROM [dbo].[Airports] " + 342 | "WHERE [CountryCode] = @CountryCode"); 343 | 344 | context.Database.ExecuteSqlCommand( 345 | "CREATE FUNCTION [dbo].[GetAirports_ComplexType] " + 346 | " (@CountryCode nchar(3)) " + 347 | "RETURNS TABLE " + 348 | "RETURN " + 349 | "SELECT [IATACode], " + 350 | " [CityCode], " + 351 | " [CountryCode], " + 352 | " [Name], " + 353 | " [TerminalCount], " + 354 | " [Type] " + 355 | "FROM [dbo].[Airports] " + 356 | "WHERE [CountryCode] = @CountryCode"); 357 | 358 | context.Database.ExecuteSqlCommand( 359 | "CREATE FUNCTION [dbo].[GetUniqueTerminalCount]()" + 360 | "RETURNS TABLE " + 361 | "RETURN " + 362 | "SELECT DISTINCT [TerminalCount] " + 363 | "FROM [dbo].[Airports]"); 364 | 365 | context.Database.ExecuteSqlCommand( 366 | "CREATE FUNCTION [dbo].[GetAirportType](@AirportType int) " + 367 | "RETURNS TABLE " + 368 | "RETURN " + 369 | "SELECT [Type] " + 370 | "FROM [dbo].[Airports] " + 371 | "WHERE [Type] = @AirportType"); 372 | 373 | context.Database.ExecuteSqlCommand( 374 | "CREATE FUNCTION [dbo].[GetPassedAirportType](@AirportType int) " + 375 | "RETURNS TABLE " + 376 | "RETURN " + 377 | "SELECT @AirportType AS [Type]"); 378 | 379 | context.Database.ExecuteSqlCommand( 380 | "CREATE FUNCTION [dbo].[GetPassedAirportTypeEnum](@AirportType int) " + 381 | "RETURNS TABLE " + 382 | "RETURN " + 383 | "SELECT @AirportType AS [Type] "); 384 | 385 | context.Database.ExecuteSqlCommand( 386 | "CREATE FUNCTION [dbo].[GetAircraft]() " + 387 | "RETURNS TABLE " + 388 | "RETURN " + 389 | "SELECT " + 390 | " [Id], " + 391 | " [Code], " + 392 | " [ProductionDate]" + 393 | "FROM [dbo].[Vehicles] " + 394 | "WHERE [Discriminator] = N'Aircraft'"); 395 | 396 | context.Database.ExecuteSqlCommand( 397 | "CREATE PROCEDURE [dbo].[GetAirports_ComplexTypeSP] @CountryCode nchar(3) AS " + 398 | "SELECT [IATACode], " + 399 | " [CityCode], " + 400 | " [CountryCode], " + 401 | " [Name], " + 402 | " [TerminalCount], " + 403 | " [Type] " + 404 | "FROM [dbo].[Airports] " + 405 | "WHERE [CountryCode] = @CountryCode"); 406 | 407 | context.Database.ExecuteSqlCommand( 408 | "CREATE PROCEDURE [dbo].[GetAirportsSP] @CountryCode nchar(2) AS " + 409 | "SELECT [IATACode], " + 410 | " [CityCode], " + 411 | " [CountryCode], " + 412 | " [Name], " + 413 | " [TerminalCount], " + 414 | " [Type] " + 415 | "FROM [dbo].[Airports] " + 416 | "WHERE [CountryCode] = @CountryCode"); 417 | 418 | context.Database.ExecuteSqlCommand( 419 | "CREATE PROCEDURE [dbo].[GetUniqueTerminalCountSP] AS " + 420 | "SELECT DISTINCT [TerminalCount] " + 421 | "FROM [dbo].[Airports]"); 422 | 423 | context.Database.ExecuteSqlCommand( 424 | "CREATE PROCEDURE [dbo].[GetAirportTypeSP] @AirportType int AS " + 425 | "SELECT [Type] " + 426 | "FROM [dbo].[Airports] " + 427 | "WHERE [Type] = @AirportType"); 428 | 429 | context.Database.ExecuteSqlCommand( 430 | "CREATE PROCEDURE [dbo].[GetAircraftSP] AS " + 431 | "SELECT " + 432 | " [Id], " + 433 | " [Code], " + 434 | " [ProductionDate]" + 435 | "FROM [dbo].[Vehicles] " + 436 | "WHERE [Discriminator] = N'Aircraft'"); 437 | 438 | context.Database.ExecuteSqlCommand( 439 | "CREATE PROCEDURE [dbo].[MultipleResultSets] AS " + 440 | "SELECT [IATACode], " + 441 | " [CityCode], " + 442 | " [CountryCode], " + 443 | " [Name], " + 444 | " [TerminalCount], " + 445 | " [Type] " + 446 | "FROM [dbo].[Airports] " + 447 | "SELECT [IATACode], " + 448 | " [CityCode], " + 449 | " [CountryCode], " + 450 | " [Name], " + 451 | " [TerminalCount], " + 452 | " [Type] " + 453 | "FROM [dbo].[Airports] " + 454 | "SELECT " + 455 | " [Id], " + 456 | " [Code], " + 457 | " [ProductionDate]" + 458 | "FROM [dbo].[Vehicles] " + 459 | "WHERE [Discriminator] = N'Aircraft' " + 460 | "SELECT 42 AS [Answer]"); 461 | 462 | context.Database.ExecuteSqlCommand( 463 | "CREATE PROCEDURE [dbo].[GetAirportTypesWithOutputParameter] @AirportType int out AS " + 464 | "SELECT @AirportType = Max([Type]) " + 465 | "FROM [dbo].[Airports] " + 466 | "SELECT DISTINCT [Type] " + 467 | "FROM [dbo].[Airports] " + 468 | "WHERE [Type] = @AirportType"); 469 | 470 | context.Database.ExecuteSqlCommand( 471 | "CREATE FUNCTION [dbo].[MyCustomTVF]()" + 472 | "RETURNS TABLE " + 473 | "RETURN " + 474 | "SELECT 1 AS [Number]"); 475 | 476 | context.Database.ExecuteSqlCommand( 477 | "CREATE FUNCTION EchoNumber(@number int) RETURNS int AS " + 478 | "BEGIN " + 479 | " RETURN @number " + 480 | "END"); 481 | 482 | context.Database.ExecuteSqlCommand( 483 | "CREATE PROCEDURE [dbo].[GetXmlInfo] @Xml xml out AS " + 484 | "SELECT @Xml = '' " + 485 | "SELECT 1234 AS Number"); 486 | } 487 | } 488 | 489 | #endregion 490 | 491 | public class E2ETests 492 | { 493 | [Fact] 494 | public void Can_invoke_primitive_TVFs_in_a_query() 495 | { 496 | using (var ctx = new MyContext()) 497 | { 498 | var query = from x in ctx.GetUniqueTerminalCount() 499 | where x > 1 500 | select x; 501 | 502 | const string expectedSql = @"SELECT 503 | [Extent1].[TerminalCount] AS [TerminalCount] 504 | FROM [dbo].[GetUniqueTerminalCount]() AS [Extent1] 505 | WHERE [Extent1].[TerminalCount] > 1"; 506 | 507 | Assert.Equal(expectedSql, ((ObjectQuery)query).ToTraceString()); 508 | 509 | Assert.Equal(new byte[] {2,5}, query.ToArray()); 510 | } 511 | } 512 | 513 | [Fact] 514 | public void Can_invoke_TVF_returning_complex_type_in_a_query() 515 | { 516 | using (var ctx = new MyContext()) 517 | { 518 | var query = ctx.GetAirports_ComplexType("PL").Where(a => a.TerminalCount > 0); 519 | 520 | var sql = ((ObjectQuery)query).ToTraceString(); 521 | 522 | const string expectedSql = @"SELECT 523 | 1 AS [C1], 524 | [Extent1].[IATACode] AS [IATACode], 525 | [Extent1].[CityCode] AS [CityCode], 526 | [Extent1].[CountryCode] AS [CountryCode], 527 | [Extent1].[Name] AS [Name], 528 | [Extent1].[TerminalCount] AS [TerminalCount], 529 | [Extent1].[Type] AS [Type] 530 | FROM [dbo].[GetAirports_ComplexType](@CountryCode) AS [Extent1] 531 | WHERE [Extent1].[TerminalCount] > 0"; 532 | 533 | Assert.Equal(expectedSql, sql); 534 | 535 | var result = query.ToList(); 536 | 537 | Assert.Single(result); 538 | Assert.Equal("WRO", result[0].IATACode); 539 | Assert.Equal("WRO", result[0].CityCode); 540 | Assert.Equal("PL", result[0].CountryCode); 541 | Assert.Equal(2, result[0].TerminalCount); 542 | Assert.Equal("Wroclaw Copernicus Airport", result[0].Name); 543 | Assert.Equal(AirportType.International, result[0].Type); 544 | } 545 | } 546 | 547 | [Fact] 548 | public void Can_invoke_TVF_mapped_to_EntitySet_in_a_query() 549 | { 550 | using (var ctx = new MyContext()) 551 | { 552 | var query = ctx.GetAirports("GB").Where(a => a.TerminalCount > 0); 553 | 554 | var sql = ((ObjectQuery)query).ToTraceString(); 555 | 556 | const string expectedSql = @"SELECT 557 | [Extent1].[IATACode] AS [IATACode], 558 | [Extent1].[CityCode] AS [CityCode], 559 | [Extent1].[CountryCode] AS [CountryCode], 560 | [Extent1].[Name] AS [Name], 561 | [Extent1].[TerminalCount] AS [TerminalCount], 562 | [Extent1].[Type] AS [Type] 563 | FROM [dbo].[GetAirports](@CountryCode) AS [Extent1] 564 | WHERE [Extent1].[TerminalCount] > 0"; 565 | 566 | Assert.Equal(expectedSql, sql); 567 | 568 | var result = query.ToList(); 569 | 570 | Assert.Equal(2, result.Count()); 571 | Assert.Equal("LHR", result[0].IATACode); 572 | Assert.Equal("LTN", result[1].IATACode); 573 | } 574 | } 575 | 576 | [Fact] 577 | public void Can_invoke_TVF_mapped_to_enum_type_in_a_query() 578 | { 579 | using (var ctx = new MyContext()) 580 | { 581 | var query = ctx.GetAirportType(AirportType.International).Where(a => a != (AirportType)(-1)); 582 | 583 | var sql = ((ObjectQuery)query).ToTraceString(); 584 | 585 | const string expectedSql = @"SELECT 586 | [Extent1].[Type] AS [Type] 587 | FROM [dbo].[GetAirportType](@AirportType) AS [Extent1] 588 | WHERE NOT ((-1 = [Extent1].[Type]) AND ([Extent1].[Type] IS NOT NULL))"; 589 | 590 | Assert.Equal(expectedSql, sql); 591 | 592 | var result = query.ToList(); 593 | 594 | Assert.Equal(4, result.Count()); 595 | Assert.True(result.All(r => r == AirportType.International)); 596 | } 597 | } 598 | 599 | [Fact] 600 | public void Can_invoke_TVF_with_nullable_parameter_returning_null_primitive_values() 601 | { 602 | using (var ctx = new MyContext()) 603 | { 604 | var airports = ctx.GetPassedAirportType(null).Where(a => a == null).ToList(); 605 | 606 | Assert.Equal(new int?[] {null}, airports); 607 | } 608 | } 609 | 610 | [Fact] 611 | public void Can_invoke_TVF_with_nullable_parameter_enum_returning_null_primitive_values() 612 | { 613 | using (var ctx = new MyContext()) 614 | { 615 | var airports = ctx.GetPassedAirportTypeEnum(null).Where(a => a == null).ToList(); 616 | 617 | Assert.Equal(new AirportType?[] { null }, airports); 618 | } 619 | } 620 | 621 | [Fact] 622 | public void Can_invoke_TVF_returning_entities_of_non_base_type() 623 | { 624 | using (var ctx = new MyContext()) 625 | { 626 | var aircraft = ctx.GetAircraft().ToList(); 627 | 628 | Assert.Single(aircraft); 629 | Assert.Equal("AT7", aircraft[0].Code); 630 | } 631 | } 632 | 633 | [Fact] 634 | public void Can_invoke_stored_proc_mapped_to_primitive_types() 635 | { 636 | using (var ctx = new MyContext()) 637 | { 638 | var result = ctx.GetUniqueTerminalCountSProc().ToList(); 639 | Assert.Equal(new byte[] { 1, 2, 5 }, result.ToArray()); 640 | } 641 | } 642 | 643 | [Fact] 644 | public void Can_invoke_stored_proc_mapped_to_ComplexTypes() 645 | { 646 | using (var ctx = new MyContext()) 647 | { 648 | var result = ctx.GetAirports_ComplexTypeSP("PL").ToList(); 649 | 650 | Assert.Single(result); 651 | Assert.Equal("WRO", result[0].IATACode); 652 | } 653 | } 654 | 655 | [Fact] 656 | public void Can_invoke_stored_proc_mapped_to_EntitySet() 657 | { 658 | using (var ctx = new MyContext()) 659 | { 660 | var result = ctx.GetAirportsSP("PL").ToList(); 661 | 662 | Assert.Single(result); 663 | Assert.Equal("WRO", result[0].IATACode); 664 | } 665 | } 666 | 667 | [Fact] 668 | public void Can_invoke_stored_proc_mapped_to_enums_types() 669 | { 670 | using (var ctx = new MyContext()) 671 | { 672 | var result = ctx.GetAirportTypeSP(AirportType.International).ToList(); 673 | 674 | Assert.Equal(4, result.Count()); 675 | Assert.True(result.All(r => r == AirportType.International)); 676 | } 677 | } 678 | 679 | [Fact] 680 | public void Can_invoke_sproc_returning_entities_of_non_base_type() 681 | { 682 | using (var ctx = new MyContext()) 683 | { 684 | var aircraft = ctx.GetAircraftSP().ToList(); 685 | 686 | Assert.Single(aircraft); 687 | Assert.Equal("AT7", aircraft[0].Code); 688 | } 689 | } 690 | 691 | [Fact] 692 | public void Can_invoke_stored_proc_with_multiple_resultsets() 693 | { 694 | using (var ctx = new MyContext()) 695 | { 696 | var results = ctx.MultipleResultSets(); 697 | Assert.Equal(5, results.ToList().Count); 698 | 699 | var secondResultSet = results.GetNextResult(); 700 | Assert.Equal(5, secondResultSet.ToList().Count); 701 | 702 | var thirdResultSet = secondResultSet.GetNextResult(); 703 | Assert.Equal(new[] { "AT7" }, thirdResultSet.ToList().Select(r => r.Code)); 704 | 705 | var fourthResultSet = thirdResultSet.GetNextResult(); 706 | Assert.Equal(new[] { 42 }, fourthResultSet.ToList()); 707 | } 708 | } 709 | 710 | [Fact] 711 | public void Can_invoke_stored_proc_with_out_parameter() 712 | { 713 | using (var ctx = new MyContext()) 714 | { 715 | var airportTypeParameter = new ObjectParameter("AirportType", typeof (AirportType?)); 716 | var results = ctx.GetAirportTypesWithOutputParameter(airportTypeParameter); 717 | 718 | Assert.Single(results.ToList()); 719 | Assert.Equal(AirportType.International, airportTypeParameter.Value); 720 | } 721 | } 722 | 723 | [Fact] 724 | public void Method_name_and_the_TVF_name_dont_have_to_math() 725 | { 726 | using (var ctx = new MyContext()) 727 | { 728 | Assert.Equal(new[] { 1 }, ctx.FunctionWhoseNameIsDifferentThenTVFName().ToList()); 729 | } 730 | } 731 | 732 | [Fact] 733 | public void Can_invoke_scalar_function_returning_primitive_type_value() 734 | { 735 | const string expectedSql = 736 | @"SELECT 737 | [Extent1].[Discriminator] AS [Discriminator], 738 | [Extent1].[Id] AS [Id], 739 | [Extent1].[ProductionDate] AS [ProductionDate], 740 | [Extent1].[Code] AS [Code] 741 | FROM [dbo].[Vehicles] AS [Extent1] 742 | WHERE ([Extent1].[Discriminator] IN (N'Aircraft',N'Vehicle')) AND ([Extent1].[Id] = ([dbo].[EchoNumber]([Extent1].[Id])))"; 743 | 744 | using (var ctx = new MyContext()) 745 | { 746 | var q = ctx.Vehicles.Where(v => v.Id == MyContext.ScalarFuncEchoNumber(v.Id)); 747 | Assert.Equal(expectedSql, q.ToString()); 748 | Assert.Equal(1, q.Count()); 749 | } 750 | } 751 | 752 | [Fact] 753 | public void Can_set_custom_store_parameter_type() 754 | { 755 | using (var ctx = new MyContext()) 756 | { 757 | var xmlParameter = new ObjectParameter("Xml", typeof(string)) {Value = ""}; 758 | var q = ctx.GetXmlInfo(xmlParameter); 759 | Assert.Equal(1234, q.ToArray().FirstOrDefault()); 760 | Assert.Equal("", xmlParameter.Value); 761 | } 762 | } 763 | 764 | [Fact] 765 | public void Can_invoke_built_in_square() 766 | { 767 | const string expectedSql = 768 | @"SELECT 769 | [Extent1].[IATACode] AS [IATACode], 770 | [Extent1].[CityCode] AS [CityCode], 771 | [Extent1].[CountryCode] AS [CountryCode], 772 | [Extent1].[Name] AS [Name], 773 | [Extent1].[TerminalCount] AS [TerminalCount], 774 | [Extent1].[Type] AS [Type] 775 | FROM [dbo].[Airports] AS [Extent1] 776 | WHERE CAST( [Extent1].[TerminalCount] AS real) = (SQUARE( CAST( [Extent1].[TerminalCount] AS real)))"; 777 | 778 | using (var ctx = new MyContext()) 779 | { 780 | var q = ctx.Airports.Where(a => a.TerminalCount == MyContext.Square(a.TerminalCount)); 781 | Assert.Equal(expectedSql, q.ToString()); 782 | Assert.Equal(3, q.Count()); 783 | } 784 | } 785 | 786 | [Fact] 787 | public void Can_invoke_built_in_format() 788 | { 789 | const string expectedSql = 790 | @"SELECT 791 | [Extent1].[Discriminator] AS [Discriminator], 792 | [Extent1].[Id] AS [Id], 793 | [Extent1].[ProductionDate] AS [ProductionDate], 794 | [Extent1].[Code] AS [Code] 795 | FROM [dbo].[Vehicles] AS [Extent1] 796 | WHERE ([Extent1].[Discriminator] IN (N'Aircraft',N'Vehicle')) AND (N'1929. 12. 07.' = (FORMAT([Extent1].[ProductionDate], N'd', N'hu-hu')))"; 797 | 798 | using (var ctx = new MyContext()) 799 | { 800 | var q = ctx.Vehicles.Where(v => MyContext.Format(v.ProductionDate, "d", "hu-hu") == "1929. 12. 07."); 801 | Assert.Equal(expectedSql, q.ToString()); 802 | Assert.Equal(1, q.Count()); 803 | } 804 | } 805 | 806 | [Fact] 807 | public void Can_invoke_niladic_current_timestamp() 808 | { 809 | const string expectedSql = 810 | @"SELECT 811 | [Limit1].[C1] AS [C1], 812 | [Limit1].[C2] AS [C2] 813 | FROM ( SELECT TOP (1) 814 | 1 AS [C1], 815 | CURRENT_TIMESTAMP AS [C2] 816 | FROM [dbo].[Vehicles] AS [Extent1] 817 | WHERE [Extent1].[Discriminator] IN (N'Aircraft',N'Vehicle') 818 | ) AS [Limit1]"; 819 | 820 | using (var ctx = new MyContext()) 821 | { 822 | var q = ctx.Vehicles.Select(x => new { TimeStamp = MyContext.CurrentTimestamp() }).Take(1); 823 | Assert.Equal(expectedSql, q.ToString()); 824 | Assert.Equal(1, q.Count()); 825 | } 826 | } 827 | } 828 | } 829 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/FunctionDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System.Collections.Generic; 6 | using System.Data.Entity.Core.Metadata.Edm; 7 | using Xunit; 8 | 9 | public class FunctionDescriptorTests 10 | { 11 | [Fact] 12 | public void FunctionDescriptor_initialized_correctly() 13 | { 14 | var edmType = PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Boolean); 15 | var parameters = 16 | new[] 17 | { 18 | new ParameterDescriptor("p1", edmType, null, false), 19 | new ParameterDescriptor("p2", edmType, null, false), 20 | }; 21 | 22 | var functionDescriptor = 23 | new FunctionDescriptor("Func", parameters, new EdmType[] { edmType }, 24 | "result", "dbo", StoreFunctionKind.TableValuedFunction, isBuiltIn: true, isNiladic: true); 25 | 26 | Assert.Equal("Func", functionDescriptor.Name); 27 | Assert.Same(edmType, functionDescriptor.ReturnTypes[0]); 28 | Assert.Equal(parameters, functionDescriptor.Parameters); 29 | Assert.Equal("result", functionDescriptor.ResultColumnName); 30 | Assert.Equal("dbo", functionDescriptor.DatabaseSchema); 31 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 32 | Assert.True(functionDescriptor.IsBuiltIn.Value); 33 | Assert.True(functionDescriptor.IsNiladic.Value); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/FunctionDiscoveryTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using Moq; 6 | using Moq.Protected; 7 | using System; 8 | using System.Data.Entity; 9 | using System.Data.Entity.Core.Metadata.Edm; 10 | using System.Data.Entity.Core.Objects; 11 | using System.Data.Entity.Infrastructure; 12 | using System.Globalization; 13 | using System.Linq; 14 | using System.Reflection; 15 | using Xunit; 16 | 17 | public static class StaticFake 18 | { 19 | [DbFunction("ns", "ExtensionMethod")] 20 | public static IQueryable ExtensionMethod(this IQueryable q, string param) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | [DbFunction("ns", "StaticMethod")] 26 | public static IQueryable StaticMethod(string param) 27 | { 28 | throw new NotImplementedException(); 29 | } 30 | } 31 | 32 | public class FunctionDiscoveryTests 33 | { 34 | public class FindFunctionsTests 35 | { 36 | private class Fake 37 | { 38 | [DbFunction("ns", "PrimitiveFunctionImportComposable")] 39 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 40 | public IQueryable PrimitiveFunctionImportComposable(int p1, string p2) 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | [DbFunction("ns", "PrivateFunction")] 46 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 47 | private IQueryable PrivateFunction(int p1) 48 | { 49 | throw new NotImplementedException(); 50 | } 51 | 52 | [DbFunction("ns", "EnumFunctionImportComposable")] 53 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 54 | public IQueryable EnumFunctionImportComposable(TestEnumType p1, string p2) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | 59 | [DbFunction("ns", "PrimitiveFunctionImportWithNullablePrimitiveTypes")] 60 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 61 | public IQueryable PrimitiveFunctionImportWithNullablePrimitiveTypes(int? p1) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | [DbFunction("ns", "EnumFunctionImportWithNullableEnums")] 67 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 68 | public IQueryable EnumFunctionImportWithNullableEnums(TestEnumType? p1) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | [DbFunction("ns", "storeFuncName")] 74 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 75 | public IQueryable FuncWithDifferentNames() 76 | { 77 | throw new NotImplementedException(); 78 | } 79 | 80 | 81 | // should not be discovered - missing DbFunctionAttribute 82 | public IQueryable NotAFunctionImport(int p1, string p2) 83 | { 84 | throw new NotImplementedException(); 85 | } 86 | 87 | [DbFunction("ns", "f")] 88 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col")] 89 | public IQueryable InvalidParamFunc(object p1) 90 | { 91 | throw new NotImplementedException(); 92 | } 93 | 94 | [DbFunction("ns", "TVFWithResultTypes")] 95 | [DbFunctionDetails(DatabaseSchema = "abc", ResultColumnName = "col", ResultTypes = new Type[0])] 96 | public IQueryable TVFWithResultTypes() 97 | { 98 | throw new NotImplementedException(); 99 | } 100 | 101 | 102 | [DbFunction("ns", "FunctionImportReturningComplexTypesComposable")] 103 | public IQueryable FunctionImportReturningComplexTypesComposable() 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | 108 | public ObjectResult StoredProcToComplexTypes() 109 | { 110 | throw new NotImplementedException(); 111 | } 112 | 113 | public ObjectResult StoredProcWithObjectParamater( 114 | [ParameterType(typeof(TestEnumType?))] ObjectParameter param) 115 | { 116 | throw new NotImplementedException(); 117 | } 118 | 119 | public ObjectResult StoredProcWithObjectParamaterAndNonEdmType( 120 | [ParameterType(typeof(ushort))] ObjectParameter param) 121 | { 122 | throw new NotImplementedException(); 123 | } 124 | 125 | public ObjectResult StoredProcWithObjectParamaterAndNoParameterTypeAttribute(ObjectParameter param) 126 | { 127 | throw new NotImplementedException(); 128 | } 129 | 130 | public ObjectResult StoredProcWithRefParam(ref int param) 131 | { 132 | throw new NotImplementedException(); 133 | } 134 | 135 | public ObjectResult StoredProcWithOutParam(out int param) 136 | { 137 | throw new NotImplementedException(); 138 | } 139 | 140 | [DbFunctionDetails(ResultTypes = new Type[0])] 141 | public ObjectResult EmptyResultType() 142 | { 143 | throw new NotImplementedException(); 144 | } 145 | 146 | [DbFunctionDetails(ResultTypes = new[] {typeof (int)})] 147 | public ObjectResult StoredProcReturnTypeAndResultTypeMismatch() 148 | { 149 | throw new NotImplementedException(); 150 | } 151 | 152 | [DbFunction("CodeFirstDatabaseSchema", "UdfPrimitive")] 153 | public int ScalarUdfPrimitiveType(string param) 154 | { 155 | throw new NotImplementedException(); 156 | } 157 | 158 | [DbFunction("CodeFirstDatabaseSchema", "UdfEnum")] 159 | public static TestEnumType UdfReturningEnumType() 160 | { 161 | throw new NotImplementedException(); 162 | } 163 | 164 | [DbFunction("CodeFirstDatabaseSchema", "UdfComplexType")] 165 | public static TestComplexType UdfReturningComplexType() 166 | { 167 | throw new NotImplementedException(); 168 | } 169 | 170 | [DbFunction("CodeFirstDatabaseSchema", "UdfEntityType")] 171 | public static TestEntityType UdfReturningEntityType() 172 | { 173 | throw new NotImplementedException(); 174 | } 175 | 176 | [DbFunction("ns", "TestUdf")] 177 | public int UdfNotInCodeFirstDatabaseSchema(string param) 178 | { 179 | throw new NotImplementedException(); 180 | } 181 | 182 | [DbFunction("CodeFirstDatabaseSchema", "UdfNonPrimitiveParam")] 183 | public static int UdfWithNonPrimitiveParam(TestEnumType param) 184 | { 185 | throw new NotImplementedException(); 186 | } 187 | 188 | [DbFunction("CodeFirstDatabaseSchema", "BuiltInFunctionName")] 189 | [DbFunctionDetails(IsBuiltIn = true)] 190 | public static int BuiltInFunction(string param) 191 | { 192 | throw new NotImplementedException(); 193 | } 194 | 195 | public class TestEntityType 196 | { 197 | public int Id { get; set; } 198 | public string Name { get; set; } 199 | } 200 | 201 | public class TestComplexType 202 | { 203 | public int Prop1 { get; set; } 204 | public string Prop2 { get; set; } 205 | 206 | } 207 | 208 | public enum TestEnumType 209 | { 210 | } 211 | 212 | // TODO: 213 | // invalid return type (return type not in model) 214 | // parameters of invalid return type 215 | // missing schema 216 | // missing result column name for scalars 217 | // result column for non-scalars 218 | } 219 | 220 | [Fact] 221 | public void FindFunctions_creates_function_descriptors_returning_primitive_types() 222 | { 223 | var mockType = new Mock(); 224 | mockType 225 | .Setup(t => t.GetMethods(It.IsAny())) 226 | .Returns(typeof (Fake) 227 | .GetMethods() 228 | .Where(m => m.Name == "PrimitiveFunctionImportComposable" || m.Name == "NotAFunctionImport") 229 | .ToArray()); 230 | 231 | var functionDescriptor = 232 | new FunctionDiscovery(CreateModel(), mockType.Object) 233 | .FindFunctions().Single(); 234 | 235 | Assert.NotNull(functionDescriptor); 236 | Assert.Equal("PrimitiveFunctionImportComposable", functionDescriptor.Name); 237 | Assert.Equal(2, functionDescriptor.Parameters.Count()); 238 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 239 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 240 | } 241 | 242 | [Fact] 243 | public void FindFunctions_creates_function_private() 244 | { 245 | var mockType = new Mock(); 246 | mockType 247 | .Setup(t => t.GetMethods(It.IsAny())) 248 | .Returns(typeof(Fake) 249 | .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) 250 | .Where(m => m.Name == "PrivateFunction") 251 | .ToArray()); 252 | 253 | var functionDescriptor = 254 | new FunctionDiscovery(CreateModel(), mockType.Object) 255 | .FindFunctions().Single(); 256 | 257 | Assert.NotNull(functionDescriptor); 258 | Assert.Equal("PrivateFunction", functionDescriptor.Name); 259 | Assert.Single(functionDescriptor.Parameters); 260 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 261 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 262 | } 263 | 264 | [Fact] 265 | public void FindFunctions_creates_function_descriptors_returning_complex_types() 266 | { 267 | var model = CreateModel(); 268 | model.ConceptualModel.AddItem(CreateComplexType()); 269 | 270 | var mockType = new Mock(); 271 | mockType 272 | .Setup(t => t.GetMethods(It.IsAny())) 273 | .Returns(typeof (Fake) 274 | .GetMethods() 275 | .Where(m => m.Name == "FunctionImportReturningComplexTypesComposable") 276 | .ToArray()); 277 | 278 | var functionDescriptor = 279 | new FunctionDiscovery(model, mockType.Object) 280 | .FindFunctions().Single(); 281 | 282 | Assert.NotNull(functionDescriptor); 283 | Assert.Equal("FunctionImportReturningComplexTypesComposable", functionDescriptor.Name); 284 | Assert.Empty(functionDescriptor.Parameters); 285 | Assert.Equal("Model.TestComplexType", functionDescriptor.ReturnTypes[0].FullName); 286 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 287 | } 288 | 289 | [Fact] 290 | public void FindFunctions_creates_function_descriptors_taking_and_returning_nullable_primitive_types() 291 | { 292 | var model = CreateModel(); 293 | model.ConceptualModel.AddItem(CreateEnumType()); 294 | 295 | var mockType = new Mock(); 296 | mockType 297 | .Setup(t => t.GetMethods(It.IsAny())) 298 | .Returns(typeof (Fake) 299 | .GetMethods() 300 | .Where(m => m.Name == "PrimitiveFunctionImportWithNullablePrimitiveTypes") 301 | .ToArray()); 302 | 303 | var functionDescriptor = 304 | new FunctionDiscovery(model, mockType.Object) 305 | .FindFunctions().SingleOrDefault(); 306 | 307 | Assert.NotNull(functionDescriptor); 308 | Assert.Equal("PrimitiveFunctionImportWithNullablePrimitiveTypes", functionDescriptor.Name); 309 | Assert.Single(functionDescriptor.Parameters); 310 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 311 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 312 | } 313 | 314 | [Fact] 315 | public void FindFunctions_creates_function_descriptors_returning_enum_types() 316 | { 317 | var model = CreateModel(); 318 | model.ConceptualModel.AddItem(CreateEnumType()); 319 | 320 | var mockType = new Mock(); 321 | mockType 322 | .Setup(t => t.GetMethods(It.IsAny())) 323 | .Returns(typeof (Fake) 324 | .GetMethods() 325 | .Where(m => m.Name == "EnumFunctionImportComposable") 326 | .ToArray()); 327 | 328 | var functionDescriptor = 329 | new FunctionDiscovery(model, mockType.Object) 330 | .FindFunctions().Single(); 331 | 332 | Assert.NotNull(functionDescriptor); 333 | Assert.Equal("EnumFunctionImportComposable", functionDescriptor.Name); 334 | Assert.Equal(2, functionDescriptor.Parameters.Count()); 335 | Assert.Equal("Model.TestEnumType", functionDescriptor.ReturnTypes[0].FullName); 336 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 337 | } 338 | 339 | [Fact] 340 | public void FindFunctions_creates_function_descriptors_taking_and_returning_nullable_enums() 341 | { 342 | var model = CreateModel(); 343 | model.ConceptualModel.AddItem(CreateEnumType()); 344 | 345 | var mockType = new Mock(); 346 | mockType 347 | .Setup(t => t.GetMethods(It.IsAny())) 348 | .Returns(typeof (Fake) 349 | .GetMethods() 350 | .Where(m => m.Name == "EnumFunctionImportWithNullableEnums") 351 | .ToArray()); 352 | 353 | var functionDescriptor = 354 | new FunctionDiscovery(model, mockType.Object) 355 | .FindFunctions().SingleOrDefault(); 356 | 357 | Assert.NotNull(functionDescriptor); 358 | Assert.Equal("EnumFunctionImportWithNullableEnums", functionDescriptor.Name); 359 | Assert.Single(functionDescriptor.Parameters); 360 | Assert.Equal("Model.TestEnumType", functionDescriptor.ReturnTypes[0].FullName); 361 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 362 | } 363 | 364 | [Fact] 365 | public void FindFunctions_creates_function_descriptors_for_functions_returning_complex_types_non_composable() 366 | { 367 | var model = CreateModel(); 368 | model.ConceptualModel.AddItem(CreateComplexType()); 369 | 370 | var mockType = new Mock(); 371 | mockType 372 | .Setup(t => t.GetMethods(It.IsAny())) 373 | .Returns(typeof (Fake) 374 | .GetMethods() 375 | .Where(m => m.Name == "StoredProcToComplexTypes") 376 | .ToArray()); 377 | 378 | var functionDescriptor = 379 | new FunctionDiscovery(model, mockType.Object) 380 | .FindFunctions().SingleOrDefault(); 381 | 382 | Assert.NotNull(functionDescriptor); 383 | Assert.Equal("StoredProcToComplexTypes", functionDescriptor.Name); 384 | Assert.Empty(functionDescriptor.Parameters); 385 | Assert.Equal("Model.TestComplexType", functionDescriptor.ReturnTypes[0].FullName); 386 | Assert.Equal(StoreFunctionKind.StoredProcedure, functionDescriptor.StoreFunctionKind); 387 | } 388 | 389 | [Fact] 390 | public void FindFunctions_creates_function_descriptors_with_custom_names() 391 | { 392 | var mockType = new Mock(); 393 | mockType 394 | .Setup(t => t.GetMethods(It.IsAny())) 395 | .Returns(typeof (Fake) 396 | .GetMethods() 397 | .Where(m => m.Name == "FuncWithDifferentNames") 398 | .ToArray()); 399 | 400 | var functionDescriptor = 401 | new FunctionDiscovery(CreateModel(), mockType.Object) 402 | .FindFunctions().Single(); 403 | 404 | Assert.NotNull(functionDescriptor); 405 | Assert.Equal("storeFuncName", functionDescriptor.Name); 406 | } 407 | 408 | [Fact] 409 | public void FindFunctions_throws_for_function_descriptors_with_invalid_parameters() 410 | { 411 | var mockType = new Mock(); 412 | mockType 413 | .Setup(t => t.GetMethods(It.IsAny())) 414 | .Returns(new[] {typeof (Fake).GetMethod("InvalidParamFunc")}); 415 | 416 | var message = 417 | Assert.Throws( 418 | () => new FunctionDiscovery(CreateModel(), mockType.Object) 419 | .FindFunctions() 420 | .ToArray()).Message; 421 | 422 | Assert.Contains("System.Object", message); 423 | Assert.Contains("p1", message); 424 | Assert.Contains("InvalidParamFunc", message); 425 | } 426 | 427 | [Fact] 428 | public void FindFunctions_throws_for_TVFs_with_ResultTypes() 429 | { 430 | var mockType = new Mock(); 431 | mockType 432 | .Setup(t => t.GetMethods(It.IsAny())) 433 | .Returns(new[] {typeof (Fake).GetMethod("TVFWithResultTypes")}); 434 | 435 | var message = 436 | Assert.Throws( 437 | () => new FunctionDiscovery(CreateModel(), mockType.Object) 438 | .FindFunctions() 439 | .ToArray()).Message; 440 | 441 | Assert.Contains("DbFunctionDetailsAttribute.ResultTypes", message); 442 | Assert.Contains("TVFWithResultTypes", message); 443 | } 444 | 445 | [Fact] 446 | public void FindFunctions_ignores_empty_ResultTypes_for_non_composable() 447 | { 448 | var mockType = new Mock(); 449 | mockType 450 | .Setup(t => t.GetMethods(It.IsAny())) 451 | .Returns(new[] {typeof (Fake).GetMethod("EmptyResultType")}); 452 | 453 | var returnType = new FunctionDiscovery(CreateModel(), mockType.Object) 454 | .FindFunctions() 455 | .ToArray()[0].ReturnTypes[0]; 456 | 457 | Assert.Contains("Edm.Int32", returnType.FullName); 458 | } 459 | 460 | [Fact] 461 | public void FindFunctions_throws_if_return_type_and_resultTypes_out_of_sync() 462 | { 463 | var mockType = new Mock(); 464 | mockType 465 | .Setup(t => t.GetMethods(It.IsAny())) 466 | .Returns(new[] {typeof (Fake).GetMethod("StoredProcReturnTypeAndResultTypeMismatch")}); 467 | 468 | var message = 469 | Assert.Throws( 470 | () => new FunctionDiscovery(CreateModel(), mockType.Object) 471 | .FindFunctions() 472 | .ToArray()).Message; 473 | 474 | Assert.Contains("ObjectResult", message); 475 | Assert.Contains("'StoredProcReturnTypeAndResultTypeMismatch'", message); 476 | Assert.Contains("'System.Int32'", message); 477 | Assert.Contains("'System.Byte'", message); 478 | Assert.Contains("DbFunctionDetailsAttribute.ResultTypes", message); 479 | } 480 | 481 | [Fact] 482 | public void FindFunctions_creates_function_descriptors_for_extension_methods() 483 | { 484 | var mockType = new Mock(); 485 | mockType 486 | .Setup(t => t.GetMethods(It.IsAny())) 487 | .Returns(new[] {typeof (StaticFake).GetMethod("ExtensionMethod")}); 488 | 489 | mockType 490 | .Protected() 491 | .Setup("GetAttributeFlagsImpl") 492 | .Returns(TypeAttributes.Abstract | TypeAttributes.Sealed); 493 | 494 | var functionDescriptor = new FunctionDiscovery(CreateModel(), mockType.Object) 495 | .FindFunctions().SingleOrDefault(); 496 | 497 | Assert.NotNull(functionDescriptor); 498 | Assert.Equal("ExtensionMethod", functionDescriptor.Name); 499 | Assert.Single(functionDescriptor.Parameters); 500 | Assert.Equal("param", functionDescriptor.Parameters.First().Name); 501 | Assert.Equal("Edm.String", functionDescriptor.Parameters.First().EdmType.FullName); 502 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 503 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 504 | } 505 | 506 | [Fact] 507 | public void FindFunctions_creates_function_descriptors_for_static_methods() 508 | { 509 | var mockType = new Mock(); 510 | mockType 511 | .Setup(t => t.GetMethods(It.IsAny())) 512 | .Returns(new[] {typeof (StaticFake).GetMethod("StaticMethod")}); 513 | 514 | mockType 515 | .Protected() 516 | .Setup("GetAttributeFlagsImpl") 517 | .Returns(TypeAttributes.Abstract | TypeAttributes.Sealed); 518 | 519 | var functionDescriptor = new FunctionDiscovery(CreateModel(), mockType.Object) 520 | .FindFunctions().SingleOrDefault(); 521 | 522 | Assert.NotNull(functionDescriptor); 523 | Assert.Equal("StaticMethod", functionDescriptor.Name); 524 | Assert.Single(functionDescriptor.Parameters); 525 | Assert.Equal("param", functionDescriptor.Parameters.First().Name); 526 | Assert.Equal("Edm.String", functionDescriptor.Parameters.First().EdmType.FullName); 527 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 528 | Assert.Equal(StoreFunctionKind.TableValuedFunction, functionDescriptor.StoreFunctionKind); 529 | } 530 | 531 | [Fact] 532 | public void FindFunctions_creates_function_descriptors_for_primitive_udfs() 533 | { 534 | var mockType = new Mock(); 535 | mockType 536 | .Setup(t => t.GetMethods(It.IsAny())) 537 | .Returns(new[] { typeof(Fake).GetMethod("ScalarUdfPrimitiveType") }); 538 | 539 | var functionDescriptor = 540 | new FunctionDiscovery(CreateModel(), mockType.Object) 541 | .FindFunctions().SingleOrDefault(); 542 | 543 | Assert.NotNull(functionDescriptor); 544 | Assert.Single(functionDescriptor.Parameters); 545 | Assert.Equal("param", functionDescriptor.Parameters.First().Name); 546 | Assert.Equal("Edm.String", functionDescriptor.Parameters.First().EdmType.FullName); 547 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 548 | Assert.Equal(StoreFunctionKind.ScalarUserDefinedFunction, functionDescriptor.StoreFunctionKind); 549 | } 550 | 551 | [Fact] 552 | public void FindFunctions_throws_for_udfs_returning_enum_types() 553 | { 554 | var model = CreateModel(); 555 | model.ConceptualModel.AddItem(CreateEnumType()); 556 | 557 | var mockType = new Mock(); 558 | mockType 559 | .Setup(t => t.GetMethods(It.IsAny())) 560 | .Returns(new[] { typeof(Fake).GetMethod("UdfReturningEnumType") }); 561 | 562 | var message = 563 | Assert.Throws(() => 564 | new FunctionDiscovery(model, mockType.Object) 565 | .FindFunctions().ToArray()).Message; 566 | 567 | Assert.Contains("TestEnumType", message); 568 | Assert.Contains("UdfReturningEnumType", message); 569 | } 570 | 571 | [Fact] 572 | public void FindFunctions_throws_for_udfs_returning_complex_types() 573 | { 574 | var model = CreateModel(); 575 | model.ConceptualModel.AddItem(CreateComplexType()); 576 | 577 | var mockType = new Mock(); 578 | mockType 579 | .Setup(t => t.GetMethods(It.IsAny())) 580 | .Returns(new[] { typeof(Fake).GetMethod("UdfReturningComplexType") }); 581 | 582 | var message = 583 | Assert.Throws(() => 584 | new FunctionDiscovery(model, mockType.Object) 585 | .FindFunctions().ToArray()).Message; 586 | 587 | Assert.Contains("TestComplexType", message); 588 | Assert.Contains("UdfReturningComplexType", message); 589 | } 590 | 591 | [Fact] 592 | public void FindFunctions_throws_for_udfs_returning_entity_types() 593 | { 594 | var model = CreateModel(); 595 | model.ConceptualModel.AddItem(CreateEntityType()); 596 | 597 | var mockType = new Mock(); 598 | mockType 599 | .Setup(t => t.GetMethods(It.IsAny())) 600 | .Returns(new[] { typeof(Fake).GetMethod("UdfReturningEntityType") }); 601 | 602 | var message = 603 | Assert.Throws(() => 604 | new FunctionDiscovery(model, mockType.Object) 605 | .FindFunctions().ToList()).Message; 606 | 607 | Assert.Contains("TestEntityType", message); 608 | Assert.Contains("UdfReturningEntityType", message); 609 | } 610 | 611 | [Fact] 612 | public void FindFunctions_throws_if_udf_not_in_CodeFirstDatabaseSchema_namespace() 613 | { 614 | var mockType = new Mock(); 615 | mockType 616 | .Setup(t => t.GetMethods(It.IsAny())) 617 | .Returns(new[] { typeof(Fake).GetMethod("UdfNotInCodeFirstDatabaseSchema") }); 618 | 619 | var message = 620 | Assert.Throws(() => 621 | new FunctionDiscovery(CreateModel(), mockType.Object) 622 | .FindFunctions().ToList()).Message; 623 | 624 | Assert.Contains("'DbFunction'", message); 625 | Assert.Contains("'CodeFirstDatabaseSchema'", message); 626 | Assert.Contains("'UdfNotInCodeFirstDatabaseSchema'", message); 627 | } 628 | 629 | [Fact] 630 | public void FindFunctions_throws_for_udfs_with_non_primitive_params() 631 | { 632 | var model = CreateModel(); 633 | model.ConceptualModel.AddItem(CreateEnumType()); 634 | 635 | var mockType = new Mock(); 636 | mockType 637 | .Setup(t => t.GetMethods(It.IsAny())) 638 | .Returns(new[] { typeof(Fake).GetMethod("UdfWithNonPrimitiveParam") }); 639 | 640 | var message = 641 | Assert.Throws(() => 642 | new FunctionDiscovery(model, mockType.Object) 643 | .FindFunctions().ToList()).Message; 644 | 645 | Assert.Contains("'Model.TestEnumType'", message); 646 | Assert.Contains("'param'", message); 647 | Assert.Contains("'UdfWithNonPrimitiveParam'", message); 648 | } 649 | 650 | [Fact] 651 | public void FindFunctions_throws_for_out_params() 652 | { 653 | var model = CreateModel(); 654 | model.ConceptualModel.AddItem(CreateComplexType()); 655 | 656 | var mockType = new Mock(); 657 | mockType 658 | .Setup(t => t.GetMethods(It.IsAny())) 659 | .Returns(new[] { typeof(Fake).GetMethod("StoredProcWithOutParam") }); 660 | 661 | var message = 662 | Assert.Throws(() => 663 | new FunctionDiscovery(model, mockType.Object) 664 | .FindFunctions().ToList()).Message; 665 | 666 | Assert.Contains("out", message); 667 | Assert.Contains("ref", message); 668 | Assert.Contains("Input/Output", message); 669 | Assert.Contains("'param'", message); 670 | Assert.Contains("'ObjectParameter'", message); 671 | } 672 | 673 | [Fact] 674 | public void FindFunctions_throws_for_ref_params() 675 | { 676 | var model = CreateModel(); 677 | model.ConceptualModel.AddItem(CreateComplexType()); 678 | 679 | var mockType = new Mock(); 680 | mockType 681 | .Setup(t => t.GetMethods(It.IsAny())) 682 | .Returns(new[] { typeof(Fake).GetMethod("StoredProcWithRefParam") }); 683 | 684 | var message = 685 | Assert.Throws(() => 686 | new FunctionDiscovery(model, mockType.Object) 687 | .FindFunctions().ToList()).Message; 688 | 689 | Assert.Contains("out", message); 690 | Assert.Contains("ref", message); 691 | Assert.Contains("Input/Output", message); 692 | Assert.Contains("'param'", message); 693 | Assert.Contains("'ObjectParameter'", message); 694 | Assert.Contains("'StoredProcWithRefParam'", message); 695 | } 696 | 697 | [Fact] 698 | public void FindFunctions_creates_function_descriptors_for_functions_taking_ObjectParameter_non_composable() 699 | { 700 | var model = CreateModel(); 701 | model.ConceptualModel.AddItem(CreateComplexType()); 702 | model.ConceptualModel.AddItem(CreateEnumType()); 703 | 704 | var mockType = new Mock(); 705 | mockType 706 | .Setup(t => t.GetMethods(It.IsAny())) 707 | .Returns(typeof(Fake) 708 | .GetMethods() 709 | .Where(m => m.Name == "StoredProcWithObjectParamater") 710 | .ToArray()); 711 | 712 | var functionDescriptor = 713 | new FunctionDiscovery(model, mockType.Object) 714 | .FindFunctions().SingleOrDefault(); 715 | 716 | Assert.NotNull(functionDescriptor); 717 | Assert.Equal("StoredProcWithObjectParamater", functionDescriptor.Name); 718 | 719 | var param = functionDescriptor.Parameters.SingleOrDefault(); 720 | Assert.NotNull(param); 721 | Assert.Equal("param", param.Name); 722 | Assert.Equal("Model.TestEnumType", param.EdmType.FullName); 723 | Assert.True(param.IsOutParam); 724 | } 725 | 726 | [Fact] 727 | public void FindFunctions_throws_if_no_Edm_type_for_ParameterTypeAttribute_type() 728 | { 729 | var model = CreateModel(); 730 | model.ConceptualModel.AddItem(CreateComplexType()); 731 | 732 | var mockType = new Mock(); 733 | mockType 734 | .Setup(t => t.GetMethods(It.IsAny())) 735 | .Returns(new[] { typeof(Fake).GetMethod("StoredProcWithObjectParamaterAndNonEdmType") }); 736 | 737 | var message = 738 | Assert.Throws(() => 739 | new FunctionDiscovery(model, mockType.Object) 740 | .FindFunctions().ToList()).Message; 741 | 742 | Assert.Contains("EdmType", message); 743 | Assert.Contains("System.UInt16", message); 744 | } 745 | 746 | [Fact] 747 | public void FindFunctions_throws_if_no_ParameterTypeAttribute_for_ObjectParameter() 748 | { 749 | var model = CreateModel(); 750 | model.ConceptualModel.AddItem(CreateComplexType()); 751 | 752 | var mockType = new Mock(); 753 | mockType 754 | .Setup(t => t.GetMethods(It.IsAny())) 755 | .Returns(new[] { typeof(Fake).GetMethod("StoredProcWithObjectParamaterAndNoParameterTypeAttribute") }); 756 | 757 | var message = 758 | Assert.Throws(() => 759 | new FunctionDiscovery(model, mockType.Object) 760 | .FindFunctions().ToList()).Message; 761 | 762 | Assert.Contains("ParameterTypeAttribute", message); 763 | Assert.Contains("ObjectParameter", message); 764 | Assert.Contains("'param'", message); 765 | Assert.Contains("'StoredProcWithObjectParamaterAndNoParameterTypeAttribute'", message); 766 | } 767 | 768 | [Fact] 769 | public void FindFunctions_creates_function_descriptors_for_built_in_function() 770 | { 771 | var mockType = new Mock(); 772 | mockType 773 | .Setup(t => t.GetMethods(It.IsAny())) 774 | .Returns(new[] { typeof(Fake).GetMethod("BuiltInFunction") }); 775 | 776 | var functionDescriptor = 777 | new FunctionDiscovery(CreateModel(), mockType.Object) 778 | .FindFunctions().SingleOrDefault(); 779 | 780 | Assert.NotNull(functionDescriptor); 781 | Assert.Single(functionDescriptor.Parameters); 782 | Assert.Equal("param", functionDescriptor.Parameters.First().Name); 783 | Assert.Equal("Edm.String", functionDescriptor.Parameters.First().EdmType.FullName); 784 | Assert.Equal("Edm.Int32", functionDescriptor.ReturnTypes[0].FullName); 785 | Assert.True(functionDescriptor.IsBuiltIn); 786 | } 787 | 788 | private static DbModel CreateModel() 789 | { 790 | return 791 | new DbModelBuilder() 792 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 793 | } 794 | 795 | private static EnumType CreateEnumType() 796 | { 797 | var enumTypeCtor = 798 | typeof(EnumType).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance) 799 | .Single(c => c.GetParameters().Count() == 5); 800 | var enumType = (EnumType)enumTypeCtor.Invoke(BindingFlags.NonPublic | BindingFlags.Instance, null, 801 | new object[] 802 | { 803 | "TestEnumType", "Model", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32), false, 804 | DataSpace.CSpace 805 | }, 806 | CultureInfo.InvariantCulture); 807 | return enumType; 808 | } 809 | 810 | private static ComplexType CreateComplexType() 811 | { 812 | var prop1 = EdmProperty.Create( 813 | "Prop1", 814 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32))); 815 | var prop2 = EdmProperty.Create( 816 | "Prop2", 817 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))); 818 | 819 | return 820 | ComplexType.Create("TestComplexType", "Model", DataSpace.CSpace, new[] {prop1, prop2}, null); 821 | } 822 | 823 | private static EntityType CreateEntityType() 824 | { 825 | var idProperty = EdmProperty.Create( 826 | "Id", 827 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32))); 828 | var nameProperty = EdmProperty.Create( 829 | "Name", 830 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))); 831 | 832 | return 833 | EntityType.Create("TestEntityType", "Model", DataSpace.CSpace, new[] {"Id"}, 834 | new[] {idProperty, nameProperty}, null); 835 | } 836 | } 837 | } 838 | } 839 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/ParameterDescriptorTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System.Data.Entity.Core.Metadata.Edm; 6 | using Xunit; 7 | 8 | public class ParameterDescriptorTests 9 | { 10 | [Fact] 11 | public void ParameterDescriptor_initialized_correctly() 12 | { 13 | var edmType = PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary); 14 | 15 | var parameterDescriptor = new ParameterDescriptor("param", edmType, "abc", true); 16 | 17 | Assert.Equal("param", parameterDescriptor.Name); 18 | Assert.Same(edmType, parameterDescriptor.EdmType); 19 | Assert.Equal("abc", parameterDescriptor.StoreType); 20 | Assert.True(parameterDescriptor.IsOutParam); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/ParameterTypeAttributeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | using System; 4 | 5 | namespace CodeFirstStoreFunctions 6 | { 7 | using Xunit; 8 | 9 | public class ParameterTypeAttributeTests 10 | { 11 | [Fact] 12 | public void ParameterTypeAttribute_initialized_correctly() 13 | { 14 | Assert.Same(typeof(object), new ParameterTypeAttribute(typeof(object)).Type); 15 | } 16 | 17 | [Fact] 18 | public void Can_get_set_Type_property() 19 | { 20 | Assert.Same(typeof(object), new ParameterTypeAttribute { Type = typeof(object)}.Type); 21 | } 22 | 23 | [Fact] 24 | public void Can_get_set_StoreType_property() 25 | { 26 | Assert.Equal("abc", new ParameterTypeAttribute { StoreType = "abc" }.StoreType); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/CodeFirstStoreFunctionsTests/StoreFunctionBuilderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Pawel Kadluczka, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | namespace CodeFirstStoreFunctions 4 | { 5 | using System; 6 | using System.Data.Entity; 7 | using System.Data.Entity.Core.Metadata.Edm; 8 | using System.Data.Entity.Infrastructure; 9 | using System.Globalization; 10 | using System.Linq; 11 | using System.Reflection; 12 | using Xunit; 13 | 14 | public class StoreFunctionBuilderTests 15 | { 16 | [Fact] 17 | public void Crate_creates_store_function_for_primitive_function_import() 18 | { 19 | var model = new DbModelBuilder() 20 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 21 | 22 | var functionDescriptor = 23 | new FunctionDescriptor( 24 | "f", 25 | new[] { 26 | new ParameterDescriptor( 27 | "p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, false), 28 | }, 29 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 30 | "ResultCol", "dbo", StoreFunctionKind.TableValuedFunction, isBuiltIn: null, isNiladic: null); 31 | 32 | var storeFunction = new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor); 33 | 34 | Assert.Equal( 35 | BuiltInTypeKind.CollectionType, 36 | storeFunction.ReturnParameter.TypeUsage.EdmType.BuiltInTypeKind); 37 | 38 | var collectionItemType = 39 | (RowType)((CollectionType)storeFunction.ReturnParameter.TypeUsage.EdmType).TypeUsage.EdmType; 40 | 41 | Assert.Single(collectionItemType.Properties); 42 | Assert.Equal("ResultCol", collectionItemType.Properties[0].Name); 43 | Assert.Equal("bigint", collectionItemType.Properties[0].TypeUsage.EdmType.Name); 44 | 45 | Assert.Single(storeFunction.Parameters); 46 | Assert.Equal("p1", storeFunction.Parameters[0].Name); 47 | Assert.Equal("nvarchar(max)", storeFunction.Parameters[0].TypeName); 48 | Assert.Equal(ParameterMode.In, storeFunction.Parameters[0].Mode); 49 | Assert.True(storeFunction.IsComposableAttribute); 50 | } 51 | 52 | [Fact] 53 | public void Crate_creates_store_function_for_complex_type_function_import() 54 | { 55 | var model = new DbModelBuilder() 56 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 57 | 58 | var enumType = EnumType.Create("TestEnum", "TestNs", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32), false, new [] { EnumMember.Create("foo", 1, null) }, null); 59 | 60 | var complexType = ComplexType.Create("CT", "ns", DataSpace.CSpace, 61 | new[] 62 | { 63 | EdmProperty.Create("Street", 64 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))), 65 | EdmProperty.Create("ZipCode", 66 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32))), 67 | EdmProperty.Create("MyEnum", TypeUsage.CreateDefaultTypeUsage(enumType)) 68 | }, 69 | null); 70 | 71 | var functionDescriptor = 72 | new FunctionDescriptor( 73 | "f", 74 | new[] 75 | {new ParameterDescriptor("p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, false)}, 76 | new EdmType[] { complexType }, 77 | "ResultCol", 78 | "dbo", 79 | StoreFunctionKind.StoredProcedure, 80 | isBuiltIn: null, 81 | isNiladic: null); 82 | 83 | var storeFunction = new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor); 84 | 85 | Assert.Null(storeFunction.ReturnParameter); 86 | 87 | Assert.Single(storeFunction.Parameters); 88 | Assert.Equal("p1", storeFunction.Parameters[0].Name); 89 | Assert.Equal("nvarchar(max)", storeFunction.Parameters[0].TypeName); 90 | Assert.Equal(ParameterMode.In, storeFunction.Parameters[0].Mode); 91 | Assert.False(storeFunction.IsComposableAttribute); 92 | } 93 | 94 | [Fact] 95 | public void Crate_creates_store_function_for_complex_type_withEnum_in_TableValuedFunction() 96 | { 97 | var model = new DbModelBuilder() 98 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 99 | 100 | var enumType = EnumType.Create("TestEnum", "TestNs", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32), false, new EnumMember[] { EnumMember.Create("foo", 1, null) }, null); 101 | 102 | var complexType = ComplexType.Create("CT", "ns", DataSpace.CSpace, 103 | new[] 104 | { 105 | EdmProperty.Create("Street", 106 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))), 107 | EdmProperty.Create("ZipCode", 108 | TypeUsage.CreateDefaultTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32))), 109 | EdmProperty.Create("MyEnum", TypeUsage.CreateDefaultTypeUsage(enumType)) 110 | }, 111 | null); 112 | 113 | var functionDescriptor = 114 | new FunctionDescriptor( 115 | "f", 116 | new[] 117 | {new ParameterDescriptor("p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, false)}, 118 | new EdmType[] { complexType }, 119 | "ResultCol", 120 | "dbo", 121 | StoreFunctionKind.TableValuedFunction, 122 | isBuiltIn: null, 123 | isNiladic: null); 124 | 125 | var storeFunction = new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor); 126 | 127 | Assert.Equal( 128 | BuiltInTypeKind.CollectionType, 129 | storeFunction.ReturnParameter.TypeUsage.EdmType.BuiltInTypeKind); 130 | 131 | Assert.Single(storeFunction.Parameters); 132 | Assert.Equal("p1", storeFunction.Parameters[0].Name); 133 | Assert.Equal("nvarchar(max)", storeFunction.Parameters[0].TypeName); 134 | Assert.Equal(ParameterMode.In, storeFunction.Parameters[0].Mode); 135 | Assert.True(storeFunction.IsComposableAttribute); 136 | } 137 | 138 | [Fact] 139 | public void Crate_creates_store_function_for_enum_type_function_import() 140 | { 141 | var model = new DbModelBuilder() 142 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 143 | 144 | var enumTypeCtor = typeof(EnumType).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single(c => c.GetParameters().Count() == 5); 145 | var enumType = (EnumType)enumTypeCtor.Invoke(BindingFlags.NonPublic | BindingFlags.Instance, null, 146 | new object[] { "TestEnumType", "Model", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int32), false, DataSpace.CSpace }, 147 | CultureInfo.InvariantCulture); 148 | 149 | var functionDescriptor = 150 | new FunctionDescriptor( 151 | "f", 152 | new[] { new ParameterDescriptor("p1", enumType, null, false) }, 153 | new EdmType[] { enumType }, 154 | "ResultCol", 155 | "dbo", 156 | StoreFunctionKind.TableValuedFunction, 157 | isBuiltIn: null, 158 | isNiladic: null); 159 | 160 | var storeFunction = new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor); 161 | 162 | Assert.Equal( 163 | BuiltInTypeKind.CollectionType, 164 | storeFunction.ReturnParameter.TypeUsage.EdmType.BuiltInTypeKind); 165 | 166 | var collectionItemType = 167 | (RowType)((CollectionType)storeFunction.ReturnParameter.TypeUsage.EdmType).TypeUsage.EdmType; 168 | 169 | Assert.Single(collectionItemType.Properties); 170 | Assert.Equal("ResultCol", collectionItemType.Properties[0].Name); 171 | Assert.Equal("int", collectionItemType.Properties[0].TypeUsage.EdmType.Name); 172 | 173 | Assert.Single(storeFunction.Parameters); 174 | Assert.Equal("p1", storeFunction.Parameters[0].Name); 175 | Assert.Equal("int", storeFunction.Parameters[0].TypeName); 176 | Assert.Equal(ParameterMode.In, storeFunction.Parameters[0].Mode); 177 | Assert.True(storeFunction.IsComposableAttribute); 178 | } 179 | 180 | [Fact] 181 | public void Crate_can_create_out_params() 182 | { 183 | var model = new DbModelBuilder() 184 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 185 | 186 | var functionDescriptor = 187 | new FunctionDescriptor( 188 | "f", 189 | new[] { 190 | new ParameterDescriptor( 191 | "p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, true), 192 | }, 193 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 194 | "ResultCol", "dbo", StoreFunctionKind.StoredProcedure, isBuiltIn: null, isNiladic: null); 195 | 196 | var storeFunction = new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor); 197 | 198 | Assert.Single(storeFunction.Parameters); 199 | Assert.Equal("p1", storeFunction.Parameters[0].Name); 200 | Assert.Equal("nvarchar(max)", storeFunction.Parameters[0].TypeName); 201 | Assert.Equal(ParameterMode.InOut, storeFunction.Parameters[0].Mode); 202 | Assert.False(storeFunction.IsComposableAttribute); 203 | } 204 | 205 | [Fact] 206 | public void StoreFunctionBuilder_uses_default_namespace_if_no_entities() 207 | { 208 | var model = new DbModelBuilder() 209 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 210 | 211 | var functionDescriptor = 212 | new FunctionDescriptor( 213 | "f", 214 | new[] 215 | {new ParameterDescriptor("p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, false)}, 216 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 217 | "ResultCol", "dbo", StoreFunctionKind.TableValuedFunction, isBuiltIn: null, isNiladic: null); 218 | 219 | var storeFunction = new StoreFunctionBuilder(model, "docs").Create(functionDescriptor); 220 | 221 | Assert.Equal("CodeFirstDatabaseSchema", storeFunction.NamespaceName); 222 | } 223 | 224 | [Fact] 225 | public void Can_specify_store_type_for_parameters() 226 | { 227 | var model = new DbModelBuilder() 228 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 229 | 230 | var functionDescriptor = 231 | new FunctionDescriptor( 232 | "f", 233 | new[] { 234 | new ParameterDescriptor( 235 | "p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), "xml", true), 236 | }, 237 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 238 | "ResultCol", "dbo", StoreFunctionKind.StoredProcedure, isBuiltIn: null, isNiladic: null); 239 | 240 | var storeFunction = new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor); 241 | 242 | Assert.Single(storeFunction.Parameters); 243 | Assert.Equal("p1", storeFunction.Parameters[0].Name); 244 | Assert.Equal("xml", storeFunction.Parameters[0].TypeName); 245 | } 246 | 247 | [Fact] 248 | public void Exception_thrown_if_provided_store_type_invalid() 249 | { 250 | var model = new DbModelBuilder() 251 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 252 | 253 | var functionDescriptor = 254 | new FunctionDescriptor( 255 | "f", 256 | new[] { 257 | new ParameterDescriptor( 258 | "p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), "json", true), 259 | }, 260 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 261 | "ResultCol", "dbo", StoreFunctionKind.StoredProcedure, isBuiltIn: null, isNiladic: null); 262 | 263 | Assert.Contains("'json'", 264 | Assert.Throws(() => 265 | new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor)).Message); 266 | } 267 | 268 | [Fact] 269 | public void Builtin_attribute_set_correctly() 270 | { 271 | var model = new DbModelBuilder() 272 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 273 | 274 | foreach (var isBuiltIn in new bool?[] { null, true, false }) 275 | { 276 | var functionDescriptor = 277 | new FunctionDescriptor( 278 | "f", 279 | new[] { new ParameterDescriptor("p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, false) }, 280 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 281 | "ResultCol", "dbo", StoreFunctionKind.TableValuedFunction, isBuiltIn, isNiladic: null); 282 | 283 | Assert.Equal(new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor).BuiltInAttribute, 284 | isBuiltIn == true); 285 | } 286 | } 287 | 288 | [Fact] 289 | public void Niladic_attribute_set_correctly() 290 | { 291 | var model = new DbModelBuilder() 292 | .Build(new DbProviderInfo("System.Data.SqlClient", "2012")); 293 | 294 | foreach (var isNiladic in new bool?[] { null, true, false }) 295 | { 296 | var functionDescriptor = 297 | new FunctionDescriptor( 298 | "f", 299 | new[] { new ParameterDescriptor("p1", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), null, false) }, 300 | new EdmType[] { PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Int64) }, 301 | "ResultCol", "dbo", StoreFunctionKind.TableValuedFunction, isBuiltIn: null, isNiladic: isNiladic); 302 | 303 | Assert.Equal(new StoreFunctionBuilder(model, "docs", "ns").Create(functionDescriptor).NiladicFunctionAttribute, 304 | isNiladic == true); 305 | } 306 | } 307 | } 308 | } 309 | --------------------------------------------------------------------------------