diff --git a/samples/features/json/product-catalog/dotnet-rest-api/.gitignore b/samples/features/json/product-catalog/dotnet-rest-api/.gitignore new file mode 100644 index 00000000..8b5804c3 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/.gitignore @@ -0,0 +1,8 @@ +*.xproj.user +.vs/* +.vscode/* +bin/* +obj/* +*.sln +*.log +*.lock.json \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/Controllers/ProductController.cs b/samples/features/json/product-catalog/dotnet-rest-api/Controllers/ProductController.cs new file mode 100644 index 00000000..da373ffb --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/Controllers/ProductController.cs @@ -0,0 +1,92 @@ +using Belgrade.SqlClient; +using Microsoft.AspNetCore.Mvc; +using System.Data.SqlClient; +using System.IO; +using System.Threading.Tasks; + +// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 + +namespace ProductCatalog.Controllers +{ + [Route("api/[controller]")] + public class ProductController : Controller + { + IQueryPipe sqlQuery = null; + ICommand sqlCmd = null; + private readonly string EMPTY_PRODUCTS_ARRAY = "{\"data\":[]}"; + + public ProductController(IQueryPipe sqlQueryService, ICommand sqlCommandService) + { + this.sqlQuery = sqlQueryService; + this.sqlCmd = sqlCommandService; + } + + // GET api/Product + [HttpGet] + public async Task Get() + { + await sqlQuery.Stream( +@"select ProductID, Name, Color, Price, Quantity, JSON_VALUE(Data, '$.MadeIn') as MadeIn, JSON_QUERY(Tags) as Tags +from Product +FOR JSON PATH, ROOT('data')", Response.Body, EMPTY_PRODUCTS_ARRAY); + } + + // GET api/Product/5 + [HttpGet("{id}")] + public async Task Get(int id) + { + var cmd = new SqlCommand( +@"select ProductID, Name, Color, Price, Quantity +from Product +where ProductId = @id +FOR JSON PATH, WITHOUT_ARRAY_WRAPPER"); + + cmd.Parameters.AddWithValue("id", id); + await sqlQuery.Stream(cmd, Response.Body, "{}"); + } + + // POST api/Product + [HttpPost] + public async Task Post() + { + string product = new StreamReader(Request.Body).ReadToEnd(); + var cmd = new SqlCommand("InsertProductFromJson"); + cmd.CommandType = System.Data.CommandType.StoredProcedure; + cmd.Parameters.AddWithValue("ProductJson", product); + await sqlCmd.ExecuteNonQuery(cmd); + } + + // PATCH api/Product + [HttpPatch] + public async Task Patch(int id) + { + string product = new StreamReader(Request.Body).ReadToEnd(); + var cmd = new SqlCommand("UpsertProductFromJson"); + cmd.CommandType = System.Data.CommandType.StoredProcedure; + cmd.Parameters.AddWithValue("ProductId", id); + cmd.Parameters.AddWithValue("ProductJson", product); + await sqlCmd.ExecuteNonQuery(cmd); + } + + // PUT api/Product/5 + [HttpPut("{id}")] + public async Task Put(int id) + { + string product = new StreamReader(Request.Body).ReadToEnd(); + var cmd = new SqlCommand("UpdateProductFromJson"); + cmd.CommandType = System.Data.CommandType.StoredProcedure; + cmd.Parameters.AddWithValue("ProductID", id); + cmd.Parameters.AddWithValue("ProductJson", product); + await sqlCmd.ExecuteNonQuery(cmd); + } + + // DELETE api/Product/5 + [HttpDelete("{id}")] + public async Task Delete(int id) + { + var cmd = new SqlCommand(@"delete Product where ProductId = @id"); + cmd.Parameters.AddWithValue("id", id); + await sqlCmd.ExecuteNonQuery(cmd); + } + } +} \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/ProductCatalog.xproj b/samples/features/json/product-catalog/dotnet-rest-api/ProductCatalog.xproj new file mode 100644 index 00000000..30ed9f6d --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/ProductCatalog.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 7e230e5a-b0b6-4f56-9561-942fd1817b80 + product_catalog + .\obj + .\bin\ + v4.6 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/Program.cs b/samples/features/json/product-catalog/dotnet-rest-api/Program.cs new file mode 100644 index 00000000..ec5d2cc0 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/Program.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using System.IO; + +namespace ProductCatalog +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/Properties/launchSettings.json b/samples/features/json/product-catalog/dotnet-rest-api/Properties/launchSettings.json new file mode 100644 index 00000000..4766fc15 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8929/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/Product", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "ProductCatalog": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "http://localhost:5000/api/Product", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/README.md b/samples/features/json/product-catalog/dotnet-rest-api/README.md new file mode 100644 index 00000000..e88f8126 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/README.md @@ -0,0 +1,79 @@ +# ASP.NET Core Product Inventory REST Service that uses SQL/JSON functionalities + +This project contains an example implementation of ASP.NET Core REST Service that enables you to get or modify list of products in catalog. + +## Contents + +[About this sample](#about-this-sample)
+[Before you begin](#before-you-begin)
+[Run this sample](#run-this-sample)
+[Sample details](#sample-details)
+[Disclaimers](#disclaimers)
+ + + +## About this sample + +- **Applies to:** SQL Server 2016 (or higher), Azure SQL Database +- **Key features:** JSON functions in SQL Server 2016/Azure SQL Database +- **Programming Language:** C#, Transact-SQL +- **Authors:** Jovan Popovic + + + +## Before you begin + +To run this sample, you need the following prerequisites. + +**Software prerequisites:** + +1. SQL Server 2016 (or higher) or an Azure SQL Database +2. Visual Studio 2015 Update 3 (or higher) or Visual Studio Code Editor with the ASP.NET Core 1.0 (or higher) + +**Azure prerequisites:** + +1. Permission to create an Azure SQL Database + + + +## Run this sample + +1. Create a database on SQL Server 2016 or Azure SQL Database and set compatibility level to 130. + +2. From SQL Server Management Studio or Sql Server Data Tools connect to your SQL Server 2016 or Azure SQL database and execute [sql-scripts/setup.sql](sql-scripts/setup.sql) script that will create and populate Product table and create required stored procedures. + +3. From Visual Studio 2015, open the **ProductCatalog.xproj** file from the root directory. Restore packages using right-click menu on the project in Visual Studio and by choosing Restore Packages item. As an alternative, you may run **dotnet restore** from the command line (from the root folder of application). + +4. Locate Startup.cs file in the project, change connection string in ConfigureServices() method to reference your database (default value ProductCatalog database on local instance with integrated security), and build solution using Ctrl+Shift+B, right-click on project + Build, Build/Build Solution from menu, or **dotnet build** command from the command line (from the root folder of application). + +5. Run the sample app using F5 or Ctrl+F5 in Visual Studio 2015, or using **dotnet run** executed in the command prompt of the project root folder. + 1. Open /api/Product Url to get all products from database, + 2. Open /api/Product/18 Url to get the product with id, + 3. Send POST Http request to /api/Product Url with JSON like {"Name":"Blade","Color":"Magenta","Price":18.0000,"Quantity":45} in the body of request to create new product, + 4. Send PUT Http request with JSON like {"Name":"Blade","Color":"Magenta","Price":18.0000,"Quantity":45} in the body of request to update the product with specified id, + 5. Send DELETE Http request /api/Product/18 Url to delete the product with specified id(18), + + + +## Sample details + +This sample application shows how to create REST API that returns list of products, single product, or update products in table. +Server-side code is implemented using ASP.NET Core Web API. +SQL Server JSON functions are used to format product data that will be sent to front-end page. + + + +## Disclaimers +The code included in this sample is not intended demonstrate some general guidance and architectural patterns for web development. It contains minimal code required to create REST API, and it does not use some patterns such as Repository. Sample uses built-in ASP.NET Core Dependency Injection mechanism; however, this is not prerequisite. +You can easily modify this code to fit the architecture of your application. + + + +## Code of Conduct +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## License +These samples and templates are all licensed under the MIT license. See the license.txt file in the root. + +## Questions +Email questions to: [sqlserversamples@microsoft.com](mailto: sqlserversamples@microsoft.com). \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/Startup.cs b/samples/features/json/product-catalog/dotnet-rest-api/Startup.cs new file mode 100644 index 00000000..4bc37b02 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/Startup.cs @@ -0,0 +1,48 @@ +using Belgrade.SqlClient; +using Belgrade.SqlClient.SqlDb; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Data.SqlClient; + +namespace ProductCatalog +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + //const string ConnString = "Server=<>.database.windows.net;Database=<>;User Id=<>;Password=<>"; + const string ConnString = "Server=.;Database=ProductCatalog;Integrated Security=true"; + + services.AddTransient(_ => new QueryPipe(new SqlConnection(ConnString))); + services.AddTransient(_ => new Command(new SqlConnection(ConnString))); + + // Add framework services. + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + loggerFactory.AddDebug(); + + app.UseMvc(); + } + } +} diff --git a/samples/features/json/product-catalog/dotnet-rest-api/app.config b/samples/features/json/product-catalog/dotnet-rest-api/app.config new file mode 100644 index 00000000..49aadfaa --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/app.config @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/features/json/product-catalog/dotnet-rest-api/appsettings.json b/samples/features/json/product-catalog/dotnet-rest-api/appsettings.json new file mode 100644 index 00000000..fa8ce71a --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/features/json/product-catalog/dotnet-rest-api/project.json b/samples/features/json/product-catalog/dotnet-rest-api/project.json new file mode 100644 index 00000000..744bb1f9 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/project.json @@ -0,0 +1,53 @@ +{ + "dependencies": { + "Belgrade.Sql.Client": "0.3.0", + "Microsoft.AspNetCore.Mvc": "1.0.0", + "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", + "Microsoft.AspNetCore.StaticFiles": "1.0.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", + "Microsoft.Extensions.Configuration.Json": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", + "Microsoft.Extensions.Logging.Console": "1.0.0", + "Microsoft.Extensions.Logging.Debug": "1.0.0" + }, + + "tools": { + "Microsoft.AspNetCore.Server.IISIntegration.Tools": { + "version": "1.0.0-preview1-final", + "imports": "portable-net45+win8+dnxcore50" + } + }, + + "frameworks": { + "netcoreapp1.0": { + + "dependencies": { + "Microsoft.NETCore.App": { + "version": "1.0.0", + "type": "platform" + } + } + }, + "net46": {} + }, + + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + + "publishOptions": { + "include": [ + "wwwroot", + "Views", + "appsettings.json", + "web.config" + ] + }, + + "scripts": { + "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + } +} diff --git a/samples/features/json/product-catalog/dotnet-rest-api/sql-scripts/demo.sql b/samples/features/json/product-catalog/dotnet-rest-api/sql-scripts/demo.sql new file mode 100644 index 00000000..091b0579 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/sql-scripts/demo.sql @@ -0,0 +1,103 @@ +USE ProductCatalog +GO + +--Combine standard columns with JSON columns. +select Name,Color,Size,Price,Quantity,Data,Tags +from Product + +--Use JSON Data in queries +select ProductID, Name, Color, Size, Price, Quantity, Data, Tags, + JSON_VALUE(Data, '$.MadeIn') as MadeIn +from Product +where JSON_VALUE(Data, '$.Type') = 'part' + +--Use JSON data in any part of query +select JSON_VALUE(Data, '$.Type') as Type, Color, + AVG( cast(JSON_VALUE(Data, '$.ManufacturingCost') as float) ) as Cost +from Product +group by JSON_VALUE(Data, '$.Type'), Color +having JSON_VALUE(Data, '$.Type') is not null +order by JSON_VALUE(Data, '$.Type') + +--Update JSON Data +--Current values in row: +select Data,Tags +from Product +where ProductID = 16 + +--Update values in JSON columns: +update Product set + Data = JSON_MODIFY(Data, '$.ManufacturingCost', 10), + Tags = JSON_MODIFY(Tags, 'append $', 'new') +where ProductID = 16 + +--Verify changes. +select Data,Tags +from Product +where ProductID = 16 + +GO + +--OPENJSON Examples - requires compatibility level 130! +alter database ProductCatalog set compatibility_level = 130 +GO + +--Convert JSON object to row. +declare @product nvarchar(max) = N'{"Name":"BB Ball Bearing","Color":"Magenta","Size":"62","Price":28.9900,"Quantity":80}' + +SELECT * +FROM OPENJSON (@product) + WITH (Name nvarchar(50),Color nvarchar(15),Size nvarchar(5),Price money,Quantity int) + +GO + +--Convert JSON array to set of rows. +DECLARE @products NVARCHAR(MAX) = +N'[{"ProductID":15,"Name":"Adjustable Race","Color":"Magenta","Size":"62","Price":100.0000,"Quantity":75,"Data":{"Type":"Part","MadeIn":"China"}},{"ProductID":16,"Name":"Bearing Ball","Color":"Magenta","Size":"62","Price":15.9900,"Quantity":90,"Data":{"ManufacturingCost":11.672700,"Type":"Part","MadeIn":"China"},"Tags":["promo"]},{"ProductID":17,"Name":"BB Ball Bearing","Color":"Magenta","Size":"62","Price":28.9900,"Quantity":80,"Data":{"ManufacturingCost":21.162700,"Type":"Part","MadeIn":"China"}},{"ProductID":18,"Name":"Blade","Color":"Magenta","Size":"62","Price":18.0000,"Quantity":45,"Data":{},"Tags":["new"]},{"ProductID":19,"Name":"Sport-100 Helmet, Red","Color":"Red","Size":"72","Price":41.9900,"Quantity":38,"Data":{"ManufacturingCost":30.652700,"Type":"Еquipment","MadeIn":"China"},"Tags":["promo"]},{"ProductID":20,"Name":"Sport-100 Helmet, Black","Color":"Black","Size":"72","Price":31.4900,"Quantity":60,"Data":{"ManufacturingCost":22.987700,"Type":"Еquipment","MadeIn":"China"},"Tags":["new","promo"]},{"ProductID":21,"Name":"Mountain Bike Socks, M","Color":"White","Size":"M","Price":560.9900,"Quantity":30,"Data":{"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":22,"Name":"Mountain Bike Socks, L","Color":"White","Size":"L","Price":120.9900,"Quantity":20,"Data":{"ManufacturingCost":88.322700,"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":23,"Name":"Long-Sleeve Logo Jersey, XL","Color":"Multi","Size":"XL","Price":44.9900,"Quantity":60,"Data":{"ManufacturingCost":32.842700,"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":24,"Name":"Road-650 Black, 52","Color":"Black","Size":"52","Price":704.6900,"Quantity":70,"Data":{"Type":"Bike","MadeIn":"UK"}},{"ProductID":25,"Name":"Mountain-100 Silver, 38","Color":"Silver","Size":"38","Price":359.9900,"Quantity":45,"Data":{"ManufacturingCost":262.792700,"Type":"Bike","MadeIn":"UK"},"Tags":["promo"]},{"ProductID":26,"Name":"Road-250 Black, 48","Color":"Black","Size":"48","Price":299.0200,"Quantity":25,"Data":{"ManufacturingCost":218.284600,"Type":"Bike","MadeIn":"UK"},"Tags":["new","promo"]},{"ProductID":27,"Name":"ML Bottom Bracket","Price":101.2400,"Quantity":50,"Data":{"Type":"Part","MadeIn":"China"}},{"ProductID":28,"Name":"HL Bottom Bracket","Price":121.4900,"Quantity":65,"Data":{"ManufacturingCost":88.687700,"Type":"Part","MadeIn":"China"}}]' + +SELECT * +FROM OPENJSON (@products) + WITH ( Name nvarchar(50),Color nvarchar(15),Size nvarchar(5),Price money,Quantity int, + Type nvarchar(20) '$.Data.Type', + Data nvarchar(max) AS JSON) + +GO + +--Query and analyze JSON array. +DECLARE @products NVARCHAR(MAX) = +N'[{"ProductID":15,"Name":"Adjustable Race","Color":"Magenta","Size":"62","Price":100.0000,"Quantity":75,"Data":{"Type":"Part","MadeIn":"China"}},{"ProductID":16,"Name":"Bearing Ball","Color":"Magenta","Size":"62","Price":15.9900,"Quantity":90,"Data":{"ManufacturingCost":11.672700,"Type":"Part","MadeIn":"China"},"Tags":["promo"]},{"ProductID":17,"Name":"BB Ball Bearing","Color":"Magenta","Size":"62","Price":28.9900,"Quantity":80,"Data":{"ManufacturingCost":21.162700,"Type":"Part","MadeIn":"China"}},{"ProductID":18,"Name":"Blade","Color":"Magenta","Size":"62","Price":18.0000,"Quantity":45,"Data":{},"Tags":["new"]},{"ProductID":19,"Name":"Sport-100 Helmet, Red","Color":"Red","Size":"72","Price":41.9900,"Quantity":38,"Data":{"ManufacturingCost":30.652700,"Type":"Еquipment","MadeIn":"China"},"Tags":["promo"]},{"ProductID":20,"Name":"Sport-100 Helmet, Black","Color":"Black","Size":"72","Price":31.4900,"Quantity":60,"Data":{"ManufacturingCost":22.987700,"Type":"Еquipment","MadeIn":"China"},"Tags":["new","promo"]},{"ProductID":21,"Name":"Mountain Bike Socks, M","Color":"White","Size":"M","Price":560.9900,"Quantity":30,"Data":{"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":22,"Name":"Mountain Bike Socks, L","Color":"White","Size":"L","Price":120.9900,"Quantity":20,"Data":{"ManufacturingCost":88.322700,"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":23,"Name":"Long-Sleeve Logo Jersey, XL","Color":"Multi","Size":"XL","Price":44.9900,"Quantity":60,"Data":{"ManufacturingCost":32.842700,"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":24,"Name":"Road-650 Black, 52","Color":"Black","Size":"52","Price":704.6900,"Quantity":70,"Data":{"Type":"Bike","MadeIn":"UK"}},{"ProductID":25,"Name":"Mountain-100 Silver, 38","Color":"Silver","Size":"38","Price":359.9900,"Quantity":45,"Data":{"ManufacturingCost":262.792700,"Type":"Bike","MadeIn":"UK"},"Tags":["promo"]},{"ProductID":26,"Name":"Road-250 Black, 48","Color":"Black","Size":"48","Price":299.0200,"Quantity":25,"Data":{"ManufacturingCost":218.284600,"Type":"Bike","MadeIn":"UK"},"Tags":["new","promo"]},{"ProductID":27,"Name":"ML Bottom Bracket","Price":101.2400,"Quantity":50,"Data":{"Type":"Part","MadeIn":"China"}},{"ProductID":28,"Name":"HL Bottom Bracket","Price":121.4900,"Quantity":65,"Data":{"ManufacturingCost":88.687700,"Type":"Part","MadeIn":"China"}}]' + +SELECT ISNULL(Color, 'N/A') Color, AVG(Price) AvgPrice, MIN(Quantity) MinQuantity +FROM OPENJSON (@products) + WITH (Name nvarchar(50),Color nvarchar(15),Size nvarchar(5),Price money,Quantity int) +group by Color +having MIN(Quantity) > 10 +order by Color + +GO + +--Find rows that contain some value in JSON array. +select ProductID, Name, Color, Size, Price, Quantity, Tags +from Product + CROSS APPLY OPENJSON(Tags) +where value = 'promo' + +GO + +--Format query results as JSON (with error). +select Name,Color,Size,Price,Quantity,Data,Tags +from Product +FOR JSON PATH + +--Format query results as JSON. +select Name,Color,Size,Price,Quantity,JSON_QUERY(Data) as Data, JSON_QUERY(Tags) as Tags +from Product +FOR JSON PATH + +--Export single row as JSON. +select Name,Color,Size,Price,Quantity,JSON_QUERY(Data) as Data, JSON_QUERY(Tags) as Tags +from Product +where ProductID = 17 +FOR JSON PATH, WITHOUT_ARRAY_WRAPPER + +USE master \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/sql-scripts/setup.sql b/samples/features/json/product-catalog/dotnet-rest-api/sql-scripts/setup.sql new file mode 100644 index 00000000..67682b40 --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/sql-scripts/setup.sql @@ -0,0 +1,119 @@ +USE master +GO + +DROP DATABASE IF EXISTS ProductCatalog +GO + +CREATE DATABASE ProductCatalog +GO + +USE ProductCatalog +GO + +DROP TABLE IF EXISTS Product +GO + +CREATE TABLE Product ( + ProductID int IDENTITY PRIMARY KEY, + Name nvarchar(50) NOT NULL, + Color nvarchar(15) NULL, + Size nvarchar(5) NULL, + Price money NOT NULL, + Quantity int NULL, + Data nvarchar(4000), + Tags nvarchar(4000) +) +GO + +SET IDENTITY_INSERT Product ON +GO + +DECLARE @products NVARCHAR(MAX) = +N'[{"ProductID":15,"Name":"Adjustable Race","Color":"Magenta","Size":"62","Price":100.0000,"Quantity":75,"Data":{"Type":"Part","MadeIn":"China"}},{"ProductID":16,"Name":"Bearing Ball","Color":"Magenta","Size":"62","Price":15.9900,"Quantity":90,"Data":{"ManufacturingCost":11.672700,"Type":"Part","MadeIn":"China"},"Tags":["promo"]},{"ProductID":17,"Name":"BB Ball Bearing","Color":"Magenta","Size":"62","Price":28.9900,"Quantity":80,"Data":{"ManufacturingCost":21.162700,"Type":"Part","MadeIn":"China"}},{"ProductID":18,"Name":"Blade","Color":"Magenta","Size":"62","Price":18.0000,"Quantity":45,"Data":{},"Tags":["new"]},{"ProductID":19,"Name":"Sport-100 Helmet, Red","Color":"Red","Size":"72","Price":41.9900,"Quantity":38,"Data":{"ManufacturingCost":30.652700,"Type":"Еquipment","MadeIn":"China"},"Tags":["promo"]},{"ProductID":20,"Name":"Sport-100 Helmet, Black","Color":"Black","Size":"72","Price":31.4900,"Quantity":60,"Data":{"ManufacturingCost":22.987700,"Type":"Еquipment","MadeIn":"China"},"Tags":["new","promo"]},{"ProductID":21,"Name":"Mountain Bike Socks, M","Color":"White","Size":"M","Price":560.9900,"Quantity":30,"Data":{"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":22,"Name":"Mountain Bike Socks, L","Color":"White","Size":"L","Price":120.9900,"Quantity":20,"Data":{"ManufacturingCost":88.322700,"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":23,"Name":"Long-Sleeve Logo Jersey, XL","Color":"Multi","Size":"XL","Price":44.9900,"Quantity":60,"Data":{"ManufacturingCost":32.842700,"Type":"Clothes"},"Tags":["sales","promo"]},{"ProductID":24,"Name":"Road-650 Black, 52","Color":"Black","Size":"52","Price":704.6900,"Quantity":70,"Data":{"Type":"Bike","MadeIn":"UK"}},{"ProductID":25,"Name":"Mountain-100 Silver, 38","Color":"Silver","Size":"38","Price":359.9900,"Quantity":45,"Data":{"ManufacturingCost":262.792700,"Type":"Bike","MadeIn":"UK"},"Tags":["promo"]},{"ProductID":26,"Name":"Road-250 Black, 48","Color":"Black","Size":"48","Price":299.0200,"Quantity":25,"Data":{"ManufacturingCost":218.284600,"Type":"Bike","MadeIn":"UK"},"Tags":["new","promo"]},{"ProductID":27,"Name":"ML Bottom Bracket","Price":101.2400,"Quantity":50,"Data":{"Type":"Part","MadeIn":"China"}},{"ProductID":28,"Name":"HL Bottom Bracket","Price":121.4900,"Quantity":65,"Data":{"ManufacturingCost":88.687700,"Type":"Part","MadeIn":"China"}}]' +INSERT INTO Product (ProductID, Name, Color, Size, Price, Quantity, Data, Tags) +SELECT ProductID, Name, Color, Size, Price, Quantity, Data, Tags +FROM OPENJSON (@products) WITH( + ProductID int, + Name nvarchar(50), + Color nvarchar(15), + Size nvarchar(5), + Price money, + Quantity int, + Data nvarchar(MAX) AS JSON, + Tags nvarchar(MAX) AS JSON +) +GO + +SET IDENTITY_INSERT Product OFF +GO + +CREATE PROCEDURE dbo.InsertProductFromJson(@ProductJson NVARCHAR(MAX)) +AS BEGIN + + INSERT INTO dbo.Product(Name,Color,Size,Price,Quantity,Data,Tags) + OUTPUT INSERTED.ProductID + SELECT Name,Color,Size,Price,Quantity,Data,Tags + FROM OPENJSON(@ProductJson) + WITH ( Name nvarchar(100) N'strict $."Name"', + Color nvarchar(30), + Size nvarchar(10), + Price money N'strict $."Price"', + Quantity int, + Data nvarchar(max) AS JSON, + Tags nvarchar(max) AS JSON) as json +END +GO + +CREATE PROCEDURE dbo.UpdateProductFromJson(@ProductID int, @ProductJson NVARCHAR(MAX)) +AS BEGIN + + UPDATE dbo.Product SET + Name = json.Name, + Color = json.Color, + Size = json.Size, + Price = json.Price, + Quantity = json.Quantity, + Data = ISNULL(json.Data, dbo.Product.Data), + Tags = ISNULL(json.Tags,dbo.Product.Tags) + FROM OPENJSON(@ProductJson) + WITH ( Name nvarchar(100) N'strict $."Name"', + Color nvarchar(30), + Size nvarchar(10), + Price money N'strict $."Price"', + Quantity int, + Data nvarchar(max) AS JSON, + Tags nvarchar(max) AS JSON) as json + WHERE dbo.Product.ProductID = @ProductID + +END +GO + +CREATE PROCEDURE dbo.UpsertProductFromJson(@ProductID int, @ProductJson NVARCHAR(MAX)) +AS BEGIN + + MERGE INTO dbo.Product + USING ( SELECT Name,Color,Size,Price,Quantity,Data,Tags + FROM OPENJSON(@ProductJson) + WITH ( + Name nvarchar(100) N'strict $."Name"', + Color nvarchar(30), + Size nvarchar(10), + Price money N'strict $."Price"', + Quantity int, + Data nvarchar(max) AS JSON, + Tags nvarchar(max) AS JSON)) as json + ON (dbo.Product.ProductID = @ProductID) + WHEN MATCHED THEN + UPDATE SET + Name = json.Name, + Color = json.Color, + Size = json.Size, + Price = json.Price, + Quantity = json.Quantity, + Data = json.Data, + Tags = json.Tags + WHEN NOT MATCHED THEN + INSERT (Name,Color,Size,Price,Quantity,Data,Tags) + VALUES (json.Name,json.Color,json.Size,json.Price,json.Quantity,json.Data,json.Tags); +END +GO \ No newline at end of file diff --git a/samples/features/json/product-catalog/dotnet-rest-api/web.config b/samples/features/json/product-catalog/dotnet-rest-api/web.config new file mode 100644 index 00000000..dc0514fc --- /dev/null +++ b/samples/features/json/product-catalog/dotnet-rest-api/web.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/samples/features/json/readme.md b/samples/features/json/readme.md index 744d1313..2e0ef1e3 100644 --- a/samples/features/json/readme.md +++ b/samples/features/json/readme.md @@ -8,3 +8,6 @@ This project contains an example implementation of ASP.NET Core REST API with CR This project contains an example implementation of NodeJS REST API using Express4 framework and Tedious. REST API has basic CRUD operations on a simple Todo table. You can learn how to build REST API on the existing database schema using new JSON functionalities that are available in SQL Server 2016 (or higher) and Azure SQL Database. +[Products REST API - ASP.NET Core](product-catalog/dotnet-rest-api) + +This project contains an example implementation of ASP.NET Core REST API with CRUD operations on a Product table. You can learn how to build REST API on the existing database schema using new JSON functionalities that are available in SQL Server 2016 (or higher) and Azure SQL Database. \ No newline at end of file