Artificial Intelligence Vector Data Types
Starting with version 23.8, Oracle EF Core supports developing both dense and sparse Oracle Database vectors with VectorDistance
, VectorEmbedding
, and ToVector
EF functions.
ODP.NET natively supports dense vectors in .NET and EF Core with the OracleVector
data type. Sparse vectors are supported using the OracleSparseVector
data type. These data types are mapped by default to Oracle Database DENSE
and SPARSE
format vectors, respectively.
Entity Framework Core 9 or higher is required for the vector data type support.
In Oracle EF Core 23.7, only dense vectors were supported and the default vector mappings were to primitive array types that included short[]
, float[]
, double[]
, and byte[]
.
In prior releases, vector data types were mapped to .NET strings.
The following table shows the .NET common language runtime (CLR) data types that Oracle Database vectors map to by default and the possible alternative mappings.
Table 5-5 Oracle Vector EF Core Data Type Mapping
Oracle Database Data Type | Default CLR Mapping | Alternative CLR Mapping |
---|---|---|
|
|
|
|
|
None |
|
|
None |
A fixed vector format is a database vector with a specific numeric format, such as VECTOR(100, FLOAT64)
, VECTOR(*, FLOAT32)
, or VECTOR(24, BINARY)
. The number of dimensions can vary.
A flexible vector format can store any numeric format, such as VECTOR(3, *)
, VECTOR(*,*)
, or VECTOR(100, *, SPARSE)
. The number of dimensions can vary as well.
Migrations
When an Oracle EF Core model contains OracleVector
or OracleSparseVector
data types, they are mapped to Oracle Database vectors only when the HasColumnType()
fluent API or corresponding data annotation is specified. Migrations are also allowed if entity properties are the ones defined in the alternative CLR mapping column as documented in the above table.
For sparse vectors, application must declare the complete vector column type in HasColumnType()
, such as HasColumnType("VECTOR(3,FLOAT64, sparse)")
.
Scaffolding
By default, Oracle Database dense vectors in any numeric format map to OracleVector
and sparse vectors in any numeric format will map to OracleSparseVector
for EF Core scaffolding.
Fixed dense vectors mapping can be customized to map to primitive array types, including short[]
, float[]
, double[]
, and byte[]
.
Code Sample
The following EF Core code sample demonstrates how to use ODP.NET vector types with EF Core, LINQ, and EF.Functions.
/* This Oracle EF Core Code First sample shows how to create and use a database schema with Oracle vector columns. It does the following: 1. Creates two tables, one with dense vectors and the other with sparse vectors. It uses native ODP.NET vectors, OracleVector and OracleSparseVector. 2. Inserts two rows into each table. 3. Shows various vector EF function use cases by executing LINQ commands on the tables. Note: Native ODP.NET vector types require the Oracle.ManagedDataAccess.Types namespace. Requires EF Core 9 or higher. */ using System; using System.Linq; using Microsoft.EntityFrameworkCore; using Oracle.ManagedDataAccess.Types; namespace OracleVectorSample { class Program { static void Main(string[] args) { using (var ctx = new ModelContext()) { ctx.Database.EnsureDeleted(); ctx.Database.EnsureCreated(); short[] shortdataD = [1, 2, 3]; float[] floatdataD = [1.1f, 2.2f, 3.3f]; double[] doubledataD = [3.3, 2.2, 1.1]; string stringdataD = "[3,2,1]"; OracleVector shortVecD = new OracleVector(shortdataD); OracleVector floatVecD = new OracleVector(floatdataD); OracleVector doubleVecD = new OracleVector(doubledataD); OracleVector flexVecD = new OracleVector(stringdataD); var row1dense = new Tabdense { Id = 1, Colint8 = new OracleVector(shortdataD), Colfloat32 = new OracleVector(floatdataD), Colfloat64 = new OracleVector(doubledataD), Colflex = new OracleVector(stringdataD) }; var row2dense = new Tabdense { Id = 2, Colint8 = shortVecD, Colfloat32 = floatVecD, Colfloat64 = doubleVecD, Colflex = flexVecD }; //Insert data into dense vectors. ctx.Tabdenses.Add(row1dense); ctx.Tabdenses.Add(row2dense); ctx.SaveChanges(); string shortdataS = "[3, [0,1,2], [1, 2, 3]]"; string floatdataS = "[3, [0,1,2], [1.1, 2.2, 3.3]]"; string doubledataS = "[3, [0,1,2], [3.3, 2.2, 1.1]]"; string stringdataS = "[3, [0,1,2], [3, 2, 1]]"; OracleSparseVector shortVecS = new OracleSparseVector(shortdataS); OracleSparseVector floatVecS = new OracleSparseVector(floatdataS); OracleSparseVector doubleVecS = new OracleSparseVector(doubledataS); OracleSparseVector flexVecS = new OracleSparseVector(stringdataS); var row1sparse = new Tabsparse { Id = 1, Colint8 = new OracleSparseVector(shortdataS), Colfloat32 = new OracleSparseVector(floatdataS), Colfloat64 = new OracleSparseVector(doubledataS), Colflex = new OracleSparseVector(stringdataS) }; var row2sparse = new Tabsparse { Id = 2, Colint8 = shortVecS, Colfloat32 = floatVecS, Colfloat64 = doubleVecS, Colflex = flexVecS }; //Insert data into sparse vectors ctx.Tabsparses.Add(row1sparse); ctx.Tabsparses.Add(row2sparse); ctx.SaveChanges(); //Select all table data. var resultTabDense = ctx.Tabdenses.ToList(); var resultTabSparse = ctx.Tabsparses.ToList(); //LINQ - USING DENSE VECTORS //Below LINQ selects the ID from TABDENSE table ordering the results based on the vector_distance of COLINT8 column and an arbitrary "[3, 2, 1]" vector data. /* Generated query- SELECT "t"."ID" FROM "TABDENSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLINT8", TO_VECTOR('[3, 2, 1]', 3, INT8, DENSE), cosine) */ var query1D = (from t in ctx.Tabdenses orderby EF.Functions.VectorDistance(t.Colint8, EF.Functions.ToVector("[3, 2, 1]", 3, "INT8", "DENSE"), "cosine") select t.Id); foreach (var row in query1D) { Console.WriteLine(row); } short[] key1 = [3, 2, 1]; OracleVector keyV = new OracleVector(key1); //Below LINQ is similar to above LINQ but uses the vector "[3, 2, 1]" as a variable and orders the results based on the vector_distance of COLFLOAT64 column and "[3, 2, 1]" vector data. /* Generated query- SELECT "t"."ID" FROM "TABDENSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLFLOAT64", TO_VECTOR(:key1_1, 3, FLOAT64, DENSE), cosine) */ var query2D = (from t in ctx.Tabdenses orderby EF.Functions.VectorDistance(t.Colfloat64, EF.Functions.ToVector(key1, 3, "FLOAT64", "DENSE"), "cosine") select t.Id); foreach (var row in query2D) { Console.WriteLine(row); } //Below LINQ is similar to above but it passes "*" as the storage format in which case the output vector format of TO_VECTOR is determined by the input vector argument format. /* Generated query- SELECT "t"."ID" FROM "TABDENSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLFLOAT64", TO_VECTOR(:keyV_1, 3, FLOAT64, *), cosine) */ var query3D = (from t in ctx.Tabdenses orderby EF.Functions.VectorDistance(t.Colfloat64, EF.Functions.ToVector(keyV, 3, "FLOAT64", "*"), "cosine") select t.Id); foreach (var row in query3D) { Console.WriteLine(row); } //Similar query as above but the vector distance is calculated between two vector columns (COLFLOAT32 and COLINT8) after converting the numeric format of COLINT8 to FLOAT32 numeric format via ToVector. /* Generated query- SELECT "t"."ID" FROM "TABDENSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLFLOAT32", TO_VECTOR("t"."COLINT8", 3, FLOAT32, *), cosine) */ var query4D = (from t in ctx.Tabdenses orderby EF.Functions.VectorDistance(t.Colfloat32, EF.Functions.ToVector(t.Colint8, 3, "FLOAT32", "*"), "cosine") select t.Id); foreach (var row in query4D) { Console.WriteLine(row); } //LINQ - USING SPARSE VECTORS //Below LINQ selects the ID from TABSPARSE table ordering the results based on the vector_distance of COLINT8 column and "[3, [0, 1, 2], [3, 2, 1]]" sparse vector data. /* Generated query- SELECT "t"."ID" FROM "TABSPARSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLINT8", TO_VECTOR('[3, [0, 1, 2], [3, 2, 1]]', 3, INT8, SPARSE), cosine) */ var query1S = (from t in ctx.Tabsparses orderby EF.Functions.VectorDistance(t.Colint8, EF.Functions.ToVector("[3, [0, 1, 2], [3, 2, 1]]", 3, "INT8", "SPARSE"), "cosine") select t.Id); foreach (var row in query1S) { Console.WriteLine(row); } //Below LINQ is similar to above LINQ but orders the results based on the vector_distance of COLFLOAT64 column and "[3, 2, 1]" vector data. /* Generated query- SELECT "t"."ID" FROM "TABSPARSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLFLOAT64", TO_VECTOR(:key1_1, 3, FLOAT64, SPARSE), cosine) */ var query2S = (from t in ctx.Tabsparses orderby EF.Functions.VectorDistance(t.Colfloat64, EF.Functions.ToVector(key1, 3, "FLOAT64", "SPARSE"), "cosine") select t.Id); foreach (var row in query2S) { Console.WriteLine(row); } //Similar LINQ as above but it uses an object of ODP.NET OracleVector Type instead of primitive type in ToVector EF function. /* Generated query- SELECT "t"."ID" FROM "TABSPARSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLFLOAT64", TO_VECTOR(:keyV_1, 3, FLOAT64, SPARSE), cosine) */ var query3S = (from t in ctx.Tabsparses orderby EF.Functions.VectorDistance(t.Colfloat64, EF.Functions.ToVector(keyV, 3, "FLOAT64", "SPARSE"), "cosine") select t.Id); foreach (var row in query3S) { Console.WriteLine(row); } //Below LINQ uses "*" as the storage format in which case the output vector format of TO_VECTOR is determined by the input vector argument format. /* Generated query- SELECT "t"."ID" FROM "TABSPARSE" "t" ORDER BY VECTOR_DISTANCE("t"."COLFLOAT32", TO_VECTOR("t"."COLINT8", 3, FLOAT32, *), cosine) */ var query4S = (from t in ctx.Tabsparses orderby EF.Functions.VectorDistance(t.Colfloat32, EF.Functions.ToVector(t.Colint8, 3, "FLOAT32", "*"), "cosine") select t.Id); foreach (var row in query4S) { Console.WriteLine(row); } } } } public partial class ModelContext : DbContext { public ModelContext() { } public ModelContext(DbContextOptions<ModelContext> options) : base(options) { } public virtual DbSet<Tabdense> Tabdenses { get; set; } public virtual DbSet<Tabsparse> Tabsparses { get; set; } //Enter your ODP.NET connection string. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseOracle("<CONNECTION STRING>"); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Tabdense>(entity => { entity.HasKey(e => e.Id); entity.ToTable("TABDENSE"); entity.Property(e => e.Id) .HasPrecision(10) .ValueGeneratedNever() .HasColumnName("ID"); entity.Property(e => e.Colflex) .HasColumnType("VECTOR(*,*,DENSE)") .HasColumnName("COLFLEX"); entity.Property(e => e.Colfloat32) .HasColumnType("VECTOR(3,FLOAT32,DENSE)") .HasColumnName("COLFLOAT32"); entity.Property(e => e.Colfloat64) .HasColumnType("VECTOR(3,FLOAT64,DENSE)") .HasColumnName("COLFLOAT64"); entity.Property(e => e.Colint8) .HasColumnType("VECTOR(3,INT8,DENSE)") .HasColumnName("COLINT8"); }); modelBuilder.Entity<Tabsparse>(entity => { entity.HasKey(e => e.Id); entity.ToTable("TABSPARSE"); entity.Property(e => e.Id) .HasPrecision(10) .ValueGeneratedNever() .HasColumnName("ID"); entity.Property(e => e.Colflex) .HasColumnType("VECTOR(*,*,SPARSE)") .HasColumnName("COLFLEX"); entity.Property(e => e.Colfloat32) .HasColumnType("VECTOR(3,FLOAT32,SPARSE)") .HasColumnName("COLFLOAT32"); entity.Property(e => e.Colfloat64) .HasColumnType("VECTOR(3,FLOAT64,SPARSE)") .HasColumnName("COLFLOAT64"); entity.Property(e => e.Colint8) .HasColumnType("VECTOR(3,INT8,SPARSE)") .HasColumnName("COLINT8"); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); } public partial class Tabdense { public int Id { get; set; } public OracleVector? Colint8 { get; set; } public OracleVector? Colfloat32 { get; set; } public OracleVector? Colfloat64 { get; set; } public OracleVector? Colflex { get; set; } } public partial class Tabsparse { public int Id { get; set; } public OracleSparseVector? Colint8 { get; set; } public OracleSparseVector? Colfloat32 { get; set; } public OracleSparseVector? Colfloat64 { get; set; } public OracleSparseVector? Colflex { get; set; } } }