Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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
5 changes: 3 additions & 2 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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: <br>
A) dotnet new console --output (Sample Name) <br>
B) dotnet add package Oracle.ManagedDataAccess.Core --version (e.g. 23.5.0)
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)
Expand Down Expand Up @@ -109,7 +109,8 @@ Entity Framework Core
* Getting Started Sample: Demonstrates a basic Oracle EF Core scenario using migrations and scaffolding. <br>
* JSON Columns Sample: Demonstrates how to create an owned entity, insert, query, update, and delete JSON column data. <br>
* Keyless Entity Types Sample: Demonstrates Oracle EF Core keyless entity types with relational and materialized views. <br>
* Stored Procedure Result Set Samples: Demonstrates using PL/SQL that returns either an explicitly or implicitly bound REF Cursor.
* Stored Procedure Result Set Samples: Demonstrates using PL/SQL that returns either an explicitly or implicitly bound REF Cursor. <br>
* T4 Text Template Samples: Demonstrates data type mapping customization when scaffolding.

Event Handler
-------------
Expand Down
8 changes: 8 additions & 0 deletions samples/ef-core/t4-templates/README.md
Original file line number Diff line number Diff line change
@@ -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.
209 changes: 209 additions & 0 deletions samples/ef-core/t4-templates/all-numeric-types/EntityType.t4
Original file line number Diff line number Diff line change
@@ -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<IAnnotationCodeGenerator>();
var code = services.GetRequiredService<ICSharpHelper>();

var usings = new List<string>
{
"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()))
{
#>
/// <summary>
/// <#= code.XmlComment(EntityType.GetComment()) #>
/// </summary>
<#
}

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()))
{
#>
/// <summary>
/// <#= code.XmlComment(property.GetComment(), indent: 1) #>
/// </summary>
<#
}

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);
#>
Loading