diff --git a/README.md b/README.md index 44ca3c8f..d1bdc013 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,22 @@ -# Oracle .NET Code Sample Respository +# Oracle .NET Code Sample Repository ## About Oracle .NET This repository provides .NET code samples for Oracle developers, specifically for Oracle Data Provider for .NET (ODP.NET) and other Oracle .NET components. .NET products for Oracle Database are free of charge. They consist of: ## Getting Started -Oracle .NET components are available individually on NuGet Gallery or bundled together as part of Oracle Data Access Components (ODAC). Provider downloads are available from NuGet Gallery and Oracle .NET download page. Oracle Developer Tools for VS Code or Visual Studio can be downloaded from the VS Code Marketplace or Visual Studio Marketplace, respectively. +Oracle .NET components are available individually on NuGet Gallery or bundled together as part of Oracle Data Access Components (ODAC). Provider downloads are available from NuGet Gallery and Oracle .NET download page. Oracle Developer Tools for Visual Studio can be downloaded from the Visual Studio Marketplace. ## Tutorials -For beginning Oracle .NET developers, these on-premises .NET database tutorials and Autonomous Database .NET tutorials will assist you in getting started with .NET application development with Oracle Database. +For novice Oracle .NET developers, these on-premises .NET database tutorials and Autonomous Database .NET tutorials will assist you in getting started with .NET application development with Oracle Database. Oracle .NET video tutorials are available from the Oracle .NET YouTube site. @@ -27,7 +26,7 @@ For those that want to build an end-to-end basic .NET web app for Oracle Databas @@ -41,6 +40,6 @@ Please consult the [security guide](./SECURITY.md) for our responsible security ## License -Copyright (c) 2015, 2023 Oracle and/or its affiliates. +Copyright (c) 2015, 2025 Oracle and/or its affiliates. Released under the MIT License diff --git a/images/oracle-nuget.png b/images/oracle-nuget.png new file mode 100644 index 00000000..63fa31d0 Binary files /dev/null and b/images/oracle-nuget.png differ diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 00000000..c8627c08 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,184 @@ +Managed ODP.NET and ODP.NET Core Code Samples +============================================= +You must have managed ODP.NET or ODP.NET Core installed. To run the samples, follow these directions: +1) Modify the Data Source attribute in the connection strings to connect to an Oracle database via Easy Connect (Plus), TNS connect descriptor, or TNS alias. +2) Most of these samples use the SCOTT or Human Resources (HR) schema.
+The SCOTT schema create scripts are located here: https://github.com/oracle/dotnet-db-samples/tree/master/schemas
+The HR schema create scripts are located here: https://github.com/oracle/db-sample-schemas +3) Add Oracle.ManagedDataAccess.dll to the sample application. +4) Review the README file in the samples subdirectory, if any. + +While these samples are designed for managed ODP.NET or ODP.NET Core, they generally can use unmanaged ODP.NET by incorporating Oracle.DataAccess.dll and +adding the unmanaged ODP.NET namespace reference (i.e. "using Oracle.DataAccess.Client;" and "using Oracle.DataAccess.Types;"). + +Running ODP.NET Core Samples from Command Line +============================================== +1) Install .NET Core SDK from Microsoft's website: https://dotnet.microsoft.com/download +2) Open a terminal such as PowerShell, command prompt, or bash. Enter the following commands to create and setup your ODP.NET Core sample:
+ A) dotnet new console --output (Sample Name)
+ B) dotnet add package Oracle.ManagedDataAccess.Core --version (e.g. 23.8.0) +4) Replace the contents of Program.cs with the GitHub sample code of interest. +5) Insert your user id, password, and data source. The sample will have its own README or comments to indicate additional configuration that may be required. +6) Run using the following command: dotnet run --project (Sample Name) + + +Below is the feature list the samples cover. Each feature's sample has its own subdirectory. + +AI Vector +---------------------- +* Search Vector Stores and Collections Sample: Load and vectorize data from .NET. Then, perform exact match searches and similarity searches against the data set. + +Application Continuity +---------------------- +* Sample 1: Unmanaged ODP.NET Application Continuity code sample with setup and runtime demo instructions. + +Parameter Array Binding +---------------------- +* Sample 1: Demonstrates parameter array binding. + +ASP.NET Core +------------ +* ASP.NET Core 2.x: Demonstrates a simple ASP.NET Core 2.x web app to connect and retrieve data.
+* ASP.NET Core 3.x: Demonstrates a simple ASP.NET Core 3.x web app to connect and retrieve data.
+* ASP.NET Core 6: Demonstrates a simple ASP.NET Core 6 web app to connect and retrieve data. + +PL/SQL Associative Array +------------------------ +* Sample 1: Demonstrates PL/SQL Associative Array binding. + +Async +----- +* Async Sample: Demonstrates using asynchronous ODP.NET (managed or core) and measures operation time.
+* Sync Sample: Demonstrates using synchronous ODP.NET (managed or core) and measures operation time to compare with async. + +Autonomous Database +------------------- +* ODP.NET Core Samples: Demonstrates how to connect ODP.NET Core to Oracle Autonomous Database via a console and an ASP.NET Core web app.
+* Managed ODP.NET Samples: Demonstrates how to connect managed ODP.NET to Oracle Autonomous Database via a console and an ASP.NET web app.
+* Unmanaged ODP.NET Sample: Demonstrates how to connect unmanaged ODP.NET to Oracle Autonomous Database via a console app. + +Azure Active Directory/Microsoft Entra ID +---------------------- +* Demonstrates connecting to Oracle Autonomous Database using a Microsoft Entra ID token with ODP.NET Core, managed, and unmanaged. + +Bulk Copy +--------- +* Sample 1: Demonstrates how to use ODP.NET bulk copy. Sample works for both managed and core ODP.NET. + +Client Factory +-------------- +* Sample 1: Demonstrates how to use the OracleClientFactory class. + +Command Builder +--------------- +* Sample 1: Demonstrates OracleCommandBuilder's SchemaSeparator property.
+* Sample 2: Demonstrates OracleCommandBuilders's QuoteIdentifier method.
+* Sample 3: Demonstrates OracleCommandBuilders's UnquoteIdentifier method. + +Configuration API +----------------- +* Samples demonstrate how to use the OracleConfiguration, OracleDataSourceCollection, and OracleOnsServerCollection classes. + +Connection +---------- +* Sample 1: Demonstrates OracleConnection's GetSchema() method.
+* Sample 2: Demonstrates all variations of OracleConnection's GetSchema(string) method overload.
+* Sample 3: Demonstrates all variations of OracleConnection's GetSchema(string, string[]) method overload. + +Connection String Builder +------------------------- +* Sample 1: Demonstrates how to use the OracleConnectionStringBuilder class. + +DataReader +---------- +* Unmanaged ODP.NET Sample: Demonstrates OracleDataReader's VisibleFieldCount and HiddenFieldCount properties.
+* ODP.NET Core Sample: Demonstrates how to connect and retrieve data using ODP.NET Core via a console app. + +Data Source Enumerator +---------------------- +* Sample 1: Demonstrates the functionality of OracleDataSourceEnumerator class. + +DataSet +------- +* Sample 1: Demonstrates data manipulation language (DML) operations on a Dataset.
+* Sample 2: Demonstrates how to populate a DataSet using C#.
+* Sample 3: Demonstrates DML operations on LOB columns.
+* Sample 4: Demonstrates how to populate a DataSet from multiple output Ref Cursors from a stored procedure.
+* Sample 5: Demonstrates how to populate a DataSet using Visual Basic .NET (VB.NET). + +Entity Framework Core +--------------------- +* Autonomous Database Sample: Demonstrates Oracle EF Core connecting to Oracle Autonomous Database.
+* Dependency Injection Sample: Demonstrates using dependency injection and ASP.NET Core with Oracle EF Core.
+* Getting Started Sample: Demonstrates a basic Oracle EF Core scenario using migrations and scaffolding.
+* JSON Columns Sample: Demonstrates how to create an owned entity, insert, query, update, and delete JSON column data.
+* Keyless Entity Types Sample: Demonstrates Oracle EF Core keyless entity types with relational and materialized views.
+* Stored Procedure Result Set Samples: Demonstrates using PL/SQL that returns either an explicitly or implicitly bound REF Cursor.
+* T4 Text Template Samples: Demonstrates data type mapping customization when scaffolding. + +Event Handler +------------- +* Sample 1: Demonstrates how to trap the OracleRowUpdatingEvent and OracleRowUpdatedEvent using VB.NET. + +Oracle Identity and Access Management +------------------------------------- +* Sample 1: Demonstrates how to use OCI .NET SDK to retrieve, authenticate, and refresh Oracle database tokens. + +JSON +---- +* Select JSON Sample: Demonstrates row insert into and query against a JSON table.
+* Select JSON CLOB Sample: Demonstrates row insert into and query against a JSON table using CLOB storage. + +LOB +--- +* Sample 1: Demonstrates how to populate and obtain LOB data from a DataSet.
+* Sample 2: Demonstrates how an OracleClob object is obtained as an output parameter of an anonymous PL/SQL block.
+* Sample 3: Demonstrates how an OracleClob object is obtained from an output parameter of a stored procedure.
+* Sample 4: Demonstrates how the LOB column data can be read as a .NET type by utilizing stream reads.
+* Sample 5: Demonstrates how to bind an OracleClob object as a parameter and refetch the newly updated CLOB data using an OracleDataReader and an OracleClob object.
+* Sample 6: Demonstrates LOB updates using row-level locking.
+* Sample 7: Demonstrates LOB updates using result set locking.
+* BFile Sample: Demonstrates accessing BFILEs through ODP.NET. + +OpenTelemetry +------------- +* Sample: Demonstrates using managed ODP.NET or ODP.NET Core with OpenTelemetry + +Performance Counters +-------------------- +* Sample 1: Demonstrates how to programmatically use ODP.NET performance counters. + +Pipelining and Async +-------------------- +* Pipelining and Async Sample: Demonstrates using pipelining and async ODP.NET (managed or core) and measures operation time.
+* No Pipelining and Sync Sample: Disables pipelining while using synchronous ODP.NET (managed or core) and measures operation time to compare with async and pipelining sample. + +Ref Cursor +---------- +* Sample 1: Demonstrates how a REF Cursor is obtained as an OracleDataReader.
+* Sample 2: Demonstrates how a REF Cursor is obtained as an OracleDataReader through the use of an OracleRefCursor object.
+* Sample 3: Demonstrates how multiple REF Cursors can be accessed by a single OracleDataReader.
+* Sample 4: Demonstrates how a DataSet can be populated from a REF Cursor. The sample also demonstrates how a REF Cursor can be updated.
+* Sample 5: Demonstrates how a DataSet can be populated from an OracleRefCursor object.
+* Sample 6: Demonstrates how to populate a DataSet with multiple REF Cursors selectively.
+* Sample 7: Demonstrates how to selectively obtain OracleDataReader objects from REF Cursors. + +Statement Cache +--------------- +* Sample 1: Demonstrates performance improvement when statement caching is enabled. + +Transaction +----------- +* Sample 1: Demonstrates the usage of EnlistTransaction API.
+* Sample 2: Demonstrates the usage of TransactionScope.
+* Sample 3: Demonstrates nested transactions with savepoints. + +User-Defined Types (UDT) +------------------------ +* Nested Table Sample: Demonstrates how to map, fetch, and manipulate a nested table of UDTs that has an inheritance hierarchy (i.e. parent and child types).
+* Object UDT Sample: Demonstrates how to map, fetch, and manipulate an Oracle UDT as a .NET custom object.
+* Spatial UDT Sample: Demonstrates how to map and fetch types similar to Oracle Spatial types as custom types.
+* Ref Sample: Demonstrates how to fetch UDTs referenced by REFs.
+* Ref Inheritance Sample: Demonstrates how to obtain and update Custom Type objects from OracleRef objects.
+* VARRAY Sample: Demonstrates how to map, fetch, and manipulate the Oracle VARRAY as a custom object. + diff --git a/samples/README.txt b/samples/README.txt deleted file mode 100644 index 2337b8e7..00000000 --- a/samples/README.txt +++ /dev/null @@ -1,163 +0,0 @@ -Managed ODP.NET and ODP.NET Core Code Samples -============================================= -You must have managed ODP.NET or ODP.NET Core installed. To run the samples, follow these directions: -1) Modify the Data Source attribute in the connection strings to connect to an Oracle database via Easy Connect, TNS connect descriptor, or TNS alias. -2) Most of these samples use the SCOTT or Human Resources (HR) schema. -The SCOTT schema create scripts are located here: https://github.com/oracle/dotnet-db-samples/tree/master/schemas -The HR schema create scripts are located here: https://github.com/oracle/db-sample-schemas -3) Add Oracle.ManagedDataAccess.dll to the sample application. -4) Read \doc\Readme.html, if any. - -While these samples are designed for managed ODP.NET or ODP.NET Core, they generally can use unmanaged ODP.NET by incorporating Oracle.DataAccess.dll and -adding the unmanaged ODP.NET namespace reference (i.e. "using Oracle.DataAccess.Client;" and "using Oracle.DataAccess.Types;"). - -Running ODP.NET Core Samples from Command Line -============================================== -1) Install .NET Core SDK from Microsoft's website: https://dotnet.microsoft.com/download -2) Open a terminal such as PowerShell, command prompt, or bash. Enter the following commands to create and setup your ODP.NET Core sample: - A) dotnet new console --output - B) dotnet add package Oracle.ManagedDataAccess.Core --version -3) Replace the contents of Program.cs with the GitHub sample code of interest. -4) Insert your user id, password, and data source. The sample will have its own README or comments to indicate additional configuration that may be required. -5) Run using the following command: dotnet run --project - - -Below is a list of topics that the samples cover: - -Application Continuity -====================== -Sample 1: Unmanaged ODP.NET Application Continuity code sample with setup and runtime demo instructions. - -Parameter Array Binding -======================= -Sample 1: Demonstrates parameter array binding. - -ASP.NET Core -============ -ASP.NET Core 2.x: Demonstrates a simple ASP.NET Core 2.x web app to connect and retrieve data. -ASP.NET Core 3.x: Demonstrates a simple ASP.NET Core 3.x web app to connect and retrieve data. -ASP.NET Core 6: Demonstrates a simple ASP.NET Core 6 web app to connect and retrieve data. - -PL/SQL Associative Array -======================== -Sample 1: Demonstrates PL/SQL Associative Array binding. - -Autonomous Database -=================== -ODP.NET Core Samples: Demonstrates how to connect ODP.NET Core to Oracle Autonomous Database via a console and an ASP.NET Core web app. -Managed ODP.NET Samples: Demonstrates how to connect managed ODP.NET to Oracle Autonomous Database via a console and an ASP.NET web app. -Unmanaged ODP.NET Sample: Demonstrates how to connect unmanaged ODP.NET to Oracle Autonomous Database via a console app. - -Azure Active Directory -====================== -Demonstrates connecting to Oracle Autonomous Database using an Azure Active Directory token with ODP.NET Core, managed, and unmanaged. - -Bulk Copy -========= -Sample 1: Demonstrates how to use ODP.NET bulk copy. Sample works for both managed and core ODP.NET. - -Client Factory -============== -Sample 1: Demonstrates how to use the OracleClientFactory class. - -Command Builder -=============== -Sample 1: Demonstrates OracleCommandBuilder's SchemaSeparator property. -Sample 2: Demonstrates OracleCommandBuilders's QuoteIdentifier method. -Sample 3: Demonstrates OracleCommandBuilders's UnquoteIdentifier method. - -Configuration API -================= -Samples demonstrate how to use the OracleConfiguration, OracleDataSourceCollection, and OracleOnsServerCollection classes. - -Connection -========== -Sample 1: Demonstrates OracleConnection's GetSchema() method. -Sample 2: Demonstrates all variations of OracleConnection's GetSchema(string) method overload. -Sample 3: Demonstrates all variations of OracleConnection's GetSchema(string, string[]) method overload. - -Connection String Builder -========================= -Sample 1: Demonstrates how to use the OracleConnectionStringBuilder class. - -DataReader -=========== -Unmanaged ODP.NET Sample: Demonstrates OracleDataReader's VisibleFieldCount and HiddenFieldCount properties. -ODP.NET Core Sample: Demonstrates how to connect and retrieve data using ODP.NET Core via a console app. - -Data Source Enumerator -====================== -Sample 1: Demonstrates the functionality of OracleDataSourceEnumerator class. - -DataSet -======= -Sample 1: Demonstrates data manipulation language (DML) operations on a Dataset. -Sample 2: Demonstrates how to populate a DataSet using C#. -Sample 3: Demonstrates DML operations on LOB columns. -Sample 4: Demonstrates how to populate a DataSet from multiple output Ref Cursors from a stored procedure. -Sample 5: Demonstrates how to populate a DataSet using Visual Basic .NET (VB.NET). - -Entity Framework Core -===================== -Autonomous Database Sample: Demonstrates Oracle EF Core connecting to Oracle Autonomous Database. -Dependency Injection Sample: Demonstrates using dependency injection and ASP.NET Core with Oracle EF Core. -Getting Started Sample: Demonstrates a basic Oracle EF Core scenario using migrations and scaffolding. -Keyless Entity Types Sample: Demonstrates Oracle EF Core keyless entity types with relational and materialized views. -Stored Procedure Result Set Samples: Demonstrates using PL/SQL that returns either an explicitly or implicitly bound REF Cursor. - -Event Handler -============= -Sample 1: Demonstrates how to trap the OracleRowUpdatingEvent and OracleRowUpdatedEvent using VB.NET. - -Oracle Identity and Access Management -===================================== -Sample 1: Demonstrates how to use OCI .NET SDK to retrieve, authenticate, and refresh Oracle database tokens. - -JSON -==== -Select JSON Sample: Demonstrates row insert into and query against a JSON table. -Select JSON CLOB Sample: Demonstrates row insert into and query against a JSON table using CLOB storage. - -LOB -=== -Sample 1: Demonstrates how to populate and obtain LOB data from a DataSet. -Sample 2: Demonstrates how an OracleClob object is obtained as an output parameter of an anonymous PL/SQL block. -Sample 3: Demonstrates how an OracleClob object is obtained from an output parameter of a stored procedure. -Sample 4: Demonstrates how the LOB column data can be read as a .NET type by utilizing stream reads. -Sample 5: Demonstrates how to bind an OracleClob object as a parameter and refetch the newly updated CLOB data using an OracleDataReader and an OracleClob object. -Sample 6: Demonstrates LOB updates using row-level locking. -Sample 7: Demonstrates LOB updates using result set locking. -BFile Sample: Demonstrates accessing BFILEs through ODP.NET. - -Performance Counters -==================== -Sample 1: Demonstrates how to programmatically use ODP.NET performance counters. - -Ref Cursor -========== -Sample 1: Demonstrates how a REF Cursor is obtained as an OracleDataReader. -Sample 2: Demonstrates how a REF Cursor is obtained as an OracleDataReader through the use of an OracleRefCursor object. -Sample 3: Demonstrates how multiple REF Cursors can be accessed by a single OracleDataReader. -Sample 4: Demonstrates how a DataSet can be populated from a REF Cursor. The sample also demonstrates how a REF Cursor can be updated. -Sample 5: Demonstrates how a DataSet can be populated from an OracleRefCursor object. -Sample 6: Demonstrates how to populate a DataSet with multiple REF Cursors selectively. -Sample 7: Demonstrates how to selectively obtain OracleDataReader objects from REF Cursors. - -Statement Cache -=============== -Sample 1: Demonstrates performance improvement when statement caching is enabled. - -Transaction -=========== -Sample 1: Demonstrates the usage of EnlistTransaction API. -Sample 2: Demonstrates the usage of TransactionScope. -Sample 3: Demonstrates nested transactions with savepoints. - -User-Defined Types (UDT) -======================== -Nested Table Sample: Demonstrates how to map, fetch, and manipulate a nested table of UDTs that has an inheritance hierarchy (i.e. parent and child types). -Object UDT Sample: Demonstrates how to map, fetch, and manipulate an Oracle UDT as a .NET custom object. -Spatial UDT Sample: Demonstrates how to map and fetch types similar to Oracle Spatial types as custom types. -Ref Sample: Demonstrates how to fetch UDTs referenced by REFs. -Ref Inheritance Sample: Demonstrates how to obtain and update Custom Type objects from OracleRef objects. -VARRAY Sample: Demonstrates how to map, fetch, and manipulate the Oracle VARRAY as a custom object. diff --git a/samples/ai-vector/Ai-vector-search-stores-and-collections.cs b/samples/ai-vector/Ai-vector-search-stores-and-collections.cs new file mode 100644 index 00000000..5a79af21 --- /dev/null +++ b/samples/ai-vector/Ai-vector-search-stores-and-collections.cs @@ -0,0 +1,192 @@ +using Oracle.VectorData; +using Oracle.ManagedDataAccess.Client; +using Microsoft.Extensions.VectorData; +using Microsoft.Extensions.Configuration; +using System.Text.Json; + +namespace OracleAIVectorData; + +// This ODP.NET sample app shows various ways to search vector data and collections. +// First, the data (hotels.json) is loaded into a .NET object list. +// The data initially includes hotel names, descriptions, ids, ratings, and parking availability. +// The hotel names and descriptions are then vectorized using the database ONNX embeddings, consisting of 384 dimensions in Float format. +// The entire data set is upserted into the database. +// Finally, four search operations are demonstrated. +// 1. Search by primary key. +// 2. Search scalar value properties. +// 3. Similarity search using cosine similarity. +// 4. Similarity search using Euclidean distance. +// This sample requires Oracle Database 23ai or higher. +// Add Oracle.VectorData and Microsoft.Extensions.Configuration.Json NuGet packages to your project. + +public class AIHotelSearchApp +{ + public static async Task Main(string[] args) + { + await SearchHotels(); + } + static async Task SearchHotels() + { + // Setup ODP.NET connection and vector configuration. + // Set connection string values in AppSettings.json file. + // Add AppSettings.json directory path below or place file in app's output directory. + var configuration = new ConfigurationBuilder() + .AddJsonFile(path: "AppSettings.json", optional: false) + .Build(); + + string? connStr = configuration.GetSection("Oracle")["ConnectionString"]; + OracleDataSource? ds = null; + OracleVectorStore? vs = null; + OracleCollection? collection = null; + string collectionName = "Hotels"; + + try + { + ds = new OracleDataSourceBuilder(connStr).Build(); + + // Create a vector store + vs = new OracleVectorStore(ds); + + // Create a vector collection + collection = (OracleCollection)vs.GetCollection(collectionName); + + // HotelsData.json contains plain text information about various hotels. + // Add Hotels.json directory path below or place file in app's output directory. + string jsonContent = File.ReadAllText("Hotels.json"); + List? hotels = JsonSerializer.Deserialize>(jsonContent); + + // Use the database ONNX generator to create VECTOR(384, FLOAT32) embeddings for each hotel/record. + foreach (Hotel hotel in hotels) + { + hotel.NameEmbedding = await GenerateEmbeddingAsync(ds, hotel.HotelName); + hotel.DescriptionEmbedding = await GenerateEmbeddingAsync(ds, hotel.Description); + } + + // Verify the collection exists in the database. + await collection.EnsureCollectionExistsAsync(); + + // Upsert the records into the database. + await collection.UpsertAsync(hotels); + + // Search hotels in the vector collection by primary key. + Console.WriteLine("Search for hotels with ID 5 and 10."); + Console.WriteLine("==============================================================================="); + IAsyncEnumerable hotelsById = collection.GetAsync([5, 10]); + await foreach (Hotel hotel in hotelsById) + { + Output(hotel); + } + Console.WriteLine(); + + // Search hotels by their characteristics, such as rating and parking availability. + Console.WriteLine("Search for hotels with a 9 or higher rating and parking."); + Console.WriteLine("==============================================================================="); + IAsyncEnumerable hotelsByFilter2 = collection.GetAsync(r => r.Rating >= 9 && r.HasParking == true, 3); + await foreach (Hotel hotel in hotelsByFilter2) + { + Output(hotel); + } + Console.WriteLine(); + + // Search hotels by their names. Return top three most similar matches. + // Provide a search term, such as "beach". Generate a vector embedding using the search term. + // ODP.NET performs a similarity search using the hotel name and search term embeddings. + // The cosine similarity metric is used to calculate vector distances to find the best matches. + // Scores closer to zero are more similar. Higher scores mean more dissimilarity. + // Results are ranked from most similar to least. + string hotelNameSearchStr = "beach"; + + var nameEmbedding = await GenerateEmbeddingAsync(ds, hotelNameSearchStr); + // Specify the search option for hotel name. + VectorSearchOptions nameOptions = new() { VectorProperty = r => r.NameEmbedding }; + IAsyncEnumerable> namesVectorSearch = collection.SearchAsync(nameEmbedding, top: 3, nameOptions); + + int rank = 1; + Console.WriteLine($"Hotel name similarity search with \"{hotelNameSearchStr}\"."); + Console.WriteLine("==============================================================================="); + await foreach (VectorSearchResult searchResult in namesVectorSearch) + { + Console.WriteLine(rank + $". {searchResult.Record.HotelName}"); + Console.WriteLine($"Score : {searchResult.Score}"); + Console.WriteLine(); + rank++; + } + Console.WriteLine(); + + // Search hotels using their descriptions. Return top three most similar matches. + // Provide a search phrase or sentence. Generate its vector embedding. + // ODP.NET performs a similarity search using the hotel description and search text embeddings. + // The Euclidean distance metric is used to calculate vector distances to find the best matches. + string descriptionSearchStr = "I want a hotel with nature activities."; + var descriptionEmbedding = await GenerateEmbeddingAsync(ds, descriptionSearchStr); + + // Specify the search option for hotel description. + VectorSearchOptions descriptionOptions = new() { VectorProperty = r => r.DescriptionEmbedding }; + IAsyncEnumerable> descriptionVectorSearch = collection.SearchAsync(descriptionEmbedding, top: 3, descriptionOptions); + + rank = 1; + Console.WriteLine($"Hotel description similarity search with \"{descriptionSearchStr}\"."); + Console.WriteLine("==============================================================================="); + await foreach (VectorSearchResult searchResult in descriptionVectorSearch) + { + Console.WriteLine(rank + $". {searchResult.Record.HotelName}"); + Console.WriteLine($"Score : {searchResult.Score}"); + Console.WriteLine($"Description: {searchResult.Record.Description}"); + Console.WriteLine(); + rank++; + } + } + + finally + { + // Clean up and delete the collection + if (vs != null) { await vs.EnsureCollectionDeletedAsync(collectionName); } + ds?.Dispose(); + vs?.Dispose(); + collection?.Dispose(); + } + } + + // Generate embeddings in ONNX format. + // This app uses Hugging Face's all-MiniLM-L12-v2 model for all its embeddings. + static async Task GenerateEmbeddingAsync(OracleDataSource ds, string searchText, CancellationToken cancellationtoken = default) + { + using (OracleConnection conn = await ds.OpenConnectionAsync(cancellationtoken)) + { + using (OracleCommand cmd = new OracleCommand($"SELECT TO_VECTOR(VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :1 as DATA), 384, FLOAT32)", conn)) + { + cmd.Parameters.Add("searchStr", OracleDbType.Varchar2, null, System.Data.ParameterDirection.Input); + cmd.Parameters[0].Value = searchText; + return (float[])cmd.ExecuteScalar(); + } + } + } + + // Output the hotel's information to the console. + static void Output(Hotel hotel) + { + Console.WriteLine($"Hotel Name = {hotel.HotelName}"); + Console.WriteLine($"Hotel Id = {hotel.HotelId}"); + Console.WriteLine($"Rating = {hotel.Rating}"); + Console.WriteLine($"HasParking = {hotel.HasParking}"); + Console.WriteLine(); + } +} +/* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ \ No newline at end of file diff --git a/samples/ai-vector/AppSettings.json b/samples/ai-vector/AppSettings.json new file mode 100644 index 00000000..aef4274b --- /dev/null +++ b/samples/ai-vector/AppSettings.json @@ -0,0 +1,5 @@ +{ + "Oracle": { + "ConnectionString": "User Id=ADMIN; Password=; Data Source=;" + } +} \ No newline at end of file diff --git a/samples/ai-vector/Hotel.cs b/samples/ai-vector/Hotel.cs new file mode 100644 index 00000000..1ff94d3a --- /dev/null +++ b/samples/ai-vector/Hotel.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.VectorData; + +namespace OracleAIVectorData +{ + public class Hotel + { + [VectorStoreKey] + public int HotelId { get; set; } + + [VectorStoreData] + public string HotelName { get; set; } + + [VectorStoreData] + public float Rating { get; set; } + + [VectorStoreData] + public bool HasParking { get; set; } + + [VectorStoreData] + public string Description { get; set; } + + //Oracle has numerous vector distance functions to identify the most relevant results. + //Let's use cosine similarity for the hotel name vectors. + [VectorStoreVector(Dimensions: 384, DistanceFunction = DistanceFunction.CosineDistance)] + public float[] NameEmbedding { get; set; } + + //Let's use Euclidean distance for the hotel description vectors. + [VectorStoreVector(Dimensions: 384, DistanceFunction = DistanceFunction.EuclideanDistance)] + public float[] DescriptionEmbedding { get; set; } + } +} +/* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ \ No newline at end of file diff --git a/samples/ai-vector/Hotels.json b/samples/ai-vector/Hotels.json new file mode 100644 index 00000000..452610df --- /dev/null +++ b/samples/ai-vector/Hotels.json @@ -0,0 +1,142 @@ +[ + { + "HotelName": "The Lodge by the Sea", + "Description": "A legendary beach hotel with beautiful sunsets, outdoor activites, and near top restaurants.", + "HotelId": 1, + "Rating": 9.5, + "HasParking": true + }, + { + "HotelName": "Hotel New York", + "Description": "An urban retreat in the heart of New York City known for its skyline views and well-appointed rooms.", + "HotelId": 2, + "Rating": 8, + "HasParking": false + }, + { + "HotelName": "Maui Island Retreat", + "Description": "A luxury Maui beach resort with environmentally sustainable design and privacy.", + "HotelId": 3, + "Rating": 3, + "HasParking": true + }, + { + "HotelName": "Hotel San Francisco", + "Description": "A historic hotel in the middle of downtown with modern elegance and legendary service.", + "HotelId": 4, + "Rating": 7.9, + "HasParking": false + }, + { + "HotelName": "The Mountain Lake Resort", + "Description": "A wellness retreat set in the Swiss Alps, offering spa, yoga, and cross-country skiing in tranquil surroundings.", + "HotelId": 5, + "Rating": 7, + "HasParking": true + }, + { + "HotelName": "The Standard", + "Description": "Palace-level luxury in London, famed for gourmet dining and the epitome of service.floral lobbies.", + "HotelId": 6, + "Rating": 6.2, + "HasParking": false + }, + { + "HotelName": "Desert Dream", + "Description": "An oasis of luxury for relaxation and fine dining.", + "HotelId": 7, + "Rating": 8, + "HasParking": true + }, + { + "HotelName": "The Cottages by the Lake", + "Description": "Countryside luxury, blending warm design, personal attention, and water activites.", + "HotelId": 8, + "Rating": 5, + "HasParking": false + }, + { + "HotelName": "The Paris", + "Description": "Historic hotel emblematic of French refinement, dining, and service.", + "HotelId": 9, + "Rating": 3.8, + "HasParking": true + }, + { + "HotelName": "Marina Hotel", + "Description": "A sanctuary for sailing enthusiasts on the beach.", + "HotelId": 10, + "Rating": 6, + "HasParking": false + }, + { + "HotelName": "The Grand Hotel", + "Description": "Iconic Tokyo landmark offering classic luxury and a celebrated history next to the city business center.", + "HotelId": 11, + "Rating": 10, + "HasParking": true + }, + { + "HotelName": "The Cabins", + "Description": "Located at the edge of a large forest. Perfect for hiking and exploring.", + "HotelId": 12, + "Rating": 5.5, + "HasParking": false + }, + { + "HotelName": "Botique Hotel", + "Description": "A chateau-hotel in wine country surrounded by the serene beauty of the nature.", + "HotelId": 13, + "Rating": 2, + "HasParking": true + }, + { + "HotelName": "Business Hotel", + "Description": "For the executive wanting to maximize productivity with minimal distractions.", + "HotelId": 14, + "Rating": 4.9, + "HasParking": false + }, + { + "HotelName": "The Country Motel", + "Description": "An affordable hotel in the outskirts of town with basic services and nearby trails.", + "HotelId": 15, + "Rating": 1, + "HasParking": true + }, + { + "HotelName": "The HK", + "Description": "Iconic urban hotel in Hong Kong with harbor views and banquet dining.", + "HotelId": 16, + "Rating": 8.1, + "HasParking": false + }, + { + "HotelName": "The Nature Inn", + "Description": "Located by a river in the hills, this hotel offers rooms with private terraces and stunning views.", + "HotelId": 17, + "Rating": 2.5, + "HasParking": true + }, + { + "HotelName": "Casino Hotel", + "Description": "Elegance, renowed entertainment, and the largest choice of games on the Las Vegas strip.", + "HotelId": 18, + "Rating": 7.3, + "HasParking": false + }, + { + "HotelName": "The Island Hotel", + "Description": "Located in the South Pacific, this luxurious resort has beach and water activities.", + "HotelId": 19, + "Rating": 4, + "HasParking": true + }, + { + "HotelName": "The Healthy Inn", + "Description": "A renowned wellness resort in California, offering holistic health programs and yoga.", + "HotelId": 20, + "Rating": 3.2, + "HasParking": false + } +] \ No newline at end of file diff --git a/samples/ai-vector/Load-model.sql b/samples/ai-vector/Load-model.sql new file mode 100644 index 00000000..d24463eb --- /dev/null +++ b/samples/ai-vector/Load-model.sql @@ -0,0 +1,56 @@ +DECLARE + ONNX_MOD_FILE VARCHAR2(100) := 'all_MiniLM_L12_v2.onnx'; + MODNAME VARCHAR2(500); + LOCATION_URI VARCHAR2(200) := 'https://adwc4pm.objectstorage.us-ashburn-1.oci.customer-oci.com/p/eLddQappgBJ7jNi6Guz9m9LOtYe2u8LWY19GfgU8flFK4N9YgP4kTlrE9Px3pE12/n/adwc4pm/b/OML-Resources/o/'; + +BEGIN + DBMS_OUTPUT.PUT_LINE('ONNX model file name in object storage is: '||ONNX_MOD_FILE); +-------------------------------------------- +-- Define a model name for the loaded model +-------------------------------------------- + SELECT UPPER(REGEXP_SUBSTR(ONNX_MOD_FILE, '[^.]+')) INTO MODNAME from dual; + DBMS_OUTPUT.PUT_LINE('Model will be loaded and saved with name: '||MODNAME); + +----------------------------------------------------- +-- Read the ONNX model file from object storage into +-- the Autonomous Database data pump directory +----------------------------------------------------- + +BEGIN DBMS_DATA_MINING.DROP_MODEL(model_name => MODNAME); +EXCEPTION WHEN OTHERS THEN NULL; END; + + DBMS_CLOUD.GET_OBJECT( + credential_name => NULL, + directory_name => 'DATA_PUMP_DIR', + object_uri => LOCATION_URI||ONNX_MOD_FILE); + +----------------------------------------- +-- Load the ONNX model to the database +----------------------------------------- + + DBMS_VECTOR.LOAD_ONNX_MODEL( + directory => 'DATA_PUMP_DIR', + file_name => ONNX_MOD_FILE, + model_name => MODNAME); + + DBMS_OUTPUT.PUT_LINE('New model successfully loaded with name: '||MODNAME); +END; + +/* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ \ No newline at end of file diff --git a/samples/ai-vector/Oracle AI Vector Data.csproj b/samples/ai-vector/Oracle AI Vector Data.csproj new file mode 100644 index 00000000..b5da0f8c --- /dev/null +++ b/samples/ai-vector/Oracle AI Vector Data.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + diff --git a/samples/array-bind/ArrayBind.cs b/samples/array-bind/ArrayBind.cs index 7508dcbb..e2eca90f 100644 --- a/samples/array-bind/ArrayBind.cs +++ b/samples/array-bind/ArrayBind.cs @@ -2,116 +2,114 @@ using System.Data; using Oracle.ManagedDataAccess.Client; -namespace ODPSample +namespace ODPArrayBind { - /// - /// Sample: Demonstrates array binding - /// - class ArrayBind - { - static void Main(string[] args) + /// + /// Sample: Demonstrates ODP.NET array binding + /// + class ArrayBind { - // Connect - // This sample code's DEPT table shares the same characteristics as SCOTT schema's DEPT table - string connectStr = "User Id=;Password=;Data Source="; - - // Setup the Tables for sample - Setup(connectStr); - - // Initialize array of data - int[] myArrayDeptNo = new int[3]{1, 2, 3}; - String[] myArrayDeptName = {"Dev", "QA", "Facility"}; - String[] myArrayDeptLoc = {"New York", "Maryland", "Texas"}; - - OracleConnection connection = new OracleConnection(connectStr); - OracleCommand command = new OracleCommand ( - "insert into dept values (:deptno, :deptname, :loc)", connection); - - // Set the Array Size to 3. This applied to all the parameter in - // associated with this command - command.ArrayBindCount = 3; - - // deptno parameter - OracleParameter deptNoParam = new OracleParameter("deptno",OracleDbType.Int32); - deptNoParam.Direction = ParameterDirection.Input; - deptNoParam.Value = myArrayDeptNo; - command.Parameters.Add(deptNoParam); - - // deptname parameter - OracleParameter deptNameParam = new OracleParameter("deptname", OracleDbType.Varchar2); - deptNameParam.Direction = ParameterDirection.Input; - deptNameParam.Value = myArrayDeptName; - command.Parameters.Add(deptNameParam); - - // loc parameter - OracleParameter deptLocParam = new OracleParameter("loc", OracleDbType.Varchar2); - deptLocParam.Direction = ParameterDirection.Input; - deptLocParam.Value = myArrayDeptLoc; - command.Parameters.Add(deptLocParam); - - try - { - connection.Open(); - command.ExecuteNonQuery(); - Console.WriteLine("{0} Rows Inserted", command.ArrayBindCount); - } - catch (Exception e) - { - Console.WriteLine("Execution Failed:" + e.Message); - } - finally - { - // connection, command used server side resource, dispose them - // asap to conserve resource - connection.Close(); - command.Dispose(); - connection.Dispose(); - } - } + static void Main(string[] args) + { + // Connect + // This sample code's DEPT table shares the same characteristics as the SCOTT schema's DEPT table. + string connectStr = "User Id=;Password=;Data Source="; - public static void Setup(string connectStr) - { - int[] myArrayDeptNo = new int[3]{1, 2, 3}; - - OracleConnection conn = new OracleConnection(connectStr); - OracleCommand cmd = new OracleCommand("delete dept where deptno = :1", conn); - - // Bind with an array of 3 items - cmd.ArrayBindCount = 3; - - OracleParameter param1 = new OracleParameter(); - param1.OracleDbType = OracleDbType.Int32; - param1.Value = myArrayDeptNo; - - cmd.Parameters.Add(param1); - - try - { - conn.Open(); - cmd.ExecuteNonQuery(); - } - catch (Exception e) - { - Console.WriteLine("Setup Failed:{0}" ,e.Message); - } - finally - { - conn.Close(); - cmd.Dispose(); - } + // Clear rows from past sample code executions + Setup(connectStr); + + // Initialize array of data + int[] myArrayDeptNo = new int[4] { 1, 2, 3, 4 }; + String[] myArrayDeptName = { "Dev", "QA", "PM", "Integration" }; + String[] myArrayDeptLoc = { "California", "Arizona", "Texas", "Oregon" }; + + OracleConnection connection = new OracleConnection(connectStr); + OracleCommand command = new OracleCommand( + "insert into dept values (:deptno, :deptname, :loc)", connection); + + // Set the array size to 4. This applies to all the command's associated parameters. + command.ArrayBindCount = 4; + + // Deptno parameter + OracleParameter deptNoParam = new OracleParameter("deptno", OracleDbType.Int32); + deptNoParam.Direction = ParameterDirection.Input; + deptNoParam.Value = myArrayDeptNo; + command.Parameters.Add(deptNoParam); + + // Deptname parameter + OracleParameter deptNameParam = new OracleParameter("deptname", OracleDbType.Varchar2); + deptNameParam.Direction = ParameterDirection.Input; + deptNameParam.Value = myArrayDeptName; + command.Parameters.Add(deptNameParam); + + // Loc parameter + OracleParameter deptLocParam = new OracleParameter("loc", OracleDbType.Varchar2); + deptLocParam.Direction = ParameterDirection.Input; + deptLocParam.Value = myArrayDeptLoc; + command.Parameters.Add(deptLocParam); + + try + { + connection.Open(); + command.ExecuteNonQuery(); + Console.WriteLine("{0} rows inserted", command.ArrayBindCount); + } + catch (Exception e) + { + Console.WriteLine("Execution failed:" + e.Message); + } + finally + { + // Dispose connection and command used server side resource + connection.Close(); + command.Dispose(); + connection.Dispose(); + } + } + + public static void Setup(string connectStr) + { + int[] myArrayDeptNo = new int[4] { 1, 2, 3, 4 }; + + OracleConnection conn = new OracleConnection(connectStr); + OracleCommand cmd = new OracleCommand("delete dept where deptno = :1", conn); + + // Bind with a 4 item array + cmd.ArrayBindCount = 4; + + OracleParameter param1 = new OracleParameter(); + param1.OracleDbType = OracleDbType.Int32; + param1.Value = myArrayDeptNo; + + cmd.Parameters.Add(param1); + + try + { + conn.Open(); + cmd.ExecuteNonQuery(); + } + catch (Exception e) + { + Console.WriteLine("Setup Failed:{0}", e.Message); + } + finally + { + conn.Close(); + cmd.Dispose(); + } + } } - } } -/* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. */ - +/* Copyright (c) 2017, 2024 Oracle and/or its affiliates. All rights reserved. */ + /****************************************************************************** * * You may not use the identified files except in compliance with The MIT * License (the "License.") * * You may obtain a copy of the License at - * https://github.com/oracle/Oracle.NET/blob/master/LICENSE + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/samples/async/async.cs b/samples/async/async.cs new file mode 100644 index 00000000..33437f11 --- /dev/null +++ b/samples/async/async.cs @@ -0,0 +1,70 @@ +using Oracle.ManagedDataAccess.Client; +using System.Threading.Tasks; +using System.Threading; +using System; + +// This app uses asynchronous ODP.NET (managed or core) APIs to open a connection, +// execute a SQL statement, and read the results. It times how long these operations take. +// To run this app, add your database's HR schema User Id, Password, and Data Source values +// with ODP.NET 23ai or higher connecting to an Oracle Database 19c or higher. + +class ODPNET_Async +{ + public static async Task Main() + { + // Add password and data source to connect to your Oracle database + string conString = "User Id=hr;Password=;Data Source=;"; + + using (OracleConnection con = new OracleConnection(conString)) + { + //Time how long it takes to open a connection asynchronously + DateTime start_time = DateTime.Now; + Task task = con.OpenAsync(); + DateTime end_time_open = DateTime.Now; + + // Simulate operation that takes one second + Thread.Sleep(1000); + + string cmdText = "SELECT * FROM EMPLOYEES FETCH FIRST 100 ROWS ONLY"; + using (OracleCommand cmd = new OracleCommand(cmdText, con)) + { + // Retrieve open connection + await task; + using (OracleDataReader reader = await cmd.ExecuteReaderAsync()) + { + await reader.ReadAsync(); + } + } + DateTime end_time_all = DateTime.Now; + + // Calculate connection open time + TimeSpan ts_open = end_time_open - start_time; + double ts_open1 = Math.Round(ts_open.TotalSeconds, 2); + Console.WriteLine("Asynchronous connection open time: " + ts_open1 + " seconds"); + + // Calculate overall ODP.NET operation time + TimeSpan ts_all = end_time_all - start_time; + double ts_all1 = Math.Round(ts_all.TotalSeconds, 2); + Console.WriteLine("Asynchronous ODP.NET overall time: " + ts_all1 + " seconds"); + } + } +} + +/* Copyright (c) 2023, 2024 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ diff --git a/samples/async/sync.cs b/samples/async/sync.cs new file mode 100644 index 00000000..165ba0f5 --- /dev/null +++ b/samples/async/sync.cs @@ -0,0 +1,69 @@ +using Oracle.ManagedDataAccess.Client; +using System.Threading.Tasks; +using System.Threading; +using System; + +// This app uses synchronous ODP.NET (managed or core) APIs to open a connection, +// execute a SQL statement, and read the results. It times how long these operations take. +// To run this app, add your database's HR schema User Id, Password, and Data Source values +// with ODP.NET 23ai or higher connecting to an Oracle Database 19c or higher. + + +class ODPNET_Sync +{ + static void Main() + { + // Add password and data source to connect to your Oracle database + string conString = "User Id=hr;Password=;Data Source=;"; + + using (OracleConnection con = new OracleConnection(conString)) + { + //Time how long it takes to open a connection + DateTime start_time = DateTime.Now; + con.Open(); + DateTime end_time_open = DateTime.Now; + + // Simulate operation that takes one second + Thread.Sleep(1000); + + string cmdText = "SELECT * FROM EMPLOYEES FETCH FIRST 100 ROWS ONLY"; + using (OracleCommand cmd = new OracleCommand(cmdText, con)) + { + using (OracleDataReader reader = cmd.ExecuteReader()) + { + reader.Read(); + } + } + DateTime end_time_all = DateTime.Now; + + // Calculate connection open time + TimeSpan ts_open = end_time_open - start_time; + double ts_open1 = Math.Round(ts_open.TotalSeconds, 2); + Console.WriteLine("Synchronous connection open time: " + ts_open1 + " seconds"); + + // Calculate overall ODP.NET operation time + TimeSpan ts_all = end_time_all - start_time; + double ts_all1 = Math.Round(ts_all.TotalSeconds, 2); + Console.WriteLine("Synchronous ODP.NET overall time: " + ts_all1 + " seconds"); + } + } +} + +/* Copyright (c) 2023, 2024 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ diff --git a/samples/azure-active-directory/README.md b/samples/azure-active-directory/README.md index 75d36ba5..8fb68474 100644 --- a/samples/azure-active-directory/README.md +++ b/samples/azure-active-directory/README.md @@ -1,8 +1,16 @@ -These ODP.NET sample code files show how to connect to an Oracle Autonomous Database using a token obtained from Azure Active Directory. There is one sample for each ODP.NET provider type: Core, Managed, and Unmanaged. +# Microsoft Entra ID/Azure Active Directory ODP.NET Sample Code +These ODP.NET sample code files show how to connect to an Oracle database using a token obtained from Microsoft Entra ID, also known as Azure Active Directory (AD), or with Azure AD single sign-on. -In each sample, provide the following values to test with your Azure AD tokens: +In the main directory, there is one sample for each ODP.NET provider type (core, managed, and unmanaged). The samples shows Oracle access token management and Azure AD authentication. They require the following to test with an Azure AD token: * Data Source * Azure AD app registration client identifier * Azure AD tenant identifier -* Azure AD app registrationsecret value +* Azure AD app registration secret value * Azure AD database registration scope + +In the SSO sub-directory, this sample shows how to use ODP.NET Azure AD SSO with service principal authentication. It can use either ODP.NET Core or managed ODP.NET and requires the following to test with: +* Data Source +* Azure AD app registration client identifier +* Azure AD tenant identifier +* Azure AD app registration secret value +* Azure AD protected resource identifier diff --git a/samples/azure-active-directory/sso/azure-ad-sso-service-principal.cs b/samples/azure-active-directory/sso/azure-ad-sso-service-principal.cs new file mode 100644 index 00000000..cc1b1ae6 --- /dev/null +++ b/samples/azure-active-directory/sso/azure-ad-sso-service-principal.cs @@ -0,0 +1,71 @@ +//This application demonstrates connecting .NET to Oracle database using Microsoft Entra ID/Azure Active +// Directory single sign-on (SSO). It uses service principal authentication with either managed ODP.NET or +// ODP.NET Core 23c or higher. + +// ODP.NET Azure AD SSO requires Oracle.ManagedDataAccess.Azure package from NuGet Gallery +using Oracle.ManagedDataAccess.Azure; +using Oracle.ManagedDataAccess.Client; +using System.Security; + +//Set your Azure Active Directory parameters below and ODP.NET data source value +string clientId = ""; +string tenantId = ""; +string clientSecret = ""; +string dbAppIdUri = ""; +var conn = new OracleConnection("User Id=/;Data Source=;Connection Timeout=900;"); + +var secureSecret = new SecureString(); +foreach (char c in clientSecret) +{ + secureSecret.AppendChar(c); +} +secureSecret.MakeReadOnly(); + +//Create Azure authentication token object and set its values. +var tokenConfig = new AzureTokenAuthentication +{ + ClientId = clientId, + TenantId = tenantId, + ClientSecret = secureSecret, + DatabaseApplicationIdUri = dbAppIdUri, +}; + +//Set token authentication mode to Azure Service Principal and use Azure token authentication +conn.TokenAuthentication = OracleTokenAuth.AzureServicePrincipal; +conn.UseAzureTokenAuthentication(tokenConfig); + +try +{ + conn.Open(); + Console.WriteLine("Connection opened successfully!"); + using (OracleCommand cmd = conn.CreateCommand()) + { + //Retrieve authenticated identity value from database + cmd.CommandText = "SELECT SYS_CONTEXT('USERENV', 'AUTHENTICATED_IDENTITY') FROM DUAL"; + Console.WriteLine($"Authenticated identity: {cmd.ExecuteScalar().ToString()}"); + } +} +catch (Exception ex) +{ + Console.WriteLine("Error: " + ex.Message); +} +conn.Dispose(); + +/* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/dotnet-db-samples/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ diff --git a/samples/ef-core/get-started/README.md b/samples/ef-core/get-started/README.md index 67f4ca6f..ffc6095e 100644 --- a/samples/ef-core/get-started/README.md +++ b/samples/ef-core/get-started/README.md @@ -1,7 +1,7 @@ # Getting Started with Oracle Entity Framework Core There are two sets of sample code showing how to perform several basic tasks with Oracle EF Core. -1. [Getting Started with Oracle EF Core 6 and 7](https://github.com/oracle/dotnet-db-samples/blob/master/samples/ef-core/get-started/create-model-save-query-scaffold-efc6.cs) +1. [Getting Started with Oracle EF Core 6 and higher](https://github.com/oracle/dotnet-db-samples/blob/master/samples/ef-core/get-started/create-model-save-query-scaffold-efc.cs) 2. [Getting Started with Oracle EF Core 5 and earlier releases](https://github.com/oracle/dotnet-db-samples/blob/master/samples/ef-core/get-started/create-model-save-query-scaffold.cs) The sample code and below instructions show how to create a data model, save it to an Oracle database, insert data, query the data, use migrations to modify the schema, and reverse engineer. diff --git a/samples/ef-core/get-started/create-model-save-query-scaffold-efc6.cs b/samples/ef-core/get-started/create-model-save-query-scaffold-efc.cs similarity index 72% rename from samples/ef-core/get-started/create-model-save-query-scaffold-efc6.cs rename to samples/ef-core/get-started/create-model-save-query-scaffold-efc.cs index d119df96..495d92c2 100644 --- a/samples/ef-core/get-started/create-model-save-query-scaffold-efc6.cs +++ b/samples/ef-core/get-started/create-model-save-query-scaffold-efc.cs @@ -1,10 +1,10 @@ using Microsoft.EntityFrameworkCore; -namespace OracleEFCore6 +namespace OracleEFCore { class Program { - //Demonstrates how to get started using Oracle Entity Framework Core 6 + //Demonstrates how to get started using Oracle Entity Framework Core 6 and higher //Code connects to on-premises Oracle DB or walletless Oracle Autonomous DB public class BloggingContext : DbContext @@ -61,20 +61,21 @@ static void Main(string[] args) } } -/* Copyright (c) 2018, 2022 Oracle and/or its affiliates. All rights reserved. */ -/* Copyright (c) .NET Foundation and Contributors */ +/* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/dotnet-db-samples/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * *****************************************************************************/ diff --git a/samples/ef-core/json/json-columns.cs b/samples/ef-core/json/json-columns.cs new file mode 100644 index 00000000..7f0434f8 --- /dev/null +++ b/samples/ef-core/json/json-columns.cs @@ -0,0 +1,171 @@ +//This sample shows how to use EF Core JSON columns with ODP.NET and Oracle Database. +//It creates an owned entity, inserts, queries, updates, and deletes JSON column data. +//It requires Oracle EF Core 8 or higher. Oracle Database 21c and higher supports JSON columns. +//Earlier database versions map aggregate types to NCLOB columns instead of JSON columns. + +//Specify the user, password, and data source in the connection string below. + +using Microsoft.EntityFrameworkCore; + +namespace ODPJsonColumns +{ + class Program + { + public class ContactDetails + { + public Address Address { get; set; } = null!; + public string? Phone { get; set; } + } + + public class Address + { + public Address(string street, string city, string postcode, string country) + { + Street = street; + City = city; + Postcode = postcode; + Country = country; + } + + public string Street { get; set; } + public string City { get; set; } + public string Postcode { get; set; } + public string Country { get; set; } + } + + public class Author + { + public int Id { get; set; } + public string? Name { get; set; } + public ContactDetails? Contact { get; set; } + } + + public class AuthorContext : DbContext + { + public DbSet Authors { get; set; } + + //To use Oracle database JSON columns, connect to Oracle Database 21c or higher version and + // specify OracleSQLCompatibility to DatabaseVersion21 or higher. + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseOracle("User Id=; Password=; Data Source=" + , b => b.UseOracleSQLCompatibility(OracleSQLCompatibility.DatabaseVersion21)); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // Configuring the Contact column as a JSON column + modelBuilder.Entity().OwnsOne( + author => author.Contact, ownedNavigationBuilder => + { + ownedNavigationBuilder.ToJson(); + ownedNavigationBuilder.OwnsOne(contactDetails => contactDetails.Address); + }); + } + } + + public static void Main() + { + using (var context = new AuthorContext()) + { + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + + var author1 = new Author() + { + Name = "John Smith", + Contact = new ContactDetails() + { + Phone = "555 123 4567", + Address = new Address("1 Any Street", "Austin", "78741", "US") + } + }; + + var author2 = new Author() + { + Name = "Kim Jones", + Contact = new ContactDetails() + { + Phone = "555 234 5678", + Address = new Address("2 Any Street", "Redwood Shores", "94065", "US") + } + }; + + /* + * Contact inserted into the JSON column as the following example: + * + * { + * "Phone": "555 123 4567", + * "Address": { + * "City": "Austin", + * "Country": "US", + * "Postcode": "78741", + * "Street": "1 Any Street" + * } + * } + */ + + // Insert data + Console.WriteLine("Inserting authors into table\n"); + context.Authors.Add(author1); + context.Authors.Add(author2); + context.SaveChanges(); + + // Query to verify insert + Console.WriteLine("Authors in table: "); + var result = context.Authors.ToList(); + foreach (var author in result) + { + Console.WriteLine($"{author.Name}"); + } + Console.WriteLine(); + + // Query using owned entity details from JSON column + var result1 = context.Authors.Where(author => author.Contact.Address.City == "Austin").First(); + Console.WriteLine($"Author residing in Austin: {result1.Name}\n"); + + // Update second author's phone number details + Console.WriteLine("Updating second author's phone number"); + author2.Contact.Phone = "123 456 7890"; + context.SaveChanges(); + + // Query to verify update + var result2 = context.Authors.Where(author => author.Name == "Kim Jones").First(); + Console.WriteLine($"Retrieve author's new phone number {result2.Name}: {result2.Contact.Phone}\n"); + + // Delete second author + Console.WriteLine("Deleting author with street address '2 Any Street' from table"); + context.Remove(context.Authors.Single(author => author.Contact.Address.Street == "2 Any Street")); + context.SaveChanges(); + Console.WriteLine("Delete complete.\n"); + + // Query to verify delete + Console.WriteLine("Authors remaining in table: "); + var result3 = context.Authors.ToList(); + foreach(var author in result3) + { + Console.WriteLine($"{author.Name}"); + } + } + } + } +} + +/* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/dotnet-db-samples/blob/master/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ diff --git a/samples/ef-core/t4-templates/README.md b/samples/ef-core/t4-templates/README.md new file mode 100644 index 00000000..6acda932 --- /dev/null +++ b/samples/ef-core/t4-templates/README.md @@ -0,0 +1,8 @@ +# Customize Oracle EF Core Data Type Mappings with T4 Text Templates + +Oracle EF Core data type mapping between entity properties and database columns can be customized with T4 text templates. This repository includes sample T4 templates that can be used as is or customized with an alternative set of .NET data type mappings. The samples demonstrate the following mapping scenarios: + +* All Numeric Types - Customizes all database numeric column type mappings to .NET properties +* Single Numeric Type - Customizes one database column type mapping to a specific .NET property + +The [ODP.NET Scaffolding documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/odpnt/EFCoreREDataTypeMapping.html) provides more information and a step-by-step usage guide. diff --git a/samples/ef-core/t4-templates/all-numeric-types/EntityType.t4 b/samples/ef-core/t4-templates/all-numeric-types/EntityType.t4 new file mode 100644 index 00000000..ac84227c --- /dev/null +++ b/samples/ef-core/t4-templates/all-numeric-types/EntityType.t4 @@ -0,0 +1,209 @@ +<# // Sample Oracle T4 template to customize mapping all database numeric column types to .NET properties. The mapped .NET properties store a superset of values of their mapped database types. #> + +<#@ template hostSpecific="true" #> +<#@ assembly name="Microsoft.EntityFrameworkCore" #> +<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #> +<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #> +<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #> +<#@ parameter name="EntityType" type="Microsoft.EntityFrameworkCore.Metadata.IEntityType" #> +<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #> +<#@ parameter name="NamespaceHint" type="System.String" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.ComponentModel.DataAnnotations" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="Microsoft.EntityFrameworkCore" #> +<#@ import namespace="Microsoft.EntityFrameworkCore.Design" #> +<#@ import namespace="Microsoft.Extensions.DependencyInjection" #> +<# + if (EntityType.IsSimpleManyToManyJoinEntityType()) + { + // Don't scaffold these + return ""; + } + + var services = (IServiceProvider)Host; + var annotationCodeGenerator = services.GetRequiredService(); + var code = services.GetRequiredService(); + + var usings = new List + { + "System", + "System.Collections.Generic" + }; + + if (Options.UseDataAnnotations) + { + usings.Add("System.ComponentModel.DataAnnotations"); + usings.Add("System.ComponentModel.DataAnnotations.Schema"); + usings.Add("Microsoft.EntityFrameworkCore"); + } + + if (!string.IsNullOrEmpty(NamespaceHint)) + { +#> +namespace <#= NamespaceHint #>; + +<# + } + + if (!string.IsNullOrEmpty(EntityType.GetComment())) + { +#> +/// +/// <#= code.XmlComment(EntityType.GetComment()) #> +/// +<# + } + + if (Options.UseDataAnnotations) + { + foreach (var dataAnnotation in EntityType.GetDataAnnotations(annotationCodeGenerator)) + { +#> +<#= code.Fragment(dataAnnotation) #> +<# + } + } +#> +public partial class <#= EntityType.Name #> +{ +<# + var firstProperty = true; + foreach (var property in EntityType.GetProperties().OrderBy(p => p.GetColumnOrder() ?? -1)) + { + if (!firstProperty) + { + WriteLine(""); + } + + if (!string.IsNullOrEmpty(property.GetComment())) + { +#> + /// + /// <#= code.XmlComment(property.GetComment(), indent: 1) #> + /// +<# + } + + if (Options.UseDataAnnotations) + { + var dataAnnotations = property.GetDataAnnotations(annotationCodeGenerator) + .Where(a => !(a.Type == typeof(RequiredAttribute) && Options.UseNullableReferenceTypes && !property.ClrType.IsValueType)); + foreach (var dataAnnotation in dataAnnotations) + { +#> + <#= code.Fragment(dataAnnotation) #> +<# + } + } + + // Make changes here to customize type mapping for all properties of certain column types. + Type clrType; + string columnType = property.GetColumnType(); // Get the store type for which we want custom mapping. + + if (columnType == "NUMBER(1)") + { + clrType = Options.UseNullableReferenceTypes && property.IsNullable ? typeof(byte?) : typeof(byte); // Map NUMBER(1) to byte. + } + else if (columnType == "NUMBER(2)" || columnType == "NUMBER(3)" || columnType == "NUMBER(4)") + { + clrType = Options.UseNullableReferenceTypes && property.IsNullable ? typeof(Int16?) : typeof(Int16); // Map NUMBER(2) to NUMBER(4) to Int16. + } + else if (columnType == "NUMBER(5)") + { + clrType = Options.UseNullableReferenceTypes && property.IsNullable ? typeof(Int32?) : typeof(Int32); // Map NUMBER(5) to Int32. + } + else if (columnType == "NUMBER(6)" || columnType == "NUMBER(7)" || columnType == "NUMBER(8)" || + columnType == "NUMBER(9)" || columnType == "NUMBER(10)") + { + clrType = Options.UseNullableReferenceTypes && property.IsNullable ? typeof(Int64?) : typeof(Int64); // Map NUMBER(6) to NUMBER(10) to Int64. + } + else if (columnType == "NUMBER(11)" || columnType == "NUMBER(12)" || columnType == "NUMBER(13)" || + columnType == "NUMBER(14)" || columnType == "NUMBER(15)" || columnType == "NUMBER(16)" || + columnType == "NUMBER(17)" || columnType == "NUMBER(18)" || columnType == "NUMBER(19)") + { + clrType = Options.UseNullableReferenceTypes && property.IsNullable ? typeof(Decimal?) : typeof(Decimal); // Map NUMBER(11) to NUMBER(19) to Decimal. + } + // Add more column types as required. + else + { + clrType = property.ClrType; // Keep the default CLR Type. + } + + + usings.AddRange(code.GetRequiredUsings(clrType)); + + var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType; + var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType; +#> + public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #> +<# + firstProperty = false; + } + + foreach (var navigation in EntityType.GetNavigations()) + { + WriteLine(""); + + if (Options.UseDataAnnotations) + { + foreach (var dataAnnotation in navigation.GetDataAnnotations(annotationCodeGenerator)) + { +#> + <#= code.Fragment(dataAnnotation) #> +<# + } + } + + var targetType = navigation.TargetEntityType.Name; + if (navigation.IsCollection) + { +#> + public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; set; } = new List<<#= targetType #>>(); +<# + } + else + { + var needsNullable = Options.UseNullableReferenceTypes && !(navigation.ForeignKey.IsRequired && navigation.IsOnDependent); + var needsInitializer = Options.UseNullableReferenceTypes && navigation.ForeignKey.IsRequired && navigation.IsOnDependent; +#> + public virtual <#= targetType #><#= needsNullable ? "?" : "" #> <#= navigation.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #> +<# + } + } + + foreach (var skipNavigation in EntityType.GetSkipNavigations()) + { + WriteLine(""); + + if (Options.UseDataAnnotations) + { + foreach (var dataAnnotation in skipNavigation.GetDataAnnotations(annotationCodeGenerator)) + { +#> + <#= code.Fragment(dataAnnotation) #> +<# + } + } +#> + public virtual ICollection<<#= skipNavigation.TargetEntityType.Name #>> <#= skipNavigation.Name #> { get; set; } = new List<<#= skipNavigation.TargetEntityType.Name #>>(); +<# + } +#> +} +<# + var previousOutput = GenerationEnvironment; + GenerationEnvironment = new StringBuilder(); + + foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer())) + { +#> +using <#= ns #>; +<# + } + + WriteLine(""); + + GenerationEnvironment.Append(previousOutput); +#> diff --git a/samples/ef-core/t4-templates/single-numeric-type/EntityType.t4 b/samples/ef-core/t4-templates/single-numeric-type/EntityType.t4 new file mode 100644 index 00000000..dc15c438 --- /dev/null +++ b/samples/ef-core/t4-templates/single-numeric-type/EntityType.t4 @@ -0,0 +1,189 @@ +<# // Sample Oracle T4 template to customize mapping one database column type to a specific .NET property with the name, Col1. The mapped .NET property, short, stores a superset of values of the mapped database type, NUMBER(1). #> + +<#@ template hostSpecific="true" #> +<#@ assembly name="Microsoft.EntityFrameworkCore" #> +<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #> +<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #> +<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #> +<#@ parameter name="EntityType" type="Microsoft.EntityFrameworkCore.Metadata.IEntityType" #> +<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #> +<#@ parameter name="NamespaceHint" type="System.String" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.ComponentModel.DataAnnotations" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="Microsoft.EntityFrameworkCore" #> +<#@ import namespace="Microsoft.EntityFrameworkCore.Design" #> +<#@ import namespace="Microsoft.Extensions.DependencyInjection" #> +<# + if (EntityType.IsSimpleManyToManyJoinEntityType()) + { + // Don't scaffold these + return ""; + } + + var services = (IServiceProvider)Host; + var annotationCodeGenerator = services.GetRequiredService(); + var code = services.GetRequiredService(); + + var usings = new List + { + "System", + "System.Collections.Generic" + }; + + if (Options.UseDataAnnotations) + { + usings.Add("System.ComponentModel.DataAnnotations"); + usings.Add("System.ComponentModel.DataAnnotations.Schema"); + usings.Add("Microsoft.EntityFrameworkCore"); + } + + if (!string.IsNullOrEmpty(NamespaceHint)) + { +#> +namespace <#= NamespaceHint #>; + +<# + } + + if (!string.IsNullOrEmpty(EntityType.GetComment())) + { +#> +/// +/// <#= code.XmlComment(EntityType.GetComment()) #> +/// +<# + } + + if (Options.UseDataAnnotations) + { + foreach (var dataAnnotation in EntityType.GetDataAnnotations(annotationCodeGenerator)) + { +#> +<#= code.Fragment(dataAnnotation) #> +<# + } + } +#> +public partial class <#= EntityType.Name #> +{ +<# + var firstProperty = true; + foreach (var property in EntityType.GetProperties().OrderBy(p => p.GetColumnOrder() ?? -1)) + { + if (!firstProperty) + { + WriteLine(""); + } + + if (!string.IsNullOrEmpty(property.GetComment())) + { +#> + /// + /// <#= code.XmlComment(property.GetComment(), indent: 1) #> + /// +<# + } + + if (Options.UseDataAnnotations) + { + var dataAnnotations = property.GetDataAnnotations(annotationCodeGenerator) + .Where(a => !(a.Type == typeof(RequiredAttribute) && Options.UseNullableReferenceTypes && !property.ClrType.IsValueType)); + foreach (var dataAnnotation in dataAnnotations) + { +#> + <#= code.Fragment(dataAnnotation) #> +<# + } + } + + // Make changes here to customize type mapping for specific properties. + Type clrType; + + if (property.Name == "Col1" // The name of the property that we want to customize the type-mapping for. + && property.GetColumnType() == "NUMBER(1)") // The store type of the property that we want to customize the type-mapping for. + { + clrType = Options.UseNullableReferenceTypes && property.IsNullable ? typeof(short?) : typeof(short); // Assign the custom CLR type we want. + } + else + { + clrType = property.ClrType; // Keep the default CLR Type. + } + + + usings.AddRange(code.GetRequiredUsings(clrType)); + + var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType; + var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType; +#> + public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #> +<# + firstProperty = false; + } + + foreach (var navigation in EntityType.GetNavigations()) + { + WriteLine(""); + + if (Options.UseDataAnnotations) + { + foreach (var dataAnnotation in navigation.GetDataAnnotations(annotationCodeGenerator)) + { +#> + <#= code.Fragment(dataAnnotation) #> +<# + } + } + + var targetType = navigation.TargetEntityType.Name; + if (navigation.IsCollection) + { +#> + public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; set; } = new List<<#= targetType #>>(); +<# + } + else + { + var needsNullable = Options.UseNullableReferenceTypes && !(navigation.ForeignKey.IsRequired && navigation.IsOnDependent); + var needsInitializer = Options.UseNullableReferenceTypes && navigation.ForeignKey.IsRequired && navigation.IsOnDependent; +#> + public virtual <#= targetType #><#= needsNullable ? "?" : "" #> <#= navigation.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #> +<# + } + } + + foreach (var skipNavigation in EntityType.GetSkipNavigations()) + { + WriteLine(""); + + if (Options.UseDataAnnotations) + { + foreach (var dataAnnotation in skipNavigation.GetDataAnnotations(annotationCodeGenerator)) + { +#> + <#= code.Fragment(dataAnnotation) #> +<# + } + } +#> + public virtual ICollection<<#= skipNavigation.TargetEntityType.Name #>> <#= skipNavigation.Name #> { get; set; } = new List<<#= skipNavigation.TargetEntityType.Name #>>(); +<# + } +#> +} +<# + var previousOutput = GenerationEnvironment; + GenerationEnvironment = new StringBuilder(); + + foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer())) + { +#> +using <#= ns #>; +<# + } + + WriteLine(""); + + GenerationEnvironment.Append(previousOutput); +#> diff --git a/samples/opentelemetry/opentelemetry.cs b/samples/opentelemetry/opentelemetry.cs new file mode 100644 index 00000000..92840299 --- /dev/null +++ b/samples/opentelemetry/opentelemetry.cs @@ -0,0 +1,85 @@ +// ODP.NET OpenTelemetry Demo +// This sample demonstrates using managed ODP.NET or ODP.NET Core with OpenTelemetry using the HR schema. +// To setup, add NuGet packages: Oracle.ManagedDataAccess.OpenTelemetry, OpenTelemetry, and an OpenTelemetry exporter. +// This sample is configured to use the Console Exporter (OpenTelemetry.Exporter.Console), but can be modified to another exporter. +// Provide the Oracle database password and data source information for the connection string. + +using System.Diagnostics; +using OpenTelemetry; // for Sdk +using OpenTelemetry.Trace; // for TracerProvider and TracerProviderBuilder +using Oracle.ManagedDataAccess.Client; // for ODP.NET +using Oracle.ManagedDataAccess.OpenTelemetry; // for ODP.NET OpenTelemetry + +class ODP_OTel_Demo +{ + static TracerProvider tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddOracleDataProviderInstrumentation(o => // ODP.NET OpenTelemetry extension method + { + o.EnableConnectionLevelAttributes = true; + o.RecordException = true; + o.InstrumentOracleDataReaderRead = true; + o.SetDbStatementForText = true; + }) + .AddSource("ODP.NET App") + .AddConsoleExporter() // OpenTelemetry.Exporter.Console NuGet package extension method + //.AddZipkinExporter() // OpenTelemetry.Exporter.Zipkin NuGet package extension method + .Build()!; + + static ActivitySource activitySource = new ActivitySource("ODP.NET App"); + + static string conString = @"User Id=hr;Password=;Data Source=;"; + + static void Main() + { + using (OracleConnection con = new OracleConnection(conString)) + { + using (OracleCommand cmd = con.CreateCommand()) + { + try + { + con.Open(); + cmd.CommandText = "select * from employees"; + + // Start OpenTelemetry activity + using (Activity activity = activitySource.StartActivity("Retrieve data")!) + { + OracleDataReader reader = cmd.ExecuteReader(); + while (reader.Read()) + { + // Use query results + } + reader.Dispose(); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + } + } +} + +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2015, 2023 Oracle +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: + +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + *****************************************************************************/ diff --git a/samples/pipelining/no-pipelining-and-sync.cs b/samples/pipelining/no-pipelining-and-sync.cs new file mode 100644 index 00000000..c80de923 --- /dev/null +++ b/samples/pipelining/no-pipelining-and-sync.cs @@ -0,0 +1,87 @@ +using Oracle.ManagedDataAccess.Client; +using System.Threading.Tasks; +using System.Threading; +using System; + +// This code sample demonstrates using synchronous ODP.NET (managed or core) without pipelining. +// It times opening a connection and several query executions. +// To run this app, add your database's HR schema User Id, Password, and Data Source values +// with ODP.NET 23ai or higher connecting to an Oracle Database 23ai or higher. + +class ODPNET_Sync_And_No_Pipelining +{ + static void Main() + { + // Add password and data source to connect to your Oracle database + string conString = "User Id=hr;Password=;Data Source=;"; + + using (OracleConnection con = new OracleConnection(conString)) + { + string cmdText1 = "SELECT * FROM EMPLOYEES"; + string cmdText2 = "SELECT * FROM DEPARTMENTS"; + string cmdText3 = "SELECT * FROM JOBS"; + + OracleCommand cmd1 = new OracleCommand(cmdText1, con); + OracleCommand cmd2 = new OracleCommand(cmdText2, con); + OracleCommand cmd3 = new OracleCommand(cmdText3, con); + + // Measure how long connection open takes + DateTime start_time = DateTime.Now; + con.Open(); + DateTime end_time_open = DateTime.Now; + + // Simulate an operation that takes one second + Thread.Sleep(1000); + + // Measure time for query executions + DateTime start_time_query = DateTime.Now; + cmd1.ExecuteNonQuery(); + cmd2.ExecuteNonQuery(); + cmd3.ExecuteNonQuery(); + + // Simulate an operation that takes one second + Thread.Sleep(1000); + + // Record time all the synchronous operations took plus the sleep time + DateTime end_time_all = DateTime.Now; + + // Calculate connection open time + TimeSpan ts_open = end_time_open - start_time; + double ts_open1 = Math.Round(ts_open.TotalSeconds, 2); + Console.WriteLine("Synchronous connection open time: " + ts_open1 + " seconds"); + + // Calculate SQL executions time + TimeSpan ts_sql = end_time_all - start_time_query; + double ts_sql1 = Math.Round(ts_sql.TotalSeconds, 2); + Console.WriteLine("Synchronous query execution time: " + ts_sql1 + " seconds"); + + // Calculate overall ODP.NET operation time + TimeSpan ts_all = end_time_all - start_time; + double ts_all1 = Math.Round(ts_all.TotalSeconds, 2); + Console.WriteLine("Synchronous operation total time: " + ts_all1 + " seconds"); + + cmd1.Dispose(); + cmd2.Dispose(); + cmd3.Dispose(); + } + } +} + +/* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ diff --git a/samples/pipelining/pipelining-and-async.cs b/samples/pipelining/pipelining-and-async.cs new file mode 100644 index 00000000..abc85528 --- /dev/null +++ b/samples/pipelining/pipelining-and-async.cs @@ -0,0 +1,105 @@ +using Oracle.ManagedDataAccess.Client; +using System.Threading.Tasks; +using System.Threading; +using System; + +// This code sample demonstrates using asynchronous ODP.NET (managed or core) with pipelining. +// It times opening a connection and several query executions. +// To run this app, add your database's HR schema User Id, Password, and Data Source values +// with ODP.NET 23ai or higher connecting to an Oracle Database 23ai or higher. + +class ODPNET_Async_Pipelining +{ + public static async Task Main() + { + // Add password and data source to connect to your Oracle database + string conString = "User Id=hr;Password=;Data Source=;"; + + //Enable pipelining + OracleConfiguration.Pipelining = true; + + using (OracleConnection con = new OracleConnection(conString)) + { + string cmdText1 = "SELECT * FROM EMPLOYEES"; + string cmdText2 = "SELECT * FROM DEPARTMENTS"; + string cmdText3 = "SELECT * FROM JOBS"; + + OracleCommand cmd1 = new OracleCommand(cmdText1, con); + OracleCommand cmd2 = new OracleCommand(cmdText2, con); + OracleCommand cmd3 = new OracleCommand(cmdText3, con); + + // Measure how long async connection open takes + DateTime start_time = DateTime.Now; + Task task = con.OpenAsync(); + DateTime end_time_open = DateTime.Now; + + // Simulate an operation that takes one second + Thread.Sleep(1000); + + // Retrieve open connection with "await" + await task; + + // Measure time for asynchronous query execution calls + DateTime start_time_query = DateTime.Now; + Task task1 = cmd1.ExecuteNonQueryAsync(); + Task task2 = cmd2.ExecuteNonQueryAsync(); + Task task3 = cmd3.ExecuteNonQueryAsync(); + + // Measure time async query initiations took + DateTime end_time_query = DateTime.Now; + + // Simulate an operation that takes one second + Thread.Sleep(1000); + + await task1; + await task2; + await task3; + + // Measure time all the async operations took plus sleep time + DateTime end_time_all = DateTime.Now; + + // Calculate connection open time + TimeSpan ts_open = end_time_open - start_time; + double ts_open1 = Math.Round(ts_open.TotalSeconds, 2); + Console.WriteLine("Asynchronous connection open time: " + ts_open1 + " seconds"); + + // Calculate queries initiation time + TimeSpan ts_initiate_sql = end_time_query - start_time_query; + double ts_sql1 = Math.Round(ts_initiate_sql.TotalSeconds, 2); + Console.WriteLine("Asynchronous and pipelining query initiation time: " + ts_sql1 + " seconds"); + + // Calculate SQL executions time + TimeSpan ts_sql = end_time_all - start_time_query; + double ts_sql2 = Math.Round(ts_sql.TotalSeconds, 2); + Console.WriteLine("Asynchronous and pipelining query execution time: " + ts_sql2 + " seconds"); + + // Calculate overall ODP.NET operation time + TimeSpan ts_all = end_time_all - start_time; + double ts_all1 = Math.Round(ts_all.TotalSeconds, 2); + Console.WriteLine("Asynchronous and pipelining operations total time: " + ts_all1 + " seconds"); + + cmd1.Dispose(); + cmd2.Dispose(); + cmd3.Dispose(); + } + } +} + +/* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with The MIT + * License (the "License.") + * + * You may obtain a copy of the License at + * https://github.com/oracle/Oracle.NET/blob/master/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *****************************************************************************/ diff --git a/schemas/scott.sql b/schemas/scott.sql index f843ba32..bed223a7 100644 --- a/schemas/scott.sql +++ b/schemas/scott.sql @@ -1,33 +1,14 @@ -Rem Copyright (c) 2016 by Oracle Corporation -Rem -Rem You may not use the identified files except in compliance with The MIT -Rem License (the "License.") -Rem -Rem You may obtain a copy of the License at -Rem https://github.com/oracle/Oracle.NET/blob/master/LICENSE -Rem -Rem Unless required by applicable law or agreed to in writing, software -Rem distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -Rem WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -Rem -Rem See the License for the specific language governing permissions and -Rem limitations under the License. -Rem Rem NAME -REM scott.sql +Rem scott.sql Rem Rem DESCRIPTION Rem SCOTT is a database user whose schema is used for Oracle code demonstrations -Rem Be sure to replace on lines 31 and 34 with your preferred password. +Rem Be sure to replace on lines 12 and 15 with your preferred password. Rem Historically, "tiger" has been SCOTT schema's password. - SET TERMOUT OFF SET ECHO OFF -rem CONGDON Invoked in RDBMS at build time. 29-DEC-1988 -rem OATES: Created: 16-Feb-83 - GRANT CONNECT,RESOURCE,UNLIMITED TABLESPACE TO SCOTT IDENTIFIED BY ; ALTER USER SCOTT DEFAULT TABLESPACE USERS; ALTER USER SCOTT TEMPORARY TABLESPACE TEMP; @@ -69,13 +50,13 @@ INSERT INTO EMP VALUES INSERT INTO EMP VALUES (7782,'CLARK','MANAGER',7839,to_date('9-6-1981','dd-mm-yyyy'),2450,NULL,10); INSERT INTO EMP VALUES -(7788,'SCOTT','ANALYST',7566,to_date('13-JUL-87')-85,3000,NULL,20); +(7788,'SCOTT','ANALYST',7566,to_date('13-07-87','dd-mm-rr')-85,3000,NULL,20); INSERT INTO EMP VALUES (7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10); INSERT INTO EMP VALUES (7844,'TURNER','SALESMAN',7698,to_date('8-9-1981','dd-mm-yyyy'),1500,0,30); INSERT INTO EMP VALUES -(7876,'ADAMS','CLERK',7788,to_date('13-JUL-87')-51,1100,NULL,20); +(7876,'ADAMS','CLERK',7788,to_date('13-07-87','dd-mm-rr')-51,1100,NULL,20); INSERT INTO EMP VALUES (7900,'JAMES','CLERK',7698,to_date('3-12-1981','dd-mm-yyyy'),950,NULL,30); INSERT INTO EMP VALUES @@ -104,3 +85,18 @@ COMMIT; SET TERMOUT ON SET ECHO ON + +Rem Copyright (c) 2016, 2024 by Oracle Corporation +Rem +Rem You may not use the identified files except in compliance with The MIT +Rem License (the "License.") +Rem +Rem You may obtain a copy of the License at +Rem https://github.com/oracle/dotnet-db-samples/blob/master/LICENSE.txt +Rem +Rem Unless required by applicable law or agreed to in writing, software +Rem distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +Rem WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +Rem +Rem See the License for the specific language governing permissions and +Rem limitations under the License. diff --git a/session-demos/2019/odtscreenshots/codegen.png b/session-demos/2019/odtscreenshots/codegen.png new file mode 100644 index 00000000..213de07b Binary files /dev/null and b/session-demos/2019/odtscreenshots/codegen.png differ diff --git a/session-demos/2019/odtscreenshots/hover.png b/session-demos/2019/odtscreenshots/hover.png new file mode 100644 index 00000000..5068731d Binary files /dev/null and b/session-demos/2019/odtscreenshots/hover.png differ diff --git a/session-demos/2019/odtscreenshots/intellisense.png b/session-demos/2019/odtscreenshots/intellisense.png new file mode 100644 index 00000000..2781058f Binary files /dev/null and b/session-demos/2019/odtscreenshots/intellisense.png differ diff --git a/session-demos/2019/odtscreenshots/intellisenseproc1.png b/session-demos/2019/odtscreenshots/intellisenseproc1.png new file mode 100644 index 00000000..41423108 Binary files /dev/null and b/session-demos/2019/odtscreenshots/intellisenseproc1.png differ diff --git a/session-demos/2019/odtscreenshots/intellisenseproc2.png b/session-demos/2019/odtscreenshots/intellisenseproc2.png new file mode 100644 index 00000000..0857cdc3 Binary files /dev/null and b/session-demos/2019/odtscreenshots/intellisenseproc2.png differ diff --git a/session-demos/2019/odtscreenshots/odtgalleryperftune.png b/session-demos/2019/odtscreenshots/odtgalleryperftune.png new file mode 100644 index 00000000..06531f44 Binary files /dev/null and b/session-demos/2019/odtscreenshots/odtgalleryperftune.png differ diff --git a/session-demos/2019/odtscreenshots/schemacompresultswindow.png b/session-demos/2019/odtscreenshots/schemacompresultswindow.png new file mode 100644 index 00000000..8da2327d Binary files /dev/null and b/session-demos/2019/odtscreenshots/schemacompresultswindow.png differ diff --git a/session-demos/2019/odtscreenshots/sqlmonitor.png b/session-demos/2019/odtscreenshots/sqlmonitor.png new file mode 100644 index 00000000..8d503d6b Binary files /dev/null and b/session-demos/2019/odtscreenshots/sqlmonitor.png differ diff --git a/session-demos/2019/odtscreenshots/sqlmonitorreport.png b/session-demos/2019/odtscreenshots/sqlmonitorreport.png new file mode 100644 index 00000000..7889c5c6 Binary files /dev/null and b/session-demos/2019/odtscreenshots/sqlmonitorreport.png differ diff --git a/session-demos/2019/odtscreenshots/tabledesigner.png b/session-demos/2019/odtscreenshots/tabledesigner.png new file mode 100644 index 00000000..c5dd0ed3 Binary files /dev/null and b/session-demos/2019/odtscreenshots/tabledesigner.png differ diff --git a/session-demos/2019/odtvscodescreenshots/codegen.png b/session-demos/2019/odtvscodescreenshots/codegen.png new file mode 100644 index 00000000..f094b4cb Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/codegen.png differ diff --git a/session-demos/2019/odtvscodescreenshots/condialogez2180.png b/session-demos/2019/odtvscodescreenshots/condialogez2180.png new file mode 100644 index 00000000..6ad02219 Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/condialogez2180.png differ diff --git a/session-demos/2019/odtvscodescreenshots/datagrid2430.png b/session-demos/2019/odtvscodescreenshots/datagrid2430.png new file mode 100644 index 00000000..6eb78120 Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/datagrid2430.png differ diff --git a/session-demos/2019/odtvscodescreenshots/explorer2340.png b/session-demos/2019/odtvscodescreenshots/explorer2340.png new file mode 100644 index 00000000..db4c278d Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/explorer2340.png differ diff --git a/session-demos/2019/odtvscodescreenshots/selectai.gif b/session-demos/2019/odtvscodescreenshots/selectai.gif new file mode 100644 index 00000000..f191b280 Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/selectai.gif differ diff --git a/session-demos/2019/odtvscodescreenshots/selectai.png b/session-demos/2019/odtvscodescreenshots/selectai.png new file mode 100644 index 00000000..9e2877c6 Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/selectai.png differ diff --git a/session-demos/2019/odtvscodescreenshots/sqlmonitorlist.png b/session-demos/2019/odtvscodescreenshots/sqlmonitorlist.png new file mode 100644 index 00000000..c0fc6ca6 Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/sqlmonitorlist.png differ diff --git a/session-demos/2019/odtvscodescreenshots/sqlmonitorreport.png b/session-demos/2019/odtvscodescreenshots/sqlmonitorreport.png new file mode 100644 index 00000000..7889c5c6 Binary files /dev/null and b/session-demos/2019/odtvscodescreenshots/sqlmonitorreport.png differ