Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oracle #206

Merged
merged 2 commits into from
Mar 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ The original idea was taken from [Yoinbol](https://github.com/Yoinbol/MicroOrm.P

All tests with MSSQL and MySQL has passed, PostgreSQL and SQLite tests are being developed.

It support the Oracle12c except for GUID issue. Tests has passed.

## Installation

```sh
Expand Down Expand Up @@ -74,6 +76,12 @@ Automatically set DataTime.UtcNow (You can use local date or define offset) for
* Supports complex primary keys.
* Supports simple Joins.
* For this moment, with MSSQL you can only use limit with offset if you call OrderBy first, otherwise limit will be ignored.
* It has a problem when try to use GUID with dapper in Oracle. In this case it doesn't work.
details see
https://github.com/DapperLib/Dapper/issues/633
https://github.com/DapperLib/Dapper/issues/637
https://github.com/vauto/Dapper.Database/pull/1


### Maps

Expand Down
27 changes: 23 additions & 4 deletions src/MicroOrm.Dapper.Repositories/DapperRepository.Insert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,18 @@ public virtual bool Insert(TEntity instance, IDbTransaction transaction)
var queryResult = SqlGenerator.GetInsert(instance);
if (SqlGenerator.IsIdentity)
{
var newId = Connection.Query<long>(queryResult.GetSql(), queryResult.Param, transaction).FirstOrDefault();
return SetValue(newId, instance);
if (SqlGenerator.Provider == Repositories.SqlGenerator.SqlProvider.Oracle)
{
Connection.Execute(queryResult.GetSql(), queryResult.Param, transaction);
int newId = ((DynamicParameters)(queryResult.Param)).Get<int>(":newId");
return SetValue(newId, instance);
}
else
{
var newId = Connection.Query<long>(queryResult.GetSql(), queryResult.Param, transaction).FirstOrDefault();
return SetValue(newId, instance);
}

}

return Connection.Execute(queryResult.GetSql(), instance, transaction) > 0;
Expand All @@ -43,8 +53,17 @@ public virtual async Task<bool> InsertAsync(TEntity instance, IDbTransaction tra
var queryResult = SqlGenerator.GetInsert(instance);
if (SqlGenerator.IsIdentity)
{
var newId = (await Connection.QueryAsync<long>(queryResult.GetSql(), queryResult.Param, transaction)).FirstOrDefault();
return SetValue(newId, instance);
if(SqlGenerator.Provider == Repositories.SqlGenerator.SqlProvider.Oracle)
{
await Connection.ExecuteAsync(queryResult.GetSql(), queryResult.Param, transaction);
int newId = ((DynamicParameters)(queryResult.Param)).Get<int>(":newId");
return SetValue(newId, instance);
}
else
{
var newId = (await Connection.QueryAsync<long>(queryResult.GetSql(), queryResult.Param, transaction)).FirstOrDefault();
return SetValue(newId, instance);
}
}

return await Connection.ExecuteAsync(queryResult.GetSql(), instance, transaction) > 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Data;
using Dapper;

namespace MicroOrm.Dapper.Repositories.Extensions
{
/// <summary>
/// TODO:
/// Currently it only add output parameter with type DbType.Int32.
/// It should actually check the prima key type then mapping it to different DbType such as DbType.Int64, etc.
/// If GUID support is added, it should also be added here.
/// </summary>
internal static class OracleDynamicParametersExtensions
{
public static void AddOracleOutputParameterForId(this DynamicParameters param)
{
param.Add(name: "newId", dbType: DbType.Int32, direction: ParameterDirection.Output);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ private string AppendJoinToUpdate<TBase>(TBase entity, SqlQuery originalBuilder,
private void AppendJoinQuery(JoinAttributeBase attrJoin, StringBuilder joinBuilder, string tableName)
{
var joinString = attrJoin.ToString();
var joinAs = Provider == SqlProvider.Oracle ? " " : " AS ";
if (attrJoin is CrossJoinAttribute)
{
joinBuilder.Append(attrJoin.TableAlias == string.Empty
? $"{joinString} {attrJoin.TableName} "
: $"{joinString} {attrJoin.TableName} AS {attrJoin.TableAlias} ");
: $"{joinString} {attrJoin.TableName}{joinAs}{attrJoin.TableAlias} ");
}
else
{
Expand Down Expand Up @@ -92,7 +93,7 @@ private void AppendJoinQuery(JoinAttributeBase attrJoin, StringBuilder joinBuild

joinBuilder.Append(attrJoin.TableAlias == string.Empty
? $"{joinString} {attrJoin.TableName} ON {tableName}.{attrJoin.Key} = {attrJoin.TableName}.{attrJoin.ExternalKey} {customFilter}"
: $"{joinString} {attrJoin.TableName} AS {attrJoin.TableAlias} ON {tableName}.{attrJoin.Key} = {attrJoin.TableAlias}.{attrJoin.ExternalKey} {customFilter}");
: $"{joinString} {attrJoin.TableName}{joinAs}{attrJoin.TableAlias} ON {tableName}.{attrJoin.Key} = {attrJoin.TableAlias}.{attrJoin.ExternalKey} {customFilter}");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Dapper;
using MicroOrm.Dapper.Repositories.Extensions;
using MicroOrm.Dapper.Repositories.SqlGenerator.QueryExpressions;

Expand Down Expand Up @@ -104,8 +106,21 @@ private void BuildQuerySql(IList<QueryExpression> queryProperties,
{
var vKey = string.Format("{0}_p{1}", qpExpr.PropertyName, qLevel); //Handle multiple uses of a field

sqlBuilder.AppendFormat("{0}.{1} {2} @{3}", tableName, columnName, qpExpr.QueryOperator, vKey);
sqlBuilder.AppendFormat("{0}.{1} {2} " + ParameterSymbol + "{3}", tableName, columnName, qpExpr.QueryOperator, vKey);
conditions.Add(new KeyValuePair<string, object>(vKey, qpExpr.PropertyValue));

// in oracle, we should pass a null value instead of an empty list in case of query something like "select * from sometable where id in :id " and :id is empty.
// make sure firsty the value in params is a type of generic list.
// i dont like this solution. if anyone has a better way plz commit.
if (Provider == SqlProvider.Oracle)
{
if(conditions[0].Value.GetType() != null && conditions[0].Value.GetType().IsGenericType && conditions[0].Value.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
var value = conditions[0].Value as IList;
if (value.Count == 0)
conditions[0] = new KeyValuePair<string, object>(vKey, null);
}
}
}

qLevel++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Dapper;
using MicroOrm.Dapper.Repositories.Attributes;

namespace MicroOrm.Dapper.Repositories.SqlGenerator
Expand Down Expand Up @@ -50,11 +51,18 @@ public virtual SqlQuery GetBulkInsert(IEnumerable<TEntity> entities)
// ReSharper disable once PossibleNullReferenceException
parameters.Add(property.PropertyName + i, entityType.GetProperty(property.PropertyName).GetValue(entity, null));

values.Add(string.Format("({0})", string.Join(", ", properties.Select(p => "@" + p.PropertyName + i))));
values.Add(string.Format("({0})", string.Join(", ", properties.Select(p => ParameterSymbol + p.PropertyName + i))));
}
if(Provider != SqlProvider.Oracle) {
query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1}) VALUES {2}", TableName, string.Join(", ", properties.Select(p => p.ColumnName)),
string.Join(",", values)); // values
}
else
{
query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1})", TableName, string.Join(", ", properties.Select(p => p.ColumnName)));
var singleInsert = values.Select(v => " SELECT " + v.Substring(1, v.Length-2) + " FROM DUAL ");
query.SqlBuilder.Append(string.Join("UNION ALL", singleInsert));
}

query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1}) VALUES {2}", TableName, string.Join(", ", properties.Select(p => p.ColumnName)),
string.Join(",", values)); // values

query.SetParam(parameters);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using MicroOrm.Dapper.Repositories.Attributes;

namespace MicroOrm.Dapper.Repositories.SqlGenerator
Expand All @@ -26,6 +27,9 @@ public virtual SqlQuery GetBulkUpdate(IEnumerable<TEntity> entities)

var parameters = new Dictionary<string, object>();

//In Oracle we use MERGE INTO to excute multipe update with argument.
List<string> singleSelectsForOracle = new List<string>();

for (var i = 0; i < entitiesArray.Length; i++)
{
var entity = entitiesArray[i];
Expand All @@ -43,11 +47,19 @@ public virtual SqlQuery GetBulkUpdate(IEnumerable<TEntity> entities)
UpdatedAtProperty.SetValue(entity, offset.DateTime);
}

if (i > 0)
query.SqlBuilder.Append("; ");
if (Provider != SqlProvider.Oracle)
{
if (i > 0)
query.SqlBuilder.Append("; ");

query.SqlBuilder.Append(
$"UPDATE {TableName} SET {string.Join(", ", properties.Select(p => $"{p.ColumnName} = @{p.PropertyName}{i}"))} WHERE {string.Join(" AND ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{p.ColumnName} = @{p.PropertyName}{i}"))}");
query.SqlBuilder.Append(
$"UPDATE {TableName} SET {string.Join(", ", properties.Select(p => $"{p.ColumnName} = {ParameterSymbol}{p.PropertyName}{i}"))} WHERE {string.Join(" AND ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{p.ColumnName} = {ParameterSymbol}{p.PropertyName}{i}"))}");
}
else
{
var singleSelect = $"SELECT {string.Join(", ", properties.Select(p => $"{ParameterSymbol}{p.PropertyName}{i} AS {p.ColumnName}"))}, {string.Join(" , ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{ParameterSymbol}{p.PropertyName}{i} AS {p.ColumnName}"))} FROM DUAL";
singleSelectsForOracle.Add(singleSelect);
}

// ReSharper disable PossibleNullReferenceException
foreach (var property in properties)
Expand All @@ -61,6 +73,16 @@ public virtual SqlQuery GetBulkUpdate(IEnumerable<TEntity> entities)

query.SetParam(parameters);

if (Provider == SqlProvider.Oracle)
{
var unionTName = $"{TableName}_BULKUPDATE";
var unionSelect = string.Join(" UNION ALL ", singleSelectsForOracle);
var unionOn = $"({string.Join(" AND ", KeySqlProperties.Where(p => !p.IgnoreUpdate).Select(p => $"{unionTName}.{p.ColumnName} = {TableName}.{p.ColumnName}"))})";
var unionSet = $"{string.Join(",", properties.Select(p => $"{p.ColumnName} = {unionTName}.{p.ColumnName} "))}";

query.SqlBuilder.Append($"MERGE INTO {TableName} {TableName} USING ({unionSelect}) {unionTName} ON {unionOn} WHEN MATCHED THEN UPDATE SET {unionSet}");
}

return query;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public virtual SqlQuery GetDelete(TEntity entity)
{
var sqlQuery = new SqlQuery();
var whereAndSql =
string.Join(" AND ", KeySqlProperties.Select(p => string.Format("{0}.{1} = @{2}", TableName, p.ColumnName, p.PropertyName)));
string.Join(" AND ", KeySqlProperties.Select(p => string.Format("{0}.{1} = {2}", TableName, p.ColumnName, ParameterSymbol+p.PropertyName)));

if (!LogicalDelete)
{
Expand Down Expand Up @@ -51,7 +51,7 @@ public virtual SqlQuery GetDelete(TEntity entity)
sqlQuery.SqlBuilder
.Append(", ")
.Append(UpdatedAtPropertyMetadata.ColumnName)
.Append(" = @")
.Append($" = {ParameterSymbol}")
.Append(UpdatedAtPropertyMetadata.PropertyName);
}

Expand Down Expand Up @@ -89,7 +89,7 @@ public virtual SqlQuery GetDelete(Expression<Func<TEntity, bool>> predicate)
sqlQuery.SqlBuilder
.Append(", ")
.Append(UpdatedAtPropertyMetadata.ColumnName)
.Append(" = @")
.Append($" = {ParameterSymbol}")
.Append(UpdatedAtPropertyMetadata.PropertyName);


Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Linq;
using System.Reflection;
using Dapper;
using MicroOrm.Dapper.Repositories.Attributes;
using MicroOrm.Dapper.Repositories.Extensions;

namespace MicroOrm.Dapper.Repositories.SqlGenerator
{
Expand Down Expand Up @@ -33,8 +35,22 @@ public virtual SqlQuery GetInsert(TEntity entity)

var query = new SqlQuery(entity);

//sorry, dapper doesn't support guid mapping in oracle.
//I can not convert guid to bytearray to build a seperate params, it can not be achieved in this lib.
//see details below to get more informations.
//https://github.com/DapperLib/Dapper/issues/633
//https://github.com/DapperLib/Dapper/issues/637
//https://github.com/vauto/Dapper.Database/pull/1

if (Provider == SqlProvider.Oracle)
{
var oracleParams = new DynamicParameters(entity);
oracleParams.AddOracleOutputParameterForId();
query.SetParam(oracleParams);
}

query.SqlBuilder.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", TableName, string.Join(", ", properties.Select(p => p.ColumnName)),
string.Join(", ", properties.Select(p => "@" + p.PropertyName))); // values
string.Join(", ", properties.Select(p => ParameterSymbol + p.PropertyName))); // values

if (IsIdentity)
switch (Provider)
Expand All @@ -55,6 +71,10 @@ public virtual SqlQuery GetInsert(TEntity entity)
query.SqlBuilder.Append(" RETURNING " + IdentitySqlProperty.ColumnName);
break;

case SqlProvider.Oracle:
query.SqlBuilder.Append(" RETURNING " + IdentitySqlProperty.ColumnName + " INTO :newId");
break;

default:
throw new ArgumentOutOfRangeException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ private SqlQuery GetSelect(Expression<Func<TEntity, bool>> predicate, bool first
}
else
{
if (Provider != SqlProvider.MSSQL)
if (Provider == SqlProvider.Oracle)
sqlQuery.SqlBuilder.Append("FETCH FIRST 1 ROW ONLY");
else if (Provider != SqlProvider.MSSQL)
sqlQuery.SqlBuilder
.Append("LIMIT 1");
}
Expand All @@ -79,6 +81,14 @@ private void SetLimit(SqlQuery sqlQuery, FilterData filterData)
sqlQuery.SqlBuilder.Append(filterData.LimitInfo.Limit);
sqlQuery.SqlBuilder.Append(" ROWS ONLY");
}
else if (Provider == SqlProvider.Oracle)
{
sqlQuery.SqlBuilder.Append("OFFSET ");
sqlQuery.SqlBuilder.Append(filterData.LimitInfo.Offset ?? 0);
sqlQuery.SqlBuilder.Append(" ROWS FETCH NEXT ");
sqlQuery.SqlBuilder.Append(filterData.LimitInfo.Limit);
sqlQuery.SqlBuilder.Append(" ROWS ONLY");
}
else
{
sqlQuery.SqlBuilder.Append("LIMIT ");
Expand Down Expand Up @@ -118,7 +128,7 @@ private void SetOrder(SqlQuery sqlQuery, FilterData filterData)
var count = filterData.OrderInfo.Columns.Count;
foreach (var col in filterData.OrderInfo.Columns)
{
if (UseQuotationMarks == true && Provider != SqlProvider.SQLite)
if (UseQuotationMarks == true && Provider != SqlProvider.SQLite && Provider != SqlProvider.Oracle)
{
sqlQuery.SqlBuilder.Append(Provider == SqlProvider.MSSQL ? $"[{col}]" : $"`{col}`");
}
Expand Down Expand Up @@ -254,7 +264,8 @@ public SqlQuery GetSelectById(object id, FilterData filterData, params Expressio
.Append(TableName)
.Append(".")
.Append(keyProperty.ColumnName)
.Append(" = @")
.Append(" = ")
.Append(ParameterSymbol)
.Append(keyProperty.PropertyName)
.Append(" ");

Expand All @@ -269,8 +280,12 @@ public SqlQuery GetSelectById(object id, FilterData filterData, params Expressio
.Append(" ");

if (includes.Length == 0 && Provider != SqlProvider.MSSQL)
sqlQuery.SqlBuilder
.Append("LIMIT 1");
{
if(Provider == SqlProvider.Oracle)
sqlQuery.SqlBuilder.Append("FETCH FIRST 1 ROWS ONLY");
else
sqlQuery.SqlBuilder.Append("LIMIT 1");
}

sqlQuery.SetParam(dictionary);
return sqlQuery;
Expand Down Expand Up @@ -326,7 +341,6 @@ private SqlQuery InitBuilderSelect(bool firstOnly, FilterData filterData)
query.SqlBuilder.Append(filterData?.SelectInfo?.Columns == null
? GetFieldsSelect(TableName, SqlProperties, UseQuotationMarks == true)
: GetFieldsSelect(filterData.SelectInfo.Columns));

return query;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ private string GetTableNameWithQuotes(JoinAttributeBase attrJoin, SqlPropertyMet

break;

case SqlProvider.Oracle:
break;

default:
throw new ArgumentOutOfRangeException(nameof(Provider));
}
Expand Down
Loading