Skip to content

Commit

Permalink
Add DbDataAdapter.Select overload that accepts raw sql with parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
VitaliyMF committed Sep 17, 2016
1 parent a5191fc commit a5ce496
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 33 deletions.
13 changes: 13 additions & 0 deletions src/NReco.Data.Tests/DbDataAdapterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ public async Task Select_Async() {
Assert.Equal(2, contactsWithHightRS.Count );
}

[Fact]
public void SelectRawSql() {
//no params
Assert.Equal(5, DbAdapter.Select("select count(*) from contacts").Single<int>() );
// simple param
Assert.Equal(5, DbAdapter.Select("select count(*) from contacts where id<{0}", 100).Single<int>() );
Assert.Equal(1, DbAdapter.Select("select count(*) from contacts where id>{0} and id<{1}", 1, 3).Single<int>() );

// custom db param
var customParam = new Microsoft.Data.Sqlite.SqliteParameter("test", "%John%");
Assert.Equal(1, DbAdapter.Select("select company_id from contacts where name like @test", customParam).Single<int>() );
}

[Fact]
public void InsertUpdateDelete_Dictionary() {
// insert
Expand Down
4 changes: 2 additions & 2 deletions src/NReco.Data/DataHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ internal static RecordSet GetRecordSetByReader(IDataReader rdr) {
return rs;
}

internal static void MapTo(IDataRecord record, object o, IDictionary<string,string> fieldToPropertyMap) {
internal static void MapTo(IDataRecord record, object o, Func<string,string> getPropertyName) {
var type = o.GetType();
for (int i = 0; i < record.FieldCount; i++) {
var fieldName = record.GetName(i);
var fieldValue = record.GetValue(i);

var propName = fieldToPropertyMap!=null && fieldToPropertyMap.ContainsKey(fieldName) ? fieldToPropertyMap[fieldName] : fieldName;
var propName = (getPropertyName!=null ? getPropertyName(fieldName) : null) ?? fieldName;
var pInfo =type.GetProperty(propName);
if (pInfo!=null) {
if (IsNullOrDBNull(fieldValue)) {
Expand Down
31 changes: 24 additions & 7 deletions src/NReco.Data/DbDataAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public DbDataAdapter(IDbConnection connection, IDbCommandBuilder cmdBuilder) {
CommandBuilder = cmdBuilder;
}

private void InitCmd(IDbCommand cmd) {
private void SetupCmd(IDbCommand cmd) {
cmd.Connection = Connection;
if (Transaction!=null)
cmd.Transaction = Transaction;
Expand All @@ -71,7 +71,7 @@ private void InitCmd(IDbCommand cmd) {
/// <param name="q">query to execute</param>
/// <returns>prepared select query</returns>
public SelectQuery Select(Query q) {
return new SelectQuery(this, q, null);
return new SelectQueryByQuery(this, q, null);
}

/// <summary>
Expand All @@ -80,12 +80,29 @@ public SelectQuery Select(Query q) {
/// <param name="q">query to execute</param>
/// <returns>prepared select query</returns>
public SelectQuery Select(Query q, IDictionary<string,string> fieldToPropertyMap) {
return new SelectQuery(this, q, fieldToPropertyMap);
return new SelectQueryByQuery(this, q, fld => fieldToPropertyMap.ContainsKey(fld) ? fieldToPropertyMap[fld] : null );
}

/// <summary>
/// Creates a <see cref="SelectQuery"/> based on a raw SQL query.
/// </summary>
/// <param name="sql">The raw SQL query.</param>
/// <param name="parameters">The values to be assigned to parameters.</param>
/// <returns>prepared select query</returns>
/// <remarks>Semantics of this method is similar to EF Core DbSet.FromSql. Any parameter values you supply will automatically be converted to a DbParameter:
/// <code>dbAdapter.Select("SELECT * FROM [dbo].[SearchBlogs]({0})", userSuppliedSearchTerm).ToRecordSet()</code>.
/// <para>You can also construct a DbParameter and supply it to as a parameter value. This allows you to use named
/// parameters in the SQL query string -
/// <code>dbAdapter.Select("SELECT * FROM [dbo].[SearchBlogs]({@searchTerm})", new SqlParameter("@searchTerm", userSuppliedSearchTerm)).ToRecordSet()</code>
/// </para>
/// </remarks>
public SelectQuery Select(string sql, params object[] parameters) {
return new SelectQueryBySql(this, sql, parameters, null);
}

int InsertInternal(string tableName, IEnumerable<KeyValuePair<string,IQueryValue>> data) {
using (var insertCmd = CommandBuilder.GetInsertCommand(tableName, data)) {
InitCmd(insertCmd);
SetupCmd(insertCmd);
return ExecuteNonQuery(insertCmd);
}
}
Expand Down Expand Up @@ -124,7 +141,7 @@ public int Insert(string tableName, object pocoModel, IDictionary<string,string>

int UpdateInternal(Query q, IEnumerable<KeyValuePair<string,IQueryValue>> data) {
using (var updateCmd = CommandBuilder.GetUpdateCommand(q, data)) {
InitCmd(updateCmd);
SetupCmd(updateCmd);
return ExecuteNonQuery(updateCmd);
}
}
Expand Down Expand Up @@ -236,7 +253,7 @@ public async Task<int> UpdateAsync(string tableName, RecordSet recordSet, Cancel
/// <returns>Number of actually deleted records.</returns>
public int Delete(Query q) {
using (var deleteCmd = CommandBuilder.GetDeleteCommand(q)) {
InitCmd(deleteCmd);
SetupCmd(deleteCmd);
return ExecuteNonQuery(deleteCmd);
}
}
Expand All @@ -254,7 +271,7 @@ public Task<int> DeleteAsync(Query q) {
public async Task<int> DeleteAsync(Query q, CancellationToken cancel) {
int affected = 0;
using (var deleteCmd = CommandBuilder.GetDeleteCommand(q)) {
InitCmd(deleteCmd);
SetupCmd(deleteCmd);
var isClosedConn = deleteCmd.Connection.State == ConnectionState.Closed;
if (isClosedConn) {
await deleteCmd.Connection.OpenAsync(cancel).ConfigureAwait(false);
Expand Down
107 changes: 86 additions & 21 deletions src/NReco.Data/Internal/DbDataAdapter.SelectQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,28 @@ public partial class DbDataAdapter {
/// <summary>
/// Represents select query (returned by <see cref="DbDataAdapter.Select"/> method).
/// </summary>
public class SelectQuery {
DbDataAdapter Adapter;
Query Query;
IDictionary<string,string> FieldToPropertyMap;
public abstract class SelectQuery {
readonly protected DbDataAdapter Adapter;
Func<string,string> FieldToPropertyMapper;

internal SelectQuery(DbDataAdapter adapter, Query q, IDictionary<string,string> fldToPropMap) {
internal SelectQuery(DbDataAdapter adapter, Func<string,string> fldToPropMapper) {
Adapter = adapter;
Query = q;
FieldToPropertyMap = fldToPropMap;
FieldToPropertyMapper = fldToPropMapper;
}

int DataReaderRecordOffset {
get {
return Adapter.ApplyOffset ? Query.RecordOffset : 0;
return Adapter.ApplyOffset ? RecordOffset : 0;
}
}

IDbCommand GetSelectCmd() {
var selectCmd = Adapter.CommandBuilder.GetSelectCommand(Query);
Adapter.InitCmd(selectCmd);
return selectCmd;
}
protected virtual int RecordOffset { get { return 0; } }

protected virtual int RecordCount { get { return Int32.MaxValue; } }

protected abstract IDbCommand GetSelectCmd();

protected virtual string FirstFieldName { get { return null; } }

/// <summary>
/// Returns the first record from the query result.
Expand Down Expand Up @@ -140,7 +140,7 @@ public Task<List<Dictionary<string,object>>> ToDictionaryListAsync() {
/// </summary>
public Task<List<Dictionary<string,object>>> ToDictionaryListAsync(CancellationToken cancel) {
using (var selectCmd = GetSelectCmd()) {
return DataHelper.ExecuteReaderAsync<List<Dictionary<string,object>>>(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, Query.RecordCount,
return DataHelper.ExecuteReaderAsync<List<Dictionary<string,object>>>(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, RecordCount,
new ListDataReaderResult<Dictionary<string,object>>( ReadDictionary ), cancel
);
}
Expand All @@ -153,7 +153,7 @@ public Task<List<Dictionary<string,object>>> ToDictionaryListAsync(CancellationT
public List<T> ToList<T>() where T : new() {
var result = new List<T>();
using (var selectCmd = GetSelectCmd()) {
DataHelper.ExecuteReader(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, Query.RecordCount,
DataHelper.ExecuteReader(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, RecordCount,
(rdr) => {
result.Add( Read<T>(rdr) );
} );
Expand All @@ -173,7 +173,7 @@ public Task<List<Dictionary<string,object>>> ToDictionaryListAsync(CancellationT
/// </summary>
public Task<List<T>> ToListAsync<T>(CancellationToken cancel) where T : new() {
using (var selectCmd = GetSelectCmd()) {
return DataHelper.ExecuteReaderAsync<List<T>>(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, Query.RecordCount,
return DataHelper.ExecuteReaderAsync<List<T>>(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, RecordCount,
new ListDataReaderResult<T>( Read<T> ), cancel
);
}
Expand All @@ -185,7 +185,7 @@ public Task<List<Dictionary<string,object>>> ToDictionaryListAsync(CancellationT
public RecordSet ToRecordSet() {
var result = new RecordSetDataReaderResult();
using (var selectCmd = GetSelectCmd()) {
DataHelper.ExecuteReader(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, Query.RecordCount,
DataHelper.ExecuteReader(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, RecordCount,
result.Read );
}
return result.Result;
Expand All @@ -203,7 +203,7 @@ public Task<RecordSet> ToRecordSetAsync() {
/// </summary>
public Task<RecordSet> ToRecordSetAsync(CancellationToken cancel) {
using (var selectCmd = GetSelectCmd()) {
return DataHelper.ExecuteReaderAsync<RecordSet>(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, Query.RecordCount,
return DataHelper.ExecuteReaderAsync<RecordSet>(selectCmd, CommandBehavior.Default, DataReaderRecordOffset, RecordCount,
new RecordSetDataReaderResult(), cancel
);
}
Expand All @@ -226,8 +226,10 @@ private Dictionary<string,object> ReadDictionary(IDataReader rdr) {
if (typeCode!=TypeCode.Object) {
if (rdr.FieldCount==1) {
return ChangeType<T>( rdr[0], typeCode);
} else if (Query.Fields!=null && Query.Fields.Length>0) {
return ChangeType<T>( rdr[Query.Fields[0].Name], typeCode);
} else if (rdr.FieldCount>1) {
var firstFld = FirstFieldName;
var val = firstFld!=null ? rdr[firstFld] : rdr[0];
return ChangeType<T>( val, typeCode);
} else {
return default(T);
}
Expand All @@ -240,10 +242,73 @@ private Dictionary<string,object> ReadDictionary(IDataReader rdr) {
}
// handle as poco model
var res = new T();
DataHelper.MapTo(rdr, res, FieldToPropertyMap);
DataHelper.MapTo(rdr, res, FieldToPropertyMapper);
return (T)res;
}
}

internal class SelectQueryByQuery : SelectQuery {

readonly Query Query;

internal SelectQueryByQuery(DbDataAdapter adapter, Query q, Func<string,string> fldToPropMapper)
: base(adapter,fldToPropMapper) {
Query = q;
}

protected override IDbCommand GetSelectCmd() {
var selectCmd = Adapter.CommandBuilder.GetSelectCommand(Query);
Adapter.SetupCmd(selectCmd);
return selectCmd;
}

protected override int RecordOffset { get { return Query.RecordOffset; } }

protected override int RecordCount { get { return Query.RecordCount; } }

protected override string FirstFieldName {
get {
return Query.Fields!=null && Query.Fields.Length>0 ? Query.Fields[0].Name : null;
}
}
}

internal class SelectQueryBySql : SelectQuery {

readonly string Sql;
object[] Parameters;

internal SelectQueryBySql(DbDataAdapter adapter, string sql, object[] parameters, Func<string,string> fldToPropMapper)
: base(adapter,fldToPropMapper) {
Sql = sql;
Parameters = parameters;
}

protected override IDbCommand GetSelectCmd() {
var selectCmd = Adapter.CommandBuilder.DbFactory.CreateCommand();

var fmtArgs = new string[Parameters.Length];
for (int i=0; i<Parameters.Length; i++) {
var paramVal = Parameters[i];

if (paramVal is IDataParameter) {
// this is already composed command parameter
selectCmd.Parameters.Add(paramVal);
fmtArgs[i] = ((IDataParameter)paramVal).ParameterName;
} else {
var cmdParam = Adapter.CommandBuilder.DbFactory.AddCommandParameter(selectCmd, paramVal);
fmtArgs[i] = cmdParam.Placeholder;
}
}
selectCmd.CommandText = String.Format(Sql, fmtArgs);

Adapter.SetupCmd(selectCmd);
return selectCmd;
}

}



}
}
6 changes: 3 additions & 3 deletions src/NReco.Data/project.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"version": "1.0.0-alpha5",
"version": "1.0.0-alpha6",
"title": "NReco.Data",
"description": "Lightweight db-independent DAL for generating SQL commands by abstract queries, schema-less CRUD operations, POCO mapping.",
"description": "Lightweight db-independent data access library: generates SQL commands by abstract queries (command builder), schema-less CRUD operations (data adapter), POCO mapping, RecordSet (replacement for DataTable).",
"authors": [ "Vitalii Fedorchenko" ],
"copyright": "Copyright (c) 2016 Vitalii Fedorchenko",
"packOptions": {
"owners": [ "Vitalii Fedorchenko" ],
"projectUrl": "http://www.nrecosite.com/dalc_net.aspx",
"tags": [ "DAL", "ado.net", "data", "relex", "sql", "query", "recordset", "generator", "batch", "database", "micro-orm", "data-mapper", "poco", "schema-less", "netstandard", "netcore", "net45" ],
"tags": [ "DAL", "ado.net", "data", "relex", "recordset", "sql", "query", "generator", "batch", "database", "micro-orm", "data-mapper", "poco", "schema-less", "netstandard", "netcore", "net45" ],
"licenseUrl": "https://raw.githubusercontent.com/nreco/data/master/LICENSE",
"iconUrl": "http://www.nrecosite.com/img/nreco-logo-200.png"
},
Expand Down

0 comments on commit a5ce496

Please sign in to comment.