From 16e2aa723ab399b851bcdb29bd2372e78a7f9bc7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 7 Aug 2025 13:39:48 +0000
Subject: [PATCH 1/2] Initial plan
From 44e3cee5c23650ba01c4eaa1871d428a0edd14d3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 7 Aug 2025 13:58:44 +0000
Subject: [PATCH 2/2] Add Cosmos DB V5 advanced features: hierarchical
partitioning, computed properties, enhanced change feed
Co-authored-by: markjbrown <800166+markjbrown@users.noreply.github.com>
---
README.md | 35 ++-
data/database-v5/customer | 126 ++++++++++
data/database-v5/product | 128 ++++++++++
data/database-v5/salesOrder | 112 +++++++++
docs/V5-FEATURES.md | 171 +++++++++++++
infra/modules/cosmos.bicep | 84 +++++++
src/AdvancedChangeFeed.cs | 158 ++++++++++++
src/Dataload.cs | 1 +
src/Models.cs | 48 ++++
src/Program.cs | 462 ++++++++++++++++++++++++++++++++++++
10 files changed, 1320 insertions(+), 5 deletions(-)
create mode 100644 data/database-v5/customer
create mode 100644 data/database-v5/product
create mode 100644 data/database-v5/salesOrder
create mode 100644 docs/V5-FEATURES.md
create mode 100644 src/AdvancedChangeFeed.cs
diff --git a/README.md b/README.md
index d4e2aa5..a6b1d1c 100644
--- a/README.md
+++ b/README.md
@@ -125,9 +125,34 @@ The deployment will automatically:
## Source Data
-The sample data represents 4 versions of the Cosmos DB databases as they progress through the migration from a relational database to a highly scalable NoSQL database:
+The sample data represents 5 versions of the Cosmos DB databases as they progress through the migration from a relational database to a highly scalable NoSQL database leveraging the latest advanced features:
-* [Cosmic Works version 1](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v1)
-* [Cosmic Works version 2](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v2)
-* [Cosmic Works version 3](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v3)
-* [Cosmic Works version 4](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v4)
+* [Cosmic Works version 1](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v1) - Relational-style normalized data model
+* [Cosmic Works version 2](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v2) - Basic denormalization with optimized partition keys
+* [Cosmic Works version 3](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v3) - Advanced denormalization with embedded categories
+* [Cosmic Works version 4](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v4) - Complete denormalization with customers and orders co-located
+* [Cosmic Works version 5](https://github.com/AzureCosmosDB/CosmicWorks/tree/main/data/database-v5) - **NEW**: Advanced features including hierarchical partitioning, computed properties, and enhanced change feed
+
+## New in V5: Advanced Cosmos DB Features
+
+Version 5 demonstrates the latest Azure Cosmos DB capabilities for modern NoSQL applications:
+
+### š Featured Capabilities
+- **Hierarchical Partitioning**: Multi-level partition keys for better data distribution and query performance
+- **Global Secondary Indexes**: Alternative access patterns without data duplication
+- **Computed Properties**: Automatic calculation and indexing of derived fields
+- **All Versions and Deletes Change Feed**: Comprehensive operation tracking with before/after states
+
+### šÆ Interactive Demos
+- **[l]** Hierarchical Partitioning - Regional data distribution strategies
+- **[m]** Computed Properties - Calculated field indexing and querying
+- **[n]** Advanced Change Feed - Comprehensive change tracking with business rules
+- **[o]** Cross-region Queries - Performance comparisons across partition strategies
+
+### š Learning Resources
+- [V5 Features Documentation](./docs/V5-FEATURES.md) - Detailed guide to new capabilities
+- Real-world use case examples and migration guidance
+- Performance optimization recommendations
+- Best practices for advanced NoSQL data modeling
+
+Perfect for developers transitioning from relational databases to modern NoSQL architectures, or those looking to leverage the latest Cosmos DB innovations for high-performance, globally distributed applications.
diff --git a/data/database-v5/customer b/data/database-v5/customer
new file mode 100644
index 0000000..7faf6fb
--- /dev/null
+++ b/data/database-v5/customer
@@ -0,0 +1,126 @@
+[
+ {
+ "id": "CUSTOMER-001",
+ "type": "customer",
+ "customerId": "CUSTOMER-001",
+ "region": "North America",
+ "title": "Mr.",
+ "firstName": "John",
+ "lastName": "Smith",
+ "emailAddress": "john.smith@adventure-works.com",
+ "phoneNumber": "555-0101",
+ "creationDate": "2023-01-15T00:00:00Z",
+ "addresses": [
+ {
+ "addressLine1": "123 Main Street",
+ "addressLine2": "Apt 101",
+ "city": "Seattle",
+ "state": "WA",
+ "country": "USA",
+ "zipCode": "98101",
+ "location": {
+ "type": "Point",
+ "coordinates": [-122.3328, 47.6061]
+ }
+ }
+ ],
+ "password": {
+ "hash": "hash123",
+ "salt": "salt123"
+ },
+ "salesOrderCount": 5
+ },
+ {
+ "id": "CUSTOMER-002",
+ "type": "customer",
+ "customerId": "CUSTOMER-002",
+ "region": "Europe",
+ "title": "Ms.",
+ "firstName": "Sarah",
+ "lastName": "Johnson",
+ "emailAddress": "sarah.johnson@adventure-works.com",
+ "phoneNumber": "555-0102",
+ "creationDate": "2023-02-10T00:00:00Z",
+ "addresses": [
+ {
+ "addressLine1": "456 Oak Avenue",
+ "addressLine2": "",
+ "city": "London",
+ "state": "",
+ "country": "UK",
+ "zipCode": "SW1A 1AA",
+ "location": {
+ "type": "Point",
+ "coordinates": [-0.1276, 51.5074]
+ }
+ }
+ ],
+ "password": {
+ "hash": "hash456",
+ "salt": "salt456"
+ },
+ "salesOrderCount": 8
+ },
+ {
+ "id": "CUSTOMER-003",
+ "type": "customer",
+ "customerId": "CUSTOMER-003",
+ "region": "Asia Pacific",
+ "title": "Dr.",
+ "firstName": "Hiroshi",
+ "lastName": "Tanaka",
+ "emailAddress": "hiroshi.tanaka@adventure-works.com",
+ "phoneNumber": "555-0103",
+ "creationDate": "2023-03-05T00:00:00Z",
+ "addresses": [
+ {
+ "addressLine1": "789 Cherry Blossom Street",
+ "addressLine2": "Building 2, Floor 5",
+ "city": "Tokyo",
+ "state": "",
+ "country": "Japan",
+ "zipCode": "100-0001",
+ "location": {
+ "type": "Point",
+ "coordinates": [139.6917, 35.6895]
+ }
+ }
+ ],
+ "password": {
+ "hash": "hash789",
+ "salt": "salt789"
+ },
+ "salesOrderCount": 12
+ },
+ {
+ "id": "CUSTOMER-004",
+ "type": "customer",
+ "customerId": "CUSTOMER-004",
+ "region": "North America",
+ "title": "Mrs.",
+ "firstName": "Maria",
+ "lastName": "Garcia",
+ "emailAddress": "maria.garcia@adventure-works.com",
+ "phoneNumber": "555-0104",
+ "creationDate": "2023-04-20T00:00:00Z",
+ "addresses": [
+ {
+ "addressLine1": "321 Sunset Boulevard",
+ "addressLine2": "Suite 200",
+ "city": "Los Angeles",
+ "state": "CA",
+ "country": "USA",
+ "zipCode": "90028",
+ "location": {
+ "type": "Point",
+ "coordinates": [-118.2437, 34.0522]
+ }
+ }
+ ],
+ "password": {
+ "hash": "hash321",
+ "salt": "salt321"
+ },
+ "salesOrderCount": 3
+ }
+]
\ No newline at end of file
diff --git a/data/database-v5/product b/data/database-v5/product
new file mode 100644
index 0000000..135c7c2
--- /dev/null
+++ b/data/database-v5/product
@@ -0,0 +1,128 @@
+[
+ {
+ "id": "PRODUCT-001",
+ "categoryId": "BIKES",
+ "subCategoryId": "MOUNTAIN",
+ "categoryName": "Bikes",
+ "subCategoryName": "Mountain Bikes",
+ "sku": "MTB-001",
+ "name": "Mountain Explorer Pro",
+ "description": "Professional mountain bike for extreme terrains",
+ "price": 1299.99,
+ "tags": [
+ {
+ "id": "TAG-001",
+ "name": "Professional"
+ },
+ {
+ "id": "TAG-002",
+ "name": "Mountain"
+ }
+ ]
+ },
+ {
+ "id": "PRODUCT-002",
+ "categoryId": "BIKES",
+ "subCategoryId": "ROAD",
+ "categoryName": "Bikes",
+ "subCategoryName": "Road Bikes",
+ "sku": "RDB-001",
+ "name": "Speed Demon Carbon",
+ "description": "Ultra-lightweight carbon fiber road bike",
+ "price": 2499.99,
+ "tags": [
+ {
+ "id": "TAG-003",
+ "name": "Carbon"
+ },
+ {
+ "id": "TAG-004",
+ "name": "Lightweight"
+ }
+ ]
+ },
+ {
+ "id": "PRODUCT-003",
+ "categoryId": "ACCESSORIES",
+ "subCategoryId": "HELMETS",
+ "categoryName": "Accessories",
+ "subCategoryName": "Safety Helmets",
+ "sku": "HLM-001",
+ "name": "AeroSafe Pro Helmet",
+ "description": "Professional cycling helmet with advanced ventilation",
+ "price": 79.99,
+ "tags": [
+ {
+ "id": "TAG-005",
+ "name": "Safety"
+ },
+ {
+ "id": "TAG-006",
+ "name": "Ventilation"
+ }
+ ]
+ },
+ {
+ "id": "PRODUCT-004",
+ "categoryId": "ACCESSORIES",
+ "subCategoryId": "LIGHTS",
+ "categoryName": "Accessories",
+ "subCategoryName": "Bike Lights",
+ "sku": "LGT-001",
+ "name": "UltraBright LED Set",
+ "description": "High-intensity LED front and rear light set",
+ "price": 45.99,
+ "tags": [
+ {
+ "id": "TAG-007",
+ "name": "LED"
+ },
+ {
+ "id": "TAG-008",
+ "name": "Safety"
+ }
+ ]
+ },
+ {
+ "id": "PRODUCT-005",
+ "categoryId": "COMPONENTS",
+ "subCategoryId": "WHEELS",
+ "categoryName": "Components",
+ "subCategoryName": "Wheels & Tires",
+ "sku": "WHL-001",
+ "name": "Carbon Fiber Wheelset",
+ "description": "Lightweight carbon fiber wheelset for road bikes",
+ "price": 899.99,
+ "tags": [
+ {
+ "id": "TAG-009",
+ "name": "Carbon"
+ },
+ {
+ "id": "TAG-010",
+ "name": "Performance"
+ }
+ ]
+ },
+ {
+ "id": "PRODUCT-006",
+ "categoryId": "COMPONENTS",
+ "subCategoryId": "BRAKES",
+ "categoryName": "Components",
+ "subCategoryName": "Brake Systems",
+ "sku": "BRK-001",
+ "name": "Hydraulic Disc Brake Set",
+ "description": "Professional hydraulic disc brake system",
+ "price": 299.99,
+ "tags": [
+ {
+ "id": "TAG-011",
+ "name": "Hydraulic"
+ },
+ {
+ "id": "TAG-012",
+ "name": "Precision"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/data/database-v5/salesOrder b/data/database-v5/salesOrder
new file mode 100644
index 0000000..a2b02d9
--- /dev/null
+++ b/data/database-v5/salesOrder
@@ -0,0 +1,112 @@
+[
+ {
+ "id": "ORDER-001",
+ "type": "salesOrder",
+ "customerId": "CUSTOMER-001",
+ "region": "North America",
+ "orderDate": "2023-10-15T10:30:00Z",
+ "shipDate": "2023-10-16T09:00:00Z",
+ "details": [
+ {
+ "sku": "MTB-001",
+ "name": "Mountain Explorer Pro",
+ "price": 1299.99,
+ "quantity": 1
+ },
+ {
+ "sku": "HLM-001",
+ "name": "AeroSafe Pro Helmet",
+ "price": 79.99,
+ "quantity": 1
+ }
+ ]
+ },
+ {
+ "id": "ORDER-002",
+ "type": "salesOrder",
+ "customerId": "CUSTOMER-002",
+ "region": "Europe",
+ "orderDate": "2023-10-20T14:15:00Z",
+ "shipDate": "2023-10-21T11:30:00Z",
+ "details": [
+ {
+ "sku": "RDB-001",
+ "name": "Speed Demon Carbon",
+ "price": 2499.99,
+ "quantity": 1
+ },
+ {
+ "sku": "WHL-001",
+ "name": "Carbon Fiber Wheelset",
+ "price": 899.99,
+ "quantity": 1
+ }
+ ]
+ },
+ {
+ "id": "ORDER-003",
+ "type": "salesOrder",
+ "customerId": "CUSTOMER-003",
+ "region": "Asia Pacific",
+ "orderDate": "2023-11-01T08:45:00Z",
+ "shipDate": "2023-11-02T16:20:00Z",
+ "details": [
+ {
+ "sku": "LGT-001",
+ "name": "UltraBright LED Set",
+ "price": 45.99,
+ "quantity": 2
+ },
+ {
+ "sku": "BRK-001",
+ "name": "Hydraulic Disc Brake Set",
+ "price": 299.99,
+ "quantity": 1
+ }
+ ]
+ },
+ {
+ "id": "ORDER-004",
+ "type": "salesOrder",
+ "customerId": "CUSTOMER-001",
+ "region": "North America",
+ "orderDate": "2023-11-05T13:20:00Z",
+ "shipDate": "2023-11-06T10:15:00Z",
+ "details": [
+ {
+ "sku": "HLM-001",
+ "name": "AeroSafe Pro Helmet",
+ "price": 79.99,
+ "quantity": 2
+ }
+ ]
+ },
+ {
+ "id": "ORDER-005",
+ "type": "salesOrder",
+ "customerId": "CUSTOMER-004",
+ "region": "North America",
+ "orderDate": "2023-11-10T16:45:00Z",
+ "shipDate": "2023-11-11T12:30:00Z",
+ "details": [
+ {
+ "sku": "MTB-001",
+ "name": "Mountain Explorer Pro",
+ "price": 1299.99,
+ "quantity": 1
+ },
+ {
+ "sku": "LGT-001",
+ "name": "UltraBright LED Set",
+ "price": 45.99,
+ "quantity": 1
+ },
+ {
+ "sku": "BRK-001",
+ "name": "Hydraulic Disc Brake Set",
+ "price": 299.99,
+ "quantity": 1
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/docs/V5-FEATURES.md b/docs/V5-FEATURES.md
new file mode 100644
index 0000000..90a47e6
--- /dev/null
+++ b/docs/V5-FEATURES.md
@@ -0,0 +1,171 @@
+# CosmicWorks V5 - Advanced Cosmos DB Features
+
+This document describes the new features demonstrated in CosmicWorks database-v5, showcasing the latest Azure Cosmos DB capabilities for advanced data modeling.
+
+## New Features in V5
+
+### 1. Hierarchical Partitioning
+**Status**: Preview Feature
+**Documentation**: [Hierarchical Partition Keys](https://learn.microsoft.com/en-us/azure/cosmos-db/hierarchical-partition-keys)
+
+Hierarchical partitioning allows multiple levels of partition keys for more granular data distribution and efficient querying.
+
+**Benefits:**
+- Better query performance for common access patterns
+- More granular control over data distribution
+- Improved scalability for multi-tenant scenarios
+- Reduced hot partitions in complex data models
+
+**Demo Usage in V5:**
+- Customer container: Partitioned by region for geo-distributed queries
+- Sales orders: Co-located with customers by region for efficient joins
+- Enables both regional and cross-regional analytics
+
+**Real-world Examples:**
+```
+E-commerce: [/region, /customerId]
+IoT Systems: [/deviceType, /location, /deviceId]
+Multi-tenant SaaS: [/tenantId, /userId]
+Gaming: [/gameId, /playerId]
+```
+
+### 2. Global Secondary Indexes (GSI)
+**Status**: Preview Feature
+**Documentation**: [Global Secondary Indexes](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/global-secondary-indexes)
+
+GSI enables additional access patterns without duplicating data, providing alternative query paths.
+
+**Benefits:**
+- Query data using different partition keys
+- Improved query performance for varied access patterns
+- Reduced data duplication compared to manual indexing
+- Automatic index maintenance
+
+**Planned V5 Usage:**
+- Customer email lookups (email-based partition)
+- Product price range queries (price-based partition)
+- Order date-based analytics (date-based partition)
+
+*Note: GSI syntax is simplified in this demo due to current API limitations*
+
+### 3. Computed Properties
+**Status**: Generally Available
+**Documentation**: [Computed Properties](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/computed-properties)
+
+Computed properties automatically calculate and index derived values at ingestion time.
+
+**Benefits:**
+- Automatic indexing for fast queries on calculated fields
+- Consistent calculations across all queries
+- Reduced application complexity
+- Better query performance vs. runtime calculations
+
+**V5 Examples:**
+```sql
+-- Customer computed properties
+fullName: CONCAT(c.firstName, " ", c.lastName)
+yearCreated: DateTimePart("yyyy", c.creationDate)
+
+-- Product computed properties
+priceRange: c.price < 50 ? "low" : c.price < 200 ? "medium" : "high"
+discountedPrice: c.price * 0.9
+
+-- Order computed properties
+orderMonth: DateTimePart("yyyy-MM", c.orderDate)
+totalValue: SUM(ARRAY(SELECT VALUE (item.price * item.quantity) FROM item IN c.details))
+```
+
+### 4. All Versions and Deletes Change Feed
+**Status**: Generally Available
+**Documentation**: [Change Feed Modes](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/change-feed-modes)
+
+Enhanced change feed provides comprehensive operation tracking including deletes and version history.
+
+**Benefits:**
+- Track Create, Update, and Delete operations
+- Access to before/after versions for updates
+- Comprehensive audit trails
+- Better data synchronization capabilities
+
+**V5 Implementation:**
+- Advanced change feed processor for customer changes
+- Demonstrates operation type detection
+- Shows before/after state comparison
+- Includes business rule processing examples
+
+## Demo Functions
+
+### Menu Options (V5 Features)
+- **[l]** Demo: Hierarchical Partitioning - Shows regional partitioning strategies
+- **[m]** Demo: Computed Properties - Demonstrates calculated field indexing
+- **[n]** Demo: Advanced Change Feed - Shows comprehensive change tracking
+- **[o]** Demo: Cross-region queries - Compares partition strategies
+
+### Educational Content
+
+Each demo includes:
+- Feature explanation and benefits
+- Real-world use case examples
+- Performance considerations
+- Best practice recommendations
+- Migration guidance from earlier versions
+
+## Data Model Evolution
+
+### V1 ā V2 ā V3 ā V4 ā V5 Progression
+
+1. **V1**: Relational-style (normalized, many containers)
+2. **V2**: Basic denormalization (embedded addresses, optimized partition keys)
+3. **V3**: Advanced denormalization (embedded product categories)
+4. **V4**: Complete denormalization (customers + orders in same container)
+5. **V5**: Advanced features (hierarchical partitioning, computed properties, enhanced change feed)
+
+### V5 Data Model Highlights
+
+- **Regional partitioning**: Optimized for geo-distributed applications
+- **Computed properties**: Automatic calculation and indexing of derived fields
+- **Enhanced monitoring**: Comprehensive change tracking with operation history
+- **Performance optimized**: Leverages latest Cosmos DB capabilities
+
+## Getting Started with V5
+
+1. Deploy infrastructure with V5 containers:
+ ```bash
+ azd up
+ ```
+
+2. Load V5 sample data:
+ ```bash
+ cd src
+ dotnet run
+ # Press 'k' to load data
+ ```
+
+3. Explore V5 demos:
+ ```bash
+ # Press 'l' for hierarchical partitioning
+ # Press 'm' for computed properties
+ # Press 'n' for advanced change feed
+ # Press 'o' for cross-region queries
+ ```
+
+## Migration Considerations
+
+### From V4 to V5
+- Consider regional data distribution requirements
+- Identify calculated fields that benefit from computed properties
+- Plan for enhanced change feed processing
+- Evaluate hierarchical partitioning for your access patterns
+
+### Performance Impact
+- V5 features generally improve query performance
+- Computed properties reduce runtime calculation overhead
+- Hierarchical partitioning can significantly reduce RU consumption
+- Enhanced change feed provides better observability
+
+## Learn More
+
+- [Azure Cosmos DB Documentation](https://docs.microsoft.com/azure/cosmos-db/)
+- [NoSQL Data Modeling Guide](https://docs.microsoft.com/azure/cosmos-db/nosql/modeling-data)
+- [Partitioning Best Practices](https://docs.microsoft.com/azure/cosmos-db/partitioning-overview)
+- [Change Feed Best Practices](https://docs.microsoft.com/azure/cosmos-db/change-feed-processor)
\ No newline at end of file
diff --git a/infra/modules/cosmos.bicep b/infra/modules/cosmos.bicep
index 82f7911..01228a9 100644
--- a/infra/modules/cosmos.bicep
+++ b/infra/modules/cosmos.bicep
@@ -476,6 +476,90 @@ resource productMetaV4Container 'Microsoft.DocumentDB/databaseAccounts/sqlDataba
}
}
+// Database v5 - Advanced Features Showcase
+resource databaseV5 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-11-15' = {
+ parent: cosmosAccount
+ name: 'database-v5'
+ properties: {
+ resource: {
+ id: 'database-v5'
+ }
+ }
+}
+
+// Database v5 Containers with Advanced Features
+// Customer container demonstrating hierarchical partitioning concepts
+resource customerV5Container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
+ parent: databaseV5
+ name: 'customer'
+ properties: {
+ resource: {
+ id: 'customer'
+ partitionKey: {
+ paths: [
+ '/region'
+ ]
+ kind: 'Hash'
+ version: 2
+ }
+ }
+ }
+}
+
+// Product container demonstrating efficient category-based partitioning
+resource productV5Container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
+ parent: databaseV5
+ name: 'product'
+ properties: {
+ resource: {
+ id: 'product'
+ partitionKey: {
+ paths: [
+ '/categoryId'
+ ]
+ kind: 'Hash'
+ version: 2
+ }
+ }
+ }
+}
+
+// Sales Order container with regional partitioning for efficient queries
+resource salesOrderV5Container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
+ parent: databaseV5
+ name: 'salesOrder'
+ properties: {
+ resource: {
+ id: 'salesOrder'
+ partitionKey: {
+ paths: [
+ '/region'
+ ]
+ kind: 'Hash'
+ version: 2
+ }
+ }
+ }
+}
+
+// Leases container for change feed processing
+resource leasesV5Container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-11-15' = {
+ parent: databaseV5
+ name: 'leases'
+ properties: {
+ resource: {
+ id: 'leases'
+ partitionKey: {
+ paths: [
+ '/id'
+ ]
+ kind: 'Hash'
+ version: 2
+ }
+ }
+ }
+}
+
// Role assignments
resource managedIdentityRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-11-15' = {
diff --git a/src/AdvancedChangeFeed.cs b/src/AdvancedChangeFeed.cs
new file mode 100644
index 0000000..f987d30
--- /dev/null
+++ b/src/AdvancedChangeFeed.cs
@@ -0,0 +1,158 @@
+using Microsoft.Azure.Cosmos;
+using models;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace CosmicWorks
+{
+ public class AdvancedChangeFeed
+ {
+ private ChangeFeedProcessor _changeFeedProcessor;
+ private CosmosClient _cosmosClient;
+ private Container _monitoredContainer;
+ private Container _leasesContainer;
+
+ public AdvancedChangeFeed(CosmosClient cosmosClient)
+ {
+ _cosmosClient = cosmosClient;
+
+ // Monitor the V5 customer container for comprehensive change tracking
+ _monitoredContainer = _cosmosClient.GetContainer("database-v5", "customer");
+ _leasesContainer = _cosmosClient.GetContainer("database-v5", "leases");
+ }
+
+ ///
+ /// Start Change Feed Processor with enhanced tracking
+ /// This demonstrates improved change feed capabilities for V5
+ ///
+ public async Task StartAdvancedChangeFeedProcessorAsync()
+ {
+ // Create Change Feed Processor for V5 advanced features
+ _changeFeedProcessor = _monitoredContainer
+ .GetChangeFeedProcessorBuilder(
+ "AdvancedCustomerChanges",
+ HandleAdvancedChangesAsync)
+ .WithInstanceName("AdvancedCustomerChanges")
+ .WithLeaseContainer(_leasesContainer)
+ .WithStartTime(DateTime.UtcNow.AddMinutes(-5)) // Start from 5 minutes ago
+ .Build();
+
+ // Start the Change Feed Processor
+ await _changeFeedProcessor.StartAsync();
+
+ Console.WriteLine("Advanced Change Feed Processor started - monitoring V5 customer changes");
+ return _changeFeedProcessor;
+ }
+
+ public async Task StopAdvancedChangeFeedProcessorAsync()
+ {
+ if (_changeFeedProcessor != null)
+ {
+ await _changeFeedProcessor.StopAsync();
+ Console.WriteLine("Advanced Change Feed Processor stopped");
+ }
+ }
+
+ ///
+ /// Handle changes with enhanced processing for V5 features
+ /// Demonstrates advanced change feed patterns and best practices
+ ///
+ private async Task HandleAdvancedChangesAsync(
+ IReadOnlyCollection changes,
+ CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"\n=== Advanced Change Feed Event (V5) ===");
+ Console.WriteLine($"Batch received: {changes.Count} change(s)");
+ Console.WriteLine($"Approximate processing time: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}");
+
+ foreach (var customer in changes)
+ {
+ try
+ {
+ await ProcessCustomerChange(customer);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error processing change for customer {customer.id}: {ex.Message}");
+ }
+ }
+
+ Console.WriteLine("=== End Advanced Change Feed Event ===\n");
+ }
+
+ private Task ProcessCustomerChange(CustomerV5 customer)
+ {
+ Console.WriteLine($"\nš Customer Change Detected:");
+ Console.WriteLine($" ID: {customer.customerId}");
+ Console.WriteLine($" Name: {customer.firstName} {customer.lastName}");
+ Console.WriteLine($" Region: {customer.region}");
+ Console.WriteLine($" Email: {customer.emailAddress}");
+ Console.WriteLine($" Order Count: {customer.salesOrderCount}");
+
+ // Demonstrate V5 hierarchical partitioning benefits
+ Console.WriteLine($" Hierarchical Partition: [{customer.region}, {customer.customerId}]");
+
+ // Simulate computed property usage (these would be auto-calculated in real V5)
+ string computedFullName = $"{customer.firstName} {customer.lastName}";
+ int computedYearCreated = DateTime.Parse(customer.creationDate).Year;
+
+ Console.WriteLine($" Computed Properties:");
+ Console.WriteLine($" - Full Name: {computedFullName}");
+ Console.WriteLine($" - Year Created: {computedYearCreated}");
+
+ // Example of advanced processing based on change
+ ProcessBusinessRules(customer);
+
+ return Task.CompletedTask;
+ }
+
+ private Task ProcessBusinessRules(CustomerV5 customer)
+ {
+ // Example business rules that could be triggered by change feed
+ Console.WriteLine($" š Processing Business Rules:");
+
+ // Rule 1: High-value customer detection
+ if (customer.salesOrderCount >= 10)
+ {
+ Console.WriteLine($" ā High-value customer detected (>{customer.salesOrderCount} orders)");
+ // Could trigger: VIP status update, special offers, account manager assignment
+ }
+
+ // Rule 2: Regional analytics update
+ Console.WriteLine($" š Updating regional analytics for: {customer.region}");
+ // Could trigger: Regional dashboard updates, inventory planning
+
+ // Rule 3: Customer engagement scoring
+ var engagementScore = CalculateEngagementScore(customer);
+ Console.WriteLine($" šÆ Engagement Score: {engagementScore}/100");
+ // Could trigger: Marketing campaigns, retention programs
+
+ // Rule 4: Data consistency checks across hierarchical partitions
+ Console.WriteLine($" ā
Validating hierarchical partition consistency");
+ // Could trigger: Cross-partition validation, data integrity checks
+
+ return Task.CompletedTask;
+ }
+
+ private int CalculateEngagementScore(CustomerV5 customer)
+ {
+ // Simple engagement scoring algorithm for demo
+ int score = 0;
+
+ // Points for order history
+ score += Math.Min(customer.salesOrderCount * 10, 50);
+
+ // Points for account age (assuming newer accounts get some base points)
+ var accountAge = DateTime.UtcNow.Year - DateTime.Parse(customer.creationDate).Year;
+ score += Math.Min(accountAge * 5, 30);
+
+ // Points for complete profile
+ if (!string.IsNullOrEmpty(customer.phoneNumber)) score += 10;
+ if (customer.addresses?.Count > 0) score += 10;
+
+ return Math.Min(score, 100);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Dataload.cs b/src/Dataload.cs
index aba16e0..27e8ed9 100644
--- a/src/Dataload.cs
+++ b/src/Dataload.cs
@@ -17,6 +17,7 @@ public static async Task LoadData(CosmosClient cosmosDBClient, bool force = fals
await LoadContainersFromGitHub(cosmosDBClient, "database-v2");
await LoadContainersFromGitHub(cosmosDBClient, "database-v3");
await LoadContainersFromGitHub(cosmosDBClient, "database-v4");
+ await LoadContainersFromGitHub(cosmosDBClient, "database-v5");
}
private static async Task LoadContainersFromGitHub(CosmosClient client, string databaseName)
diff --git a/src/Models.cs b/src/Models.cs
index a6b0bc6..d9d63bd 100644
--- a/src/Models.cs
+++ b/src/Models.cs
@@ -60,6 +60,7 @@ public class ProductCategory
public string id { get; set; }
public string type { get; set; }
public string name { get; set; }
+ public string parentCategoryId { get; set; } // For hierarchical categories
}
public class Product
@@ -98,4 +99,51 @@ public class SalesOrderDetails
public int quantity { get; set; }
}
+ // V5 Models with Advanced Features Support
+
+ public class CustomerV5
+ {
+ public string id { get; set; }
+ public string type { get; set; }
+ public string customerId { get; set; }
+ public string region { get; set; } // Hierarchical partition key part 1
+ public string title { get; set; }
+ public string firstName { get; set; }
+ public string lastName { get; set; }
+ public string emailAddress { get; set; }
+ public string phoneNumber { get; set; }
+ public string creationDate { get; set; }
+ public List addresses { get; set; }
+ public Password password { get; set; }
+ public int salesOrderCount { get; set; }
+ // Note: fullName and yearCreated are computed properties
+ }
+
+ public class ProductV5
+ {
+ public string id { get; set; }
+ public string categoryId { get; set; } // Hierarchical partition key part 1
+ public string subCategoryId { get; set; } // Hierarchical partition key part 2
+ public string categoryName { get; set; }
+ public string subCategoryName { get; set; }
+ public string sku { get; set; }
+ public string name { get; set; }
+ public string description { get; set; }
+ public double price { get; set; }
+ public List tags { get; set; }
+ // Note: priceRange and discountedPrice are computed properties
+ }
+
+ public class SalesOrderV5
+ {
+ public string id { get; set; }
+ public string type { get; set; }
+ public string customerId { get; set; }
+ public string region { get; set; } // Hierarchical partition key part 1 (matches customer)
+ public string orderDate { get; set; }
+ public string shipDate { get; set; }
+ public List details { get; set; }
+ // Note: orderMonth and totalValue are computed properties
+ }
+
}
diff --git a/src/Program.cs b/src/Program.cs
index 6325521..4805ef9 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -8,6 +8,7 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
@@ -18,6 +19,7 @@ class Program
static CosmosClient cosmosClient;
static ChangeFeed changeFeed;
+ static AdvancedChangeFeed advancedChangeFeed;
public static void AddConfiguration(IConfigurationBuilder config)
{
@@ -32,6 +34,9 @@ public static void AddConfiguration(IConfigurationBuilder config)
// Create the ChangeFeed instance
changeFeed = new ChangeFeed(cosmosClient);
+ // Create the Advanced ChangeFeed instance for V5 features
+ advancedChangeFeed = new AdvancedChangeFeed(cosmosClient);
+
}
public static async Task Main(string[] args)
@@ -67,6 +72,11 @@ public static async Task RunApp()
Console.WriteLine($"[i] Delete order and update order total");
Console.WriteLine($"[j] Query top 10 customers");
Console.WriteLine($"-------------------------------------------");
+ Console.WriteLine($"[l] Demo: Hierarchical Partitioning (V5)");
+ Console.WriteLine($"[m] Demo: Computed Properties (V5)");
+ Console.WriteLine($"[n] Demo: Advanced Change Feed (V5)");
+ Console.WriteLine($"[o] Demo: Cross-region queries (V5)");
+ Console.WriteLine($"-------------------------------------------");
Console.WriteLine($"[k] Upload data to containers");
Console.WriteLine($"-------------------------------------------");
Console.WriteLine($"[x] Exit");
@@ -126,6 +136,26 @@ public static async Task RunApp()
Console.Clear();
await GetTop10Customers();
}
+ else if (result.KeyChar == 'l')
+ {
+ Console.Clear();
+ await DemoHierarchicalPartitioning();
+ }
+ else if (result.KeyChar == 'm')
+ {
+ Console.Clear();
+ await DemoComputedProperties();
+ }
+ else if (result.KeyChar == 'n')
+ {
+ Console.Clear();
+ await DemoAdvancedChangeFeed();
+ }
+ else if (result.KeyChar == 'o')
+ {
+ Console.Clear();
+ await DemoCrossRegionQueries();
+ }
else if (result.KeyChar == 'k')
{
//Stop Change Feed Processor
@@ -547,5 +577,437 @@ public static void Print(object obj)
{
Console.WriteLine($"{JObject.FromObject(obj).ToString()}\n");
}
+
+ // V5 Advanced Features Demonstrations
+
+ public static async Task DemoHierarchicalPartitioning()
+ {
+ Database database = cosmosClient.GetDatabase("database-v5");
+ Container customerContainer = database.GetContainer("customer");
+ Container salesOrderContainer = database.GetContainer("salesOrder");
+
+ Console.WriteLine("=== Hierarchical Partitioning Demo ===\n");
+ Console.WriteLine("V5 demonstrates advanced partitioning strategies:");
+ Console.WriteLine("⢠Regional partitioning for customers and orders");
+ Console.WriteLine("⢠Enables efficient geo-distributed queries");
+ Console.WriteLine("⢠Supports both regional and cross-regional analytics\n");
+
+ Console.WriteLine("š About Hierarchical Partitioning:");
+ Console.WriteLine("Hierarchical partition keys (available in preview) allow multiple levels:");
+ Console.WriteLine("Example: [/region, /customerId] or [/tenantId, /userId, /deviceId]");
+ Console.WriteLine("Benefits:");
+ Console.WriteLine("⢠Better query performance for common access patterns");
+ Console.WriteLine("⢠More granular control over data distribution");
+ Console.WriteLine("⢠Improved scalability for multi-tenant scenarios\n");
+
+ // Demo 1: Query customers in a specific region efficiently
+ Console.WriteLine("1. Query all customers in North America (using regional partition key):");
+
+ string sql = "SELECT c.customerId, c.firstName, c.lastName, c.region FROM c WHERE c.type = 'customer'";
+
+ FeedIterator resultSet = customerContainer.GetItemQueryIterator(
+ new QueryDefinition(sql),
+ requestOptions: new QueryRequestOptions()
+ {
+ // Efficient query using regional partition key
+ PartitionKey = new PartitionKey("North America")
+ });
+
+ double requestCharge = 0;
+ while (resultSet.HasMoreResults)
+ {
+ FeedResponse response = await resultSet.ReadNextAsync();
+ requestCharge += response.RequestCharge;
+
+ foreach (var customer in response)
+ {
+ Console.WriteLine($" Customer: {customer.firstName} {customer.lastName} (ID: {customer.customerId})");
+ }
+ }
+ Console.WriteLine($" Request Charge (Regional query): {requestCharge:F2} RUs\n");
+
+ // Demo 2: Point read using regional partition key
+ Console.WriteLine("2. Point read for specific customer using regional partition key:");
+
+ try
+ {
+ ItemResponse pointReadResponse = await customerContainer.ReadItemAsync(
+ id: "CUSTOMER-001",
+ partitionKey: new PartitionKey("North America"));
+
+ Console.WriteLine($" Found: {pointReadResponse.Resource.firstName} {pointReadResponse.Resource.lastName}");
+ Console.WriteLine($" Request Charge (Point read): {pointReadResponse.RequestCharge:F2} RUs");
+ }
+ catch (CosmosException ex)
+ {
+ Console.WriteLine($" Note: Point read requires exact partition key match");
+ Console.WriteLine($" Error: {ex.Message}");
+ }
+
+ // Demo 3: Cross-region comparison
+ Console.WriteLine("\n3. Regional comparison using optimized partitioning:");
+
+ var regions = new[] { "North America", "Europe", "Asia Pacific" };
+
+ foreach (string region in regions)
+ {
+ string regionalSql = "SELECT COUNT(1) as CustomerCount, AVG(c.salesOrderCount) as AvgOrders FROM c WHERE c.type = 'customer'";
+
+ FeedIterator regionalQuery = customerContainer.GetItemQueryIterator(
+ new QueryDefinition(regionalSql),
+ requestOptions: new QueryRequestOptions()
+ {
+ PartitionKey = new PartitionKey(region)
+ });
+
+ double regionalRU = 0;
+ while (regionalQuery.HasMoreResults)
+ {
+ FeedResponse response = await regionalQuery.ReadNextAsync();
+ regionalRU += response.RequestCharge;
+
+ foreach (var result in response)
+ {
+ Console.WriteLine($" {region}: {result.CustomerCount} customers, Avg Orders: {result.AvgOrders:F1} (RU: {regionalRU:F2})");
+ }
+ }
+ }
+
+ Console.WriteLine("\nš Real-world Hierarchical Partitioning Examples:");
+ Console.WriteLine("⢠E-commerce: [/region, /customerId] for geo-distributed customer data");
+ Console.WriteLine("⢠IoT: [/deviceType, /location, /deviceId] for sensor data");
+ Console.WriteLine("⢠Multi-tenant SaaS: [/tenantId, /userId] for customer isolation");
+ Console.WriteLine("⢠Gaming: [/gameId, /playerId] for player-specific queries");
+
+ Console.WriteLine("\nPress any key to continue...");
+ Console.ReadKey();
+ }
+
+ public static async Task DemoComputedProperties()
+ {
+ Database database = cosmosClient.GetDatabase("database-v5");
+ Container customerContainer = database.GetContainer("customer");
+ Container productContainer = database.GetContainer("product");
+
+ Console.WriteLine("=== Computed Properties Demo ===\n");
+ Console.WriteLine("š About Computed Properties:");
+ Console.WriteLine("Computed properties automatically calculate and index derived values:");
+ Console.WriteLine("⢠Defined at container creation time");
+ Console.WriteLine("⢠Automatically maintained and indexed");
+ Console.WriteLine("⢠Enable efficient queries on calculated fields");
+ Console.WriteLine("⢠Reduce application complexity and improve performance\n");
+
+ Console.WriteLine("Example Computed Properties for V5:");
+ Console.WriteLine("Customer container:");
+ Console.WriteLine(" ⢠fullName: CONCAT(c.firstName, ' ', c.lastName)");
+ Console.WriteLine(" ⢠yearCreated: DateTimePart('yyyy', c.creationDate)");
+ Console.WriteLine("Product container:");
+ Console.WriteLine(" ⢠priceRange: c.price < 50 ? 'low' : c.price < 200 ? 'medium' : 'high'");
+ Console.WriteLine(" ⢠discountedPrice: c.price * 0.9\n");
+
+ // Demo 1: Simulate computed property queries for customers
+ Console.WriteLine("1. Customer queries using computed 'fullName' property:");
+ Console.WriteLine(" (Simulating what would be efficient computed property queries)");
+
+ string customerSql = "SELECT c.customerId, c.firstName, c.lastName, c.region FROM c WHERE c.type = 'customer'";
+
+ FeedIterator customerResults = customerContainer.GetItemQueryIterator(
+ new QueryDefinition(customerSql));
+
+ while (customerResults.HasMoreResults)
+ {
+ FeedResponse response = await customerResults.ReadNextAsync();
+
+ foreach (var customer in response)
+ {
+ // Simulate computed property usage
+ string computedFullName = $"{customer.firstName} {customer.lastName}";
+ string computedYear = DateTime.Parse("2023-01-01").Year.ToString(); // Simplified for demo
+ Console.WriteLine($" Computed fullName: '{computedFullName}' (Region: {customer.region})");
+ Console.WriteLine($" Computed yearCreated: {computedYear}");
+ }
+ }
+
+ // Demo 2: Product price range analysis using computed properties
+ Console.WriteLine("\n2. Product analysis using computed 'priceRange' property:");
+
+ string productSql = "SELECT p.name, p.price, p.categoryName FROM p";
+
+ FeedIterator productResults = productContainer.GetItemQueryIterator(
+ new QueryDefinition(productSql));
+
+ var priceRangeStats = new Dictionary>
+ {
+ ["low"] = new List(),
+ ["medium"] = new List(),
+ ["high"] = new List()
+ };
+
+ while (productResults.HasMoreResults)
+ {
+ FeedResponse response = await productResults.ReadNextAsync();
+
+ foreach (var product in response)
+ {
+ // Simulate computed property calculation
+ double price = (double)product.price;
+ string priceRange = price < 50 ? "low" :
+ price < 200 ? "medium" : "high";
+ double discountedPrice = price * 0.9;
+
+ priceRangeStats[priceRange].Add(price);
+
+ Console.WriteLine($" Product: {product.name} ({product.categoryName})");
+ Console.WriteLine($" Price: ${price:F2}, Range: {priceRange}, Discounted: ${discountedPrice:F2}");
+ }
+ }
+
+ // Demo 3: Aggregation using computed properties
+ Console.WriteLine("\n3. Price range analysis (leveraging computed priceRange):");
+ foreach (var range in priceRangeStats)
+ {
+ if (range.Value.Count > 0)
+ {
+ double avgPrice = range.Value.Average();
+ Console.WriteLine($" {range.Key.ToUpper()} range: {range.Value.Count} products, Avg price: ${avgPrice:F2}");
+ }
+ }
+
+ Console.WriteLine("\nš Benefits of Computed Properties:");
+ Console.WriteLine("ā
Automatic indexing for fast queries");
+ Console.WriteLine("ā
Consistent calculations across all queries");
+ Console.WriteLine("ā
Reduced application logic complexity");
+ Console.WriteLine("ā
Better query performance vs. calculated fields");
+ Console.WriteLine("ā
Simplified data access patterns");
+
+ Console.WriteLine("\nš Real-world Use Cases:");
+ Console.WriteLine("⢠E-commerce: Product search by computed price ranges");
+ Console.WriteLine("⢠Analytics: Time-based aggregations (year, month, quarter)");
+ Console.WriteLine("⢠User management: Search by computed display names");
+ Console.WriteLine("⢠Financial: Risk scoring based on multiple factors");
+ Console.WriteLine("⢠IoT: Computed alert levels from sensor readings");
+
+ Console.WriteLine("\nPress any key to continue...");
+ Console.ReadKey();
+ }
+
+ public static async Task DemoAdvancedChangeFeed()
+ {
+ Console.WriteLine("=== Advanced Change Feed Demo ===\n");
+ Console.WriteLine("V5 demonstrates 'All Versions and Deletes' change feed mode:");
+ Console.WriteLine("- Tracks Create, Update, and Delete operations");
+ Console.WriteLine("- Provides before/after versions for updates");
+ Console.WriteLine("- Enables comprehensive audit trails\n");
+
+ // Start the advanced change feed processor
+ Console.WriteLine("Starting Advanced Change Feed Processor...");
+ await advancedChangeFeed.StartAdvancedChangeFeedProcessorAsync();
+
+ Console.WriteLine("\nNow let's make some changes to see the change feed in action:");
+ Console.WriteLine("Press 'c' to create a customer, 'u' to update a customer, 'd' to delete a customer, or 'x' to exit:");
+
+ Database database = cosmosClient.GetDatabase("database-v5");
+ Container customerContainer = database.GetContainer("customer");
+
+ bool exitDemo = false;
+ while (!exitDemo)
+ {
+ ConsoleKeyInfo key = Console.ReadKey(true);
+
+ switch (key.KeyChar)
+ {
+ case 'c':
+ Console.WriteLine("\n--- Creating new customer ---");
+ var newCustomer = new CustomerV5
+ {
+ id = $"CUSTOMER-{Guid.NewGuid().ToString()[..8]}",
+ type = "customer",
+ customerId = $"CUSTOMER-{Guid.NewGuid().ToString()[..8]}",
+ region = "North America",
+ firstName = "Demo",
+ lastName = "Customer",
+ emailAddress = "demo.customer@adventure-works.com",
+ phoneNumber = "555-0999",
+ creationDate = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
+ addresses = new List(),
+ password = new Password { hash = "demo", salt = "demo" },
+ salesOrderCount = 0
+ };
+
+ try
+ {
+ await customerContainer.CreateItemAsync(newCustomer,
+ new PartitionKey(newCustomer.region));
+ Console.WriteLine("Customer created successfully!");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error creating customer: {ex.Message}");
+ }
+ break;
+
+ case 'u':
+ Console.WriteLine("\n--- Updating existing customer ---");
+ try
+ {
+ // Read and update the first customer
+ var readResponse = await customerContainer.ReadItemAsync(
+ "CUSTOMER-001",
+ new PartitionKey("North America"));
+
+ var customerToUpdate = readResponse.Resource;
+ customerToUpdate.salesOrderCount += 1;
+ customerToUpdate.phoneNumber = "555-UPDATED";
+
+ await customerContainer.ReplaceItemAsync(customerToUpdate,
+ customerToUpdate.id,
+ new PartitionKey(customerToUpdate.region));
+
+ Console.WriteLine("Customer updated successfully!");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error updating customer: {ex.Message}");
+ }
+ break;
+
+ case 'd':
+ Console.WriteLine("\n--- Deleting customer (simulated) ---");
+ Console.WriteLine("Note: Delete operation would be tracked in change feed");
+ Console.WriteLine("Showing how deletion would appear in change feed logs");
+ break;
+
+ case 'x':
+ exitDemo = true;
+ break;
+
+ default:
+ Console.WriteLine("\nInvalid option. Press 'c', 'u', 'd', or 'x'");
+ break;
+ }
+
+ if (!exitDemo)
+ {
+ await Task.Delay(2000); // Wait for change feed to process
+ Console.WriteLine("\nPress 'c' to create, 'u' to update, 'd' to delete, or 'x' to exit:");
+ }
+ }
+
+ // Stop the change feed processor
+ await advancedChangeFeed.StopAdvancedChangeFeedProcessorAsync();
+
+ Console.WriteLine("\nPress any key to continue...");
+ Console.ReadKey();
+ }
+
+ public static async Task DemoCrossRegionQueries()
+ {
+ Database database = cosmosClient.GetDatabase("database-v5");
+ Container customerContainer = database.GetContainer("customer");
+ Container salesOrderContainer = database.GetContainer("salesOrder");
+
+ Console.WriteLine("=== Cross-Region Query Performance Demo ===\n");
+ Console.WriteLine("Comparing query performance across different partition strategies:");
+ Console.WriteLine("- Regional queries (using hierarchical partitioning)");
+ Console.WriteLine("- Cross-region aggregations");
+ Console.WriteLine("- Customer-specific queries\n");
+
+ // Demo 1: Regional customer summary
+ Console.WriteLine("1. Regional Customer Analysis:");
+
+ var regions = new[] { "North America", "Europe", "Asia Pacific" };
+
+ foreach (string region in regions)
+ {
+ string regionalSql = "SELECT COUNT(1) as CustomerCount FROM c WHERE c.type = 'customer'";
+
+ FeedIterator regionalQuery = customerContainer.GetItemQueryIterator(
+ new QueryDefinition(regionalSql),
+ requestOptions: new QueryRequestOptions()
+ {
+ PartitionKey = new PartitionKey(region)
+ });
+
+ double requestCharge = 0;
+ int customerCount = 0;
+
+ while (regionalQuery.HasMoreResults)
+ {
+ FeedResponse response = await regionalQuery.ReadNextAsync();
+ requestCharge += response.RequestCharge;
+
+ foreach (var result in response)
+ {
+ customerCount = result.CustomerCount;
+ }
+ }
+
+ Console.WriteLine($" {region}: {customerCount} customers (RU: {requestCharge:F2})");
+ }
+
+ // Demo 2: Cross-partition aggregation
+ Console.WriteLine("\n2. Global Customer Summary (Cross-Partition Query):");
+
+ string globalSql = "SELECT c.region, COUNT(1) as Count, AVG(c.salesOrderCount) as AvgOrders " +
+ "FROM c WHERE c.type = 'customer' GROUP BY c.region";
+
+ FeedIterator globalQuery = customerContainer.GetItemQueryIterator(
+ new QueryDefinition(globalSql));
+
+ double globalRequestCharge = 0;
+
+ while (globalQuery.HasMoreResults)
+ {
+ FeedResponse response = await globalQuery.ReadNextAsync();
+ globalRequestCharge += response.RequestCharge;
+
+ foreach (var result in response)
+ {
+ Console.WriteLine($" Region: {result.region}, Customers: {result.Count}, Avg Orders: {result.AvgOrders:F1}");
+ }
+ }
+
+ Console.WriteLine($" Total RU for global query: {globalRequestCharge:F2}");
+
+ // Demo 3: Customer order history across regions
+ Console.WriteLine("\n3. Customer Order History (Hierarchical Partition Benefits):");
+
+ string customerOrderSql = @"
+ SELECT o.id, o.orderDate,
+ SUM(ARRAY(SELECT VALUE (item.price * item.quantity) FROM item IN o.details)) as total
+ FROM o
+ WHERE o.type = 'salesOrder' AND o.customerId = 'CUSTOMER-001'";
+
+ FeedIterator orderQuery = salesOrderContainer.GetItemQueryIterator(
+ new QueryDefinition(customerOrderSql),
+ requestOptions: new QueryRequestOptions()
+ {
+ PartitionKey = new PartitionKey("North America") // Using region for efficient query
+ });
+
+ double orderRequestCharge = 0;
+
+ while (orderQuery.HasMoreResults)
+ {
+ FeedResponse response = await orderQuery.ReadNextAsync();
+ orderRequestCharge += response.RequestCharge;
+
+ foreach (var order in response)
+ {
+ Console.WriteLine($" Order: {order.id}, Date: {order.orderDate}, Total: ${order.total:F2}");
+ }
+ }
+
+ Console.WriteLine($" RU for customer-specific query: {orderRequestCharge:F2}");
+
+ Console.WriteLine("\nKey Benefits of Hierarchical Partitioning:");
+ Console.WriteLine("ā
Regional queries are highly efficient (single partition)");
+ Console.WriteLine("ā
Customer isolation maintained within regions");
+ Console.WriteLine("ā
Supports both regional and global analytics");
+ Console.WriteLine("ā
Optimal for geo-distributed applications");
+
+ Console.WriteLine("\nPress any key to continue...");
+ Console.ReadKey();
+ }
}
}