{"id":11147,"date":"2021-05-27T08:01:06","date_gmt":"2021-05-27T15:01:06","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=11147"},"modified":"2025-06-13T19:40:16","modified_gmt":"2025-06-14T02:40:16","slug":"sql-nosql-comparison-aspnet-application","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/","title":{"rendered":"SQL and NoSQL comparison: ASP.NET application"},"content":{"rendered":"<div class=\"paragraph\">\n<p>This SQL and NoSQL comparison is the next step after converting your SQL Server database to Couchbase. In <a href=\"https:\/\/www.couchbase.com\/blog\/sql-to-nosql-automated-migration\/\">the previous post<\/a>, I copied AdventureWorks from SQL Server to Couchbase.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>In this post, I\u2019m going to show an ASP.NET Core application that uses SQL Server, and how that same application would use Couchbase. If you\u2019d like to follow along, you can check out the <a href=\"https:\/\/github.com\/mgroves\/SqlServerToCouchbase\">SqlServerToCouchbase project<\/a> on GitHub.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Unlike the previous post, I\u2019m making no attempt at an &#8220;automatic&#8221; conversion of an application. Instead, think of this more as a SQL and NoSQL comparison at the application level.<\/p>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_asp_net_sql_server_applications\">ASP.NET SQL Server Applications<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>I created a very simple ASP.NET Core REST API style application. I used Entity Framework, but if you\u2019re using Dapper, ADO.NET, NHibernate, etc, you should still be able to follow along.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Each endpoint returns JSON. I\u2019ve also added Swashbuckle to the project, so you can issue requests right from your browser via OpenAPI.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_asp_net_couchbase_server_application\">ASP.NET Couchbase Server Application<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>The Couchbase version of the application returns the same data, because it\u2019s using the same SQL Server AdventureWorks data.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>In the application, I\u2019m using the <a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/hello-world\/start-using-sdk.html\">Couchbase .NET SDK<\/a> and <a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/distributed-acid-transactions-from-the-sdk.html\">Couchbase Transactions<\/a> libraries. (You could use <a href=\"https:\/\/github.com\/couchbaselabs\/Linq2Couchbase\">Linq2Couchbase<\/a> as a type of Entity Framework replacement).<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Otherwise, the application is the same, providing a SQL and NoSQL comparison (and contrast). The endpoints are returning JSON, and Swashbuckle is installed.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>There\u2019s one controller in each sample. Let\u2019s go through each endpoint in the controller and perform a SQL and NoSQL comparison.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_sql_and_nosql_comparison_get_by_id\">SQL and NoSQL Comparison: Get by ID<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Let\u2019s start with the <code>GetPersonByIdAsync<\/code> endpoint. Given a person ID, this endpoint returns the Person data for the given ID.<\/p>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_sql_server\">SQL Server<\/h3>\n<div class=\"paragraph\">\n<p>Here\u2019s the SQL Server example using Entity Framework:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/person\/{personId}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonByIdAsync(int personId)\r\n{\r\n    var person = await _context.Persons\r\n        .SingleOrDefaultAsync(p =&gt; p.BusinessEntityID == personId);\r\n\r\n    return Ok(person);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>I also wrote another version of this method, called <code>GetPersonByIdRawAsync<\/code> which uses a &#8220;raw&#8221; SQL query. This query is very similar to the one that Entity Framework (above) ultimately generates, and it\u2019s similar to a Dapper approach.<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/personRaw\/{personId}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonByIdRawAsync(int personId)\r\n{\r\n    var person = await _context.Persons\r\n        .FromSqlRaw(@\"SELECT * FROM Person.Person WHERE BusinessEntityID = {0}\", personId)\r\n        .SingleOrDefaultAsync();\r\n\r\n    return Ok(person);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Note that either way, a SQL query is being executed.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>With N1QL, we could query the data in Couchbase in a very similar way. Here\u2019s the <code>GetPersonByIdRawAsync<\/code> in the Couchbase project:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/personRaw\/{personId}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonByIdRawAsync(int personId)\r\n{\r\n    var bucket = await _bucketProvider.GetBucketAsync();\r\n    var cluster = bucket.Cluster;\r\n    var personResult = await cluster.QueryAsync&lt;Person&gt;(@\"\r\n        SELECT p.* FROM AdventureWorks2016.Person.Person p WHERE p.BusinessEntityID = $personId\",\r\n        new QueryOptions().Parameter(\"personId\", personId));\r\n    return Ok(await personResult.Rows.SingleOrDefaultAsync());\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>(There\u2019s an extra step going from &#8220;bucket&#8221; to &#8220;cluster&#8221;. This could be skipped, but I use bucket elsewhere in the controller, so I left it in).<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>However, using a N1QL query involves some extra overhead (indexing, query parsing, etc). With Couchbase, if we already know the Person ID, we can skip a N1QL query and do a direct key\/value (K\/V) lookup.<\/p>\n<\/div>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_get_by_id_with_k_v\">Get by ID with K\/V<\/h3>\n<div class=\"paragraph\">\n<p>The key is already known; it\u2019s given as an argument. Instead of using SQL, let\u2019s do a key\/value lookup. I did this in an endpoint method called <code>GetPersonByIdAsync<\/code>:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/person\/{personId}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonByIdAsync(int personId)\r\n{\r\n    var bucket = await _bucketProvider.GetBucketAsync();\r\n    var scope = await bucket.ScopeAsync(\"Person\");\r\n    var coll = await scope.CollectionAsync(\"Person\");\r\n    var personDoc = await coll.GetAsync(personId.ToString());\r\n    return Ok(personDoc.ContentAs&lt;Person&gt;());\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Unlike SQL Server, Couchbase supports a variety of APIs to interact with data. In this case, the key\/value lookup will be pulling the Person document directly from memory. There\u2019s no need to parse a SQL query or use any indexing. Key\/value lookups in Couchbase will often be measured in microseconds.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>My advice: use the key\/value lookup whenever you can.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_get_an_expanded_entity_by_id\">Get an expanded entity by ID<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Data can be complex and span multiple tables (or multiple documents in the case of Couchbase). Depending on which tools you are using, you may have some functionality that can load related entities.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>For instance, with Entity Framework, you can use an <code>Include<\/code> to pull in related entities, as shown in this <code>GetPersonByIdExpandedAsync<\/code> example:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/personExpanded\/{personId}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonByIdExpandedAsync(int personId)\r\n{\r\n    var person = await _context.Persons\r\n        .Include(p =&gt; p.EmailAddresses)\r\n        .SingleOrDefaultAsync(p =&gt; p.BusinessEntityID == personId);\r\n\r\n    return Ok(person);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Behind the scenes, Entity Framework may generate a JOIN and\/or multiple SELECT queries to make this happen.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>This is where any O\/RM (not just Entity Framework) can be dangerous. Make sure to use a tool like SQL Profiler to see what queries are actually being run.<\/p>\n<\/div>\n<div class=\"admonitionblock note\">\n<table>\n<tbody>\n<tr>\n<td class=\"icon\">\n<div class=\"title\">Note<\/div>\n<\/td>\n<td class=\"content\">O\/RMs can help, but in a <a href=\"https:\/\/www.couchbase.com\/blog\/sql-to-nosql-automated-migration\/\">SQL to NoSQL<\/a> comparison, it\u2019s important to remember that impedance mismatch is much less of a problem in the NoSQL world.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<div class=\"paragraph\">\n<p>For the Couchbase sample, I\u2019m not using Entity Framework, but instead I can use the <a href=\"https:\/\/docs.couchbase.com\/server\/current\/n1ql\/n1ql-language-reference\/nest.html\">NEST syntax<\/a> that\u2019s part of the N1QL extensions on the SQL standard. Here\u2019s how the Couchbase version of <code>GetPersonByIdExpandedAsync<\/code> looks:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/personExpanded\/{personId}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonByIdExpandedAsync(int personId)\r\n{\r\n    var bucket = await _bucketProvider.GetBucketAsync();\r\n    var cluster = bucket.Cluster;\r\n    var personResult = await cluster.QueryAsync&lt;Person&gt;(@\"\r\n        SELECT p.*, EmailAddresses\r\n        FROM AdventureWorks2016.Person.Person p\r\n        NEST AdventureWorks2016.Person.EmailAddress EmailAddresses ON EmailAddresses.BusinessEntityID = p.BusinessEntityID\r\n        WHERE p.BusinessEntityID = $personId\",\r\n        new QueryOptions().Parameter(\"personId\", personId));\r\n    return Ok(await personResult.Rows.SingleOrDefaultAsync());\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>NEST is a kind of JOIN that puts the JOINed data into a nested JSON object. Instead of using an O\/RM to map the data, this data can be directly serialized into C# objects.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_paging_query\">Paging query<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Let\u2019s look at an example where we do NOT have a single key to look up a piece of data. Let\u2019s look at a method that returns a &#8220;page&#8221; of results (perhaps to populate a UI grid or list).<\/p>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_paging_in_sql_server\">Paging in SQL Server<\/h3>\n<div class=\"paragraph\">\n<p>Here\u2019s the SQL Server version of <code>GetPersonsPageAsync<\/code>:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpGet(\"\/persons\/page\/{pageNum}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonsPageAsync(int pageNum)\r\n{\r\n    var pageSize = 10;\r\n    var personPage = await _context.Persons\r\n        .OrderBy(p =&gt; p.LastName)\r\n        .Skip(pageNum * pageSize)\r\n        .Take(pageSize)\r\n        .Select(p =&gt; new { p.BusinessEntityID, p.FirstName, p.LastName })\r\n        .ToListAsync();\r\n    return Ok(personPage);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>With Entity Framework, <code>OrderBy<\/code>, <code>Skip<\/code>, and <code>Take<\/code> are typically used for paging. If we open up SQL Server Profiler, the SQL this generates looks something like this:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-SQL\" data-lang=\"SQL\">exec sp_executesql N'SELECT [p].[BusinessEntityID], [p].[FirstName], [p].[LastName]\r\nFROM [Person].[Person] AS [p]\r\nORDER BY [p].[LastName]\r\nOFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY',N'@__p_0 int',@__p_0=10<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p><code>OFFSET \u2026\u200b ROWS FETCH NEXT \u2026\u200b<\/code> is the syntax being used for paging here.<\/p>\n<\/div>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_paging_in_couchbase\">Paging in Couchbase<\/h3>\n<div class=\"paragraph\">\n<p>Paging syntax always varies between SQL implementations. Couchbase leans more towards Oracle\/MySQL syntax in this regard. Here\u2019s the Couchbase version of <code>GetPersonsPageAsync<\/code>:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-SQL\" data-lang=\"SQL\">[HttpGet(\"\/persons\/page\/{pageNum}\")]\r\npublic async Task&lt;IActionResult&gt; GetPersonsPageAsync(int pageNum)\r\n{\r\n    var pageSize = 10;\r\n    var bucket = await _bucketProvider.GetBucketAsync();\r\n    var bucketName = bucket.Name;\r\n    var cluster = bucket.Cluster;\r\n\r\n    var personPage = await cluster.QueryAsync&lt;Person&gt;($@\"\r\n        SELECT p.LastName, p.BusinessEntityID, p.FirstName\r\n        FROM `{bucketName}`.Person.Person p\r\n        WHERE p.LastName IS NOT MISSING\r\n        ORDER BY p.LastName\r\n        LIMIT {pageSize} OFFSET {(pageNum * pageSize)}\r\n    \");\r\n\r\n    return Ok(await personPage.Rows.ToListAsync());\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>In this case, <code>LIMIT \u2026\u200b OFFSET \u2026\u200b<\/code> is being used.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>I also want to point out the <code>WHERE p.LastName IS NOT MISSING<\/code>. Because Couchbase is a NoSQL database, the query engine cannot assume that <code>LastName<\/code> will be in every document, even with <code>ORDER BY p.LastName<\/code>. By adding this <code>WHERE<\/code> clause, the query now knows which index to use. Without this, the query will take much longer to run.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_update_with_an_acid_transaction\">Update with an ACID transaction<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>With the relational-style model that we\u2019re using in both SQL Server and Couchbase for this example, ACID transactions will be important for both applications.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>In these examples, there is a <code>PersonUpdateApi<\/code> which will allow the user to update <strong>both<\/strong> a person\u2019s name and their email address. Since this data is in two separate tables\/rows (SQL Server) or two separate documents (Couchbase), we want this to be an all-or-nothing, atomic operation.<\/p>\n<\/div>\n<div class=\"admonitionblock note\">\n<table>\n<tbody>\n<tr>\n<td class=\"icon\">\n<div class=\"title\">Note<\/div>\n<\/td>\n<td class=\"content\">An ID is specified for both (to simplify the API), since it\u2019s possible (but rare in this dataset) for a person to have multiple email addresses.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_acid_with_entity_framework\">ACID with Entity Framework<\/h3>\n<div class=\"paragraph\">\n<p>Here\u2019s an example of an ACID transaction using Entity Framework to update both a row of data in the Person table and a row of data in the EmailAddress table.<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpPut(\"\/person\")]\r\npublic async Task&lt;IActionResult&gt; UpdatePurchaseOrderAsync(PersonUpdateApi personUpdateApi)\r\n{\r\n    var transaction = await _context.Database.BeginTransactionAsync();\r\n\r\n    try\r\n    {\r\n        \/\/ find the person\r\n        var person = await _context.Persons\r\n            .Include(p =&gt; p.EmailAddresses)\r\n            .SingleOrDefaultAsync(p =&gt; p.BusinessEntityID == personUpdateApi.PersonId);\r\n\r\n        \/\/ update name\r\n        person.FirstName = personUpdateApi.FirstName;\r\n        person.LastName = personUpdateApi.LastName;\r\n\r\n        \/\/ get the particular email address and update it\r\n        \/\/ if the supplied ID is invalid, this will throw an exception\r\n        var email = person.EmailAddresses.Single(e =&gt;\r\n            e.EmailAddressID == personUpdateApi.EmailAddressId);\r\n        email.EmailAddress = personUpdateApi.EmailAddress;\r\n\r\n        await _context.SaveChangesAsync();\r\n\r\n        \/\/ commit transaction\r\n        await transaction.CommitAsync();\r\n\r\n        return Ok($\"Person {personUpdateApi.PersonId} name and email updated.\");\r\n    }\r\n    catch (Exception ex)\r\n    {\r\n        await transaction.RollbackAsync();\r\n        return BadRequest(\"Something went wrong, transaction rolled back\");\r\n    }\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Note the four main parts of a transaction:<\/p>\n<\/div>\n<div class=\"olist arabic\">\n<ol class=\"arabic\">\n<li>Begin transaction (<code>_context.Database.BeginTransactionAsync();<\/code>)<\/li>\n<li><code>try<\/code>\/<code>catch<\/code><\/li>\n<li>Commit transaction (<code>await transaction.CommitAsync();<\/code>)<\/li>\n<li>Rollback transaction in the <code>catch<\/code> (<code>transaction.RollbackAsync();<\/code>)<\/li>\n<\/ol>\n<\/div>\n<div class=\"paragraph\">\n<p>This is an important feature where a SQL and NoSQL comparison has changed in recent years. With Couchbase, ACID transactions are now possible.<\/p>\n<\/div>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_acid_with_a_couchbase_transaction\">ACID with a Couchbase transaction<\/h3>\n<div class=\"paragraph\">\n<p>With Couchbase, the API is slightly different, but the same steps are all there:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">[HttpPut(\"\/person\")]\r\npublic async Task&lt;IActionResult&gt; UpdatePurchaseOrderAsync(PersonUpdateApi personUpdateApi)\r\n{\r\n    \/\/ setup bucket, cluster, and collections\r\n    var bucket = await _bucketProvider.GetBucketAsync();\r\n    var scope = await bucket.ScopeAsync(\"Person\");\r\n    var personColl = await scope.CollectionAsync(\"Person\");\r\n    var emailColl = await scope.CollectionAsync(\"EmailAddress\");\r\n\r\n    \/\/ create transaction\r\n    var cluster = bucket.Cluster;\r\n    var transaction = Transactions.Create(cluster,\r\n        TransactionConfigBuilder.Create()\r\n            .DurabilityLevel(DurabilityLevel.None)\r\n            .Build());\r\n\r\n    try\r\n    {\r\n        await transaction.RunAsync(async (context) =&gt;\r\n        {\r\n            \/\/ update person and email documents\r\n            \/\/ based on values passed in API object\r\n            var personKey = personUpdateApi.PersonId.ToString();\r\n            var emailKey = personKey + \"::\" + personUpdateApi.EmailAddressId.ToString();\r\n            var person = await context.GetAsync(personColl, personKey);\r\n            var email = await context.GetAsync(emailColl, emailKey);\r\n\r\n            var personDoc = person.ContentAs&lt;dynamic&gt;();\r\n            var emailDoc = email.ContentAs&lt;dynamic&gt;();\r\n\r\n            personDoc.FirstName = personUpdateApi.FirstName;\r\n            personDoc.LastName = personUpdateApi.LastName;\r\n            emailDoc.EmailAddress = personUpdateApi.EmailAddress;\r\n\r\n            await context.ReplaceAsync(person, personDoc);\r\n            await context.ReplaceAsync(email, emailDoc);\r\n        });\r\n        return Ok($\"Person {personUpdateApi.PersonId} name and email updated.\");\r\n    }\r\n    catch (Exception ex)\r\n    {\r\n        return BadRequest(\"Something went wrong, transaction rolled back.\");\r\n    }\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>The same main steps are:<\/p>\n<\/div>\n<div class=\"olist arabic\">\n<ol class=\"arabic\">\n<li>Begin transaction (<code>transaction.RunAsync( \u2026\u200b )<\/code>)<\/li>\n<li><code>try<\/code>\/<code>catch<\/code><\/li>\n<li>Commit transaction (implied, but <code>context.CommitAsync()<\/code> could be used)<\/li>\n<li>Rollback transaction (again, implied, but <code>context.RollbackAsync()<\/code> could be used).<\/li>\n<\/ol>\n<\/div>\n<div class=\"paragraph\">\n<p>In both cases, we have an ACID transaction. <strong>Unlike<\/strong> SQL Server, however, we can <strong>later<\/strong> optimize and consolidate the data in Couchbase to reduce the amount of ACID transactions that we need and increase performance.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_stored_procedures_a_sql_and_nosql_comparison\">Stored Procedures: a SQL and NoSQL comparison<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Stored procedures are a sometimes controversial topic. Generally speaking, they can contain a lot of functionality and logic.<\/p>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_stored_procedure_in_sql_server\">Stored Procedure in SQL Server<\/h3>\n<div class=\"paragraph\">\n<p>I\u2019ve created a stored procedure called &#8220;ListSubcomponents&#8221; (you can view the <a href=\"https:\/\/github.com\/mgroves\/SqlServerToCouchbase\">full details on GitHub<\/a>). With Entity Framework, you can use <code>FromSqlRaw<\/code> to execute it and map the results to C# objects. I\u2019ve created a pseudo-entity C# object called <code>ListSubcomponents<\/code> that\u2019s used just for this sproc:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">\/\/ sproc example - see ExampleStoredProcedure.sql\r\n[HttpGet(\"\/getListSubcomponents\/{listPriceMin}\/{listPriceMax}\")]\r\npublic async Task&lt;IActionResult&gt; GetListSubcomponents(decimal listPriceMin, decimal listPriceMax)\r\n{\r\n    var listPriceMinParam = new SqlParameter(\"@ListPriceMin\", SqlDbType.Decimal) {Value = listPriceMin };\r\n    var listPriceMaxParam = new SqlParameter(\"@ListPriceMax\", SqlDbType.Decimal) {Value = listPriceMax };\r\n\r\n    var result = await _context.ListSubcomponents\r\n        .FromSqlRaw(\"EXECUTE dbo.ListSubcomponents @ListPriceMin, @ListPriceMax\", listPriceMinParam, listPriceMaxParam)\r\n        .ToListAsync();\r\n\r\n    return Ok(result);\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>The stored procedure has two parameters.<\/p>\n<\/div>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_couchbase_user_defined_function\">Couchbase User-defined function<\/h3>\n<div class=\"paragraph\">\n<p>Couchbase doesn\u2019t have anything called a &#8220;stored procedure&#8221; (yet), but it does have something called a user-defined function (UDF) that can also contain complex logic when necessary.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>I created a UDF called <code>ListSubcomponents<\/code> (which you can also <a href=\"https:\/\/github.com\/mgroves\/SqlServerToCouchbase\">view on GitHub<\/a>) which matches the functionality of the SQL Server sproc.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>Here\u2019s how to execute that UDF from ASP.NET:<\/p>\n<\/div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight decode:true\"><code class=\"language-C#\" data-lang=\"C#\">\/\/ sproc example - see ExampleStoredProcedure.sql\r\n[HttpGet(\"\/getListSubcomponents\/{listPriceMin}\/{listPriceMax}\")]\r\npublic async Task&lt;IActionResult&gt; GetListSubcomponents(decimal listPriceMin, decimal listPriceMax)\r\n{\r\n    var bucket = await _bucketProvider.GetBucketAsync();\r\n    var cluster = bucket.Cluster;\r\n\r\n    var options = new QueryOptions();\r\n    options.Parameter(\"$listPriceMin\", listPriceMin);\r\n    options.Parameter(\"$listPriceMax\", listPriceMax);\r\n\r\n    var result = await cluster.QueryAsync&lt;ListSubcomponent&gt;(\r\n        \"SELECT l.* FROM ListSubcomponents($listPriceMin, $listPriceMax) l\", options);\r\n\r\n    return Ok(await result.Rows.ToListAsync());\r\n}<\/code><\/pre>\n<\/div>\n<\/div>\n<div class=\"paragraph\">\n<p>Invoking it in Couchbase with two parameters is very similar to using FromSqlRaw with Entity Framework.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_performance_sql_to_nosql_comparison\">Performance &#8211; SQL to NoSQL Comparison<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Now that I\u2019ve converted the app to use Couchbase, does the new version run at least as fast as the old SQL Server version?<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>It\u2019s a complicated question to answer because:<\/p>\n<\/div>\n<div class=\"ulist\">\n<ul>\n<li>I haven\u2019t made ANY optimizations to the data model. I\u2019m still using the literal conversion of data from <a href=\"https:\/\/www.couchbase.com\/blog\/sql-to-nosql-automated-migration\/\">the previous post<\/a>.<\/li>\n<li>Data access can vary wildly from use case to use case.<\/li>\n<li>Environments can vary wildly from person to person, company to company.<\/li>\n<\/ul>\n<\/div>\n<div class=\"paragraph\">\n<p>However, I wanted to do some &#8216;back of the envelope&#8217; load testing as a sanity check.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>I ran both applications on my local machine, and I used <a href=\"https:\/\/ngrok.com\">ngrok<\/a> to expose them to the Internet. I then used <a href=\"https:\/\/loader.io\/\">loader.io<\/a> (an excellent tool for load testing with concurrency). I then ran some quick performance tests against only the &#8216;paging&#8217; endpoint. This is the endpoint I\u2019m most concerned about for performance, and I also think it\u2019s the most &#8220;apples to apples&#8221; SQL and NoSQL comparison among the endpoints.<\/p>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_load_testing_sql_and_nosql_comparison\">Load testing SQL and NoSQL comparison<\/h3>\n<div class=\"paragraph\">\n<p>Here are the results of the SQL Server application:<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p><span class=\"image\"><img decoding=\"async\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2021\/05\/13301-sql-server-loader-performance.png\" alt=\"SQL and NoSQL comparison - SQL Server load testing\" \/><\/span><\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>And here are the results of the Couchbase Server application:<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p><span class=\"image\"><img decoding=\"async\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2021\/05\/13302-couchbase-server-loader-performance.png\" alt=\"SQL and NoSQL comparison - Couchbase Server load testing\" \/><\/span><\/p>\n<\/div>\n<\/div>\n<div class=\"sect2\">\n<h3 id=\"_interpreting_the_results_of_the_sql_and_nosql_comparison_load_test\">Interpreting the results of the SQL and NoSQL comparison load test<\/h3>\n<div class=\"paragraph\">\n<p>This is not meant to be a benchmark or data point saying &#8220;Couchbase is faster than SQL Server&#8221;.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>It is only meant to be a sanity check.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>If I\u2019m not getting at least as good performance under load as I was before, maybe I\u2019m doing something wrong. This is a crucial benefit to the <a href=\"https:\/\/www.couchbase.com\/blog\/proof-of-concept-move-relational\/\">proof of concept<\/a> process. Even though Couchbase, especially Couchbase 7, is very relational-friendly, there are still differences and nuances between <strong>every<\/strong> database, and this process will help you identify the differences that matter most to you and your project.<\/p>\n<\/div>\n<div class=\"paragraph\">\n<p>If you are looking for more robust benchmarks, here are some resources that you can check out:<\/p>\n<\/div>\n<div class=\"ulist\">\n<ul>\n<li><a href=\"https:\/\/www.couchbase.com\/benchmarks\/\">Benchmark Reports from Altoros<\/a> (3rd party)<\/li>\n<li><a href=\"https:\/\/resources.couchbase.com\/c\/altoros-report-eval-nosql-dbaas-ycsb?x=p2kCzD#zoom=100\" target=\"_blank\" rel=\"noopener\">Cloud Benchmarks<\/a><\/li>\n<li>Couchbase Server <a href=\"https:\/\/showfast.sc.couchbase.com\/#\/timeline\/Linux\/kv\/max_ops\/all\">&#8220;ShowFast&#8221; benchmarks<\/a><\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_conclusion\">Conclusion<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>The SQL and NoSQL comparison and conversion of the application code, combined with some very basic load testing shows me that I can:<\/p>\n<\/div>\n<div class=\"ulist\">\n<ul>\n<li>Host a relational data model as-is, no modeling changes<\/li>\n<li>Convert ASP.NET endpoint(s) to use the Couchbase SDK<\/li>\n<li>Expect at least as good performance to start, with plenty of room to scale and improve, at a low risk.<\/li>\n<\/ul>\n<\/div>\n<div class=\"paragraph\">\n<p>Your use case may vary, but also remember that during this conversion, Couchbase gave us:<\/p>\n<\/div>\n<div class=\"ulist\">\n<ul>\n<li>Easy <a href=\"https:\/\/docs.couchbase.com\/server\/7.0\/introduction\/why-couchbase.html#scale-out-archi\">horizontal scalability<\/a><\/li>\n<li><a href=\"https:\/\/docs.couchbase.com\/server\/7.0\/introduction\/why-couchbase.html#ha\">High availability<\/a><\/li>\n<li><a href=\"https:\/\/docs.couchbase.com\/server\/7.0\/introduction\/why-couchbase.html#mem-first-archi\">Built-in caching<\/a><\/li>\n<li><a href=\"https:\/\/docs.couchbase.com\/server\/7.0\/introduction\/why-couchbase.html#core-db-engine\">Schema flexibility<\/a> (which is probably why you\u2019re looking to use Couchbase in the first place).<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_appendix\">Appendix<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Here is a succinct guide to the SQL and NoSQL comparison that I made in the application.<\/p>\n<\/div>\n<table class=\"tableblock frame-all grid-all spread\">\n<colgroup>\n<col style=\"width: 50%\" \/>\n<col style=\"width: 50%\" \/> <\/colgroup>\n<thead>\n<tr>\n<th class=\"tableblock halign-left valign-top\">SQL Server operation<\/th>\n<th class=\"tableblock halign-left valign-top\">Couchbase operation<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">Read\/write one row\/entity<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\"><a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/kv-operations.html\">Key\/value lookup(s)<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">Read\/write multiple rows\/pages<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\"><a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/n1ql-queries-with-sdk.html\">N1QL query<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">SELECT one entity with related entities<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">N1QL query with <a href=\"https:\/\/docs.couchbase.com\/server\/current\/n1ql\/n1ql-language-reference\/nest.html\">NEST<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">BeginTransaction<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\"><a href=\"https:\/\/docs.couchbase.com\/dotnet-sdk\/current\/howtos\/distributed-acid-transactions-from-the-sdk.html\">Transaction.Create<\/a><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\">Stored Procedure<\/p>\n<\/td>\n<td class=\"tableblock halign-left valign-top\">\n<p class=\"tableblock\"><a href=\"https:\/\/docs.couchbase.com\/server\/current\/n1ql\/n1ql-language-reference\/userfun.html\">UDF<\/a> (<a href=\"https:\/\/docs.couchbase.com\/server\/7.0\/eventing\/eventing-overview.html\">Eventing<\/a> may also be useful here)<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"paragraph\">\n<p>Reminders:<\/p>\n<\/div>\n<div class=\"olist arabic\">\n<ol class=\"arabic\">\n<li>Switch to key\/value API when you can<\/li>\n<li>Use indexing, indexing plan visualization, and index advisor when writing N1QL<\/li>\n<li>Use an ACID transaction (only) when you need to<\/li>\n<li>Think about performance goals, and establish a way to test<\/li>\n<\/ol>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"sect1\">\n<h2 id=\"_next_steps\">Next steps<\/h2>\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>Check out <a href=\"https:\/\/www.couchbase.com\/downloads\/\">Couchbase Server 7, currently in beta<\/a>, today. It\u2019s a free download. Try loading your relational data into it, converting a few endpoints, and see if the process works for you.<\/p>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>This SQL and NoSQL comparison is the next step after converting your SQL Server database to Couchbase. In the previous post, I copied AdventureWorks from SQL Server to Couchbase. In this post, I\u2019m going to show an ASP.NET Core application [&hellip;]<\/p>\n","protected":false},"author":71,"featured_media":13873,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1811,10126,1816,1812,2396],"tags":[9499,1556],"ppma_author":[8937],"class_list":["post-11147","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-asp-dotnet","category-couchbase-server","category-n1ql-query","category-transactions","tag-acid-transactions","tag-sql-server"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.8 (Yoast SEO v25.8) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>SQL and NoSQL comparison: ASP.NET application - The Couchbase Blog<\/title>\n<meta name=\"description\" content=\"This SQL and NoSQL comparison shows the application code and queries for both SQL Server and Couchbase, including paging, SQL, load testing.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"SQL and NoSQL comparison: ASP.NET application\" \/>\n<meta property=\"og:description\" content=\"This SQL and NoSQL comparison shows the application code and queries for both SQL Server and Couchbase, including paging, SQL, load testing.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-05-27T15:01:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T02:40:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2021\/05\/13301-sql-server-loader-performance.png\" \/>\n<meta name=\"author\" content=\"Matthew Groves\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@mgroves\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Matthew Groves\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\"},\"author\":{\"name\":\"Matthew Groves\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58\"},\"headline\":\"SQL and NoSQL comparison: ASP.NET application\",\"datePublished\":\"2021-05-27T15:01:06+00:00\",\"dateModified\":\"2025-06-14T02:40:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\"},\"wordCount\":1899,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"keywords\":[\"ACID transactions\",\"SQL Server\"],\"articleSection\":[\".NET\",\"ASP.NET\",\"Couchbase Server\",\"SQL++ \/ N1QL Query\",\"Transactions\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\",\"name\":\"SQL and NoSQL comparison: ASP.NET application - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"datePublished\":\"2021-05-27T15:01:06+00:00\",\"dateModified\":\"2025-06-14T02:40:16+00:00\",\"description\":\"This SQL and NoSQL comparison shows the application code and queries for both SQL Server and Couchbase, including paging, SQL, load testing.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"width\":1800,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"SQL and NoSQL comparison: ASP.NET application\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58\",\"name\":\"Matthew Groves\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/ba51e6aacc53995c323a634e4502ef54\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g\",\"caption\":\"Matthew Groves\"},\"description\":\"Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.\",\"sameAs\":[\"https:\/\/crosscuttingconcerns.com\",\"https:\/\/x.com\/mgroves\"],\"url\":\"https:\/\/www.couchbase.com\/blog\/author\/matthew-groves\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"SQL and NoSQL comparison: ASP.NET application - The Couchbase Blog","description":"This SQL and NoSQL comparison shows the application code and queries for both SQL Server and Couchbase, including paging, SQL, load testing.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/","og_locale":"en_US","og_type":"article","og_title":"SQL and NoSQL comparison: ASP.NET application","og_description":"This SQL and NoSQL comparison shows the application code and queries for both SQL Server and Couchbase, including paging, SQL, load testing.","og_url":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/","og_site_name":"The Couchbase Blog","article_published_time":"2021-05-27T15:01:06+00:00","article_modified_time":"2025-06-14T02:40:16+00:00","og_image":[{"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2021\/05\/13301-sql-server-loader-performance.png","type":"","width":"","height":""}],"author":"Matthew Groves","twitter_card":"summary_large_image","twitter_creator":"@mgroves","twitter_misc":{"Written by":"Matthew Groves","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/"},"author":{"name":"Matthew Groves","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58"},"headline":"SQL and NoSQL comparison: ASP.NET application","datePublished":"2021-05-27T15:01:06+00:00","dateModified":"2025-06-14T02:40:16+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/"},"wordCount":1899,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","keywords":["ACID transactions","SQL Server"],"articleSection":[".NET","ASP.NET","Couchbase Server","SQL++ \/ N1QL Query","Transactions"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/","url":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/","name":"SQL and NoSQL comparison: ASP.NET application - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","datePublished":"2021-05-27T15:01:06+00:00","dateModified":"2025-06-14T02:40:16+00:00","description":"This SQL and NoSQL comparison shows the application code and queries for both SQL Server and Couchbase, including paging, SQL, load testing.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","width":1800,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/sql-nosql-comparison-aspnet-application\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"SQL and NoSQL comparison: ASP.NET application"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/3929663e372020321b0152dc4fa65a58","name":"Matthew Groves","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/ba51e6aacc53995c323a634e4502ef54","url":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","caption":"Matthew Groves"},"description":"Matthew D. Groves is a guy who loves to code. It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything. He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s. He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community. He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP.","sameAs":["https:\/\/crosscuttingconcerns.com","https:\/\/x.com\/mgroves"],"url":"https:\/\/www.couchbase.com\/blog\/author\/matthew-groves\/"}]}},"authors":[{"term_id":8937,"user_id":71,"is_guest":0,"slug":"matthew-groves","display_name":"Matthew Groves","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/70feb1b28a099ad0112b8d21fe1e81e1a4524beed3e20b7f107d5370e85a07ab?s=96&d=mm&r=g","author_category":"","last_name":"Groves","first_name":"Matthew","job_title":"","user_url":"https:\/\/crosscuttingconcerns.com","description":"Matthew D. Groves is a guy who loves to code.  It doesn't matter if it's C#, jQuery, or PHP: he'll submit pull requests for anything.  He has been coding professionally ever since he wrote a QuickBASIC point-of-sale app for his parent's pizza shop back in the 90s.  He currently works as a Senior Product Marketing Manager for Couchbase. His free time is spent with his family, watching the Reds, and getting involved in the developer community.  He is the author of AOP in .NET, Pro Microservices in .NET, a Pluralsight author, and a Microsoft MVP."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/11147","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/71"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=11147"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/11147\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/13873"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=11147"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=11147"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=11147"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=11147"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}