├── Spry.Tests ├── TestDatabase.mdf ├── TestDatabase_log.ldf ├── Dto │ ├── Customer.cs │ └── CustomerAddress.cs ├── Spry.Tests.csproj ├── BaseTest.cs ├── Benchmarks │ └── DapperDirectVsSpryInsertBenchmark.cs ├── RepositoryTest.cs └── CustomerRepository.cs ├── Spry ├── IBuilder.cs ├── Table │ ├── InnerJoin.cs │ ├── FullOuterJoin.cs │ ├── LeftOuterJoin.cs │ ├── RightOuterJoin.cs │ ├── IConditionItem.cs │ ├── Join.cs │ ├── SpryTableConditions.cs │ └── SpryTable.cs ├── Delete │ ├── SpryDelete.cs │ └── SpryDeleteTable.cs ├── Select │ ├── SprySelect.cs │ ├── SprySelectColumn.cs │ └── SprySelectTable.cs ├── IExecutable.cs ├── Spry.csproj ├── Insert │ ├── SpryInsertTable.cs │ ├── SpryInsert.cs │ └── InsertValue.cs ├── Update │ ├── SpryUpdate.cs │ ├── SpryUpdateTable.cs │ └── UpdateValue.cs ├── SqlExecutor.cs ├── Spry.cs ├── SpryExpression.cs ├── SpryParameter.cs └── Where │ └── Where.cs ├── .gitignore ├── Spry.Console ├── Spry.Console.csproj └── Program.cs ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── LICENSE ├── Spry.sln └── README.MD /Spry.Tests/TestDatabase.mdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshmsft/Spry/HEAD/Spry.Tests/TestDatabase.mdf -------------------------------------------------------------------------------- /Spry.Tests/TestDatabase_log.ldf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vigneshmsft/Spry/HEAD/Spry.Tests/TestDatabase_log.ldf -------------------------------------------------------------------------------- /Spry/IBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Spry 2 | { 3 | public abstract class Builder 4 | { 5 | internal abstract string Build(); 6 | } 7 | 8 | public abstract class Buildable 9 | { 10 | internal abstract string BuildImpl(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Spry.Tests/Dto/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Spry.Tests.Dto 4 | { 5 | public class Customer 6 | { 7 | public int CustomerId { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public DateTime DateOfBirth { get; set; } 12 | 13 | public CustomerAddress Address { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /Spry/bin 6 | /Spry/obj 7 | /Spry.Console/bin 8 | /Spry.Console/obj 9 | /Spry.Tests/bin 10 | /Spry.Tests/obj 11 | /TestResults 12 | *.nupkg 13 | /packages 14 | /.vs/ 15 | /.vs/**/ 16 | *.suo 17 | -------------------------------------------------------------------------------- /Spry.Tests/Dto/CustomerAddress.cs: -------------------------------------------------------------------------------- 1 | namespace Spry.Tests.Dto 2 | { 3 | public class CustomerAddress 4 | { 5 | public int CustomerAddressId { get; set; } 6 | 7 | public int CustomerId { get; set; } 8 | 9 | public string LineOne { get; set; } 10 | 11 | public string City { get; set; } 12 | 13 | public string Country { get; set; } 14 | 15 | public string PostCode { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Spry.Console/Spry.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | false 6 | 7 | Exe 8 | Spry.Console.Program 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Spry/Table/InnerJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spry.Select; 3 | 4 | namespace Spry.Table 5 | { 6 | public class InnerJoin : Join 7 | { 8 | public InnerJoin(SprySelect spry, SprySelectTable tableOne, SprySelectTable tableTwo) 9 | : base(spry, tableOne, tableTwo) 10 | { 11 | 12 | } 13 | 14 | internal override string BuildImpl() 15 | { 16 | return "INNER JOIN " + TableTwo.BuildImpl() + OnCondition + Environment.NewLine; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Spry/Table/FullOuterJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spry.Select; 3 | 4 | namespace Spry.Table 5 | { 6 | public class FullOuterJoin : Join 7 | { 8 | public FullOuterJoin(SprySelect spry, SprySelectTable tableOne, SprySelectTable tableTwo) 9 | : base(spry, tableOne, tableTwo) 10 | { 11 | } 12 | 13 | internal override string BuildImpl() 14 | { 15 | return "FULL OUTER JOIN " + TableTwo.BuildImpl() + OnCondition + Environment.NewLine; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | name: Checkout Repository 15 | 16 | - name: Use .NET Core 3.1.301 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.301 20 | 21 | - name: dotnet restore 22 | run: dotnet restore /nologo 23 | 24 | - name: dotnet build 25 | run: dotnet build -c Release --no-restore --nologo 26 | -------------------------------------------------------------------------------- /Spry/Table/LeftOuterJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spry.Select; 3 | 4 | namespace Spry.Table 5 | { 6 | public class LeftOuterJoin : Join 7 | { 8 | public LeftOuterJoin(SprySelect spry, SprySelectTable tableOne, SprySelectTable tableTwo) 9 | : base(spry, tableOne, tableTwo) 10 | { 11 | } 12 | 13 | internal override string BuildImpl() 14 | { 15 | return "LEFT OUTER JOIN " + TableTwo.BuildImpl() + OnCondition + Environment.NewLine; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Spry/Table/RightOuterJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spry.Select; 3 | 4 | namespace Spry.Table 5 | { 6 | public class RightOuterJoin : Join 7 | { 8 | public RightOuterJoin(SprySelect spry, SprySelectTable tableOne, SprySelectTable tableTwo) 9 | : base(spry, tableOne, tableTwo) 10 | { 11 | } 12 | 13 | internal override string BuildImpl() 14 | { 15 | return "RIGHT OUTER JOIN " + TableTwo.BuildImpl() + OnCondition + Environment.NewLine; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Spry/Delete/SpryDelete.cs: -------------------------------------------------------------------------------- 1 | using Spry.Table; 2 | 3 | namespace Spry.Delete 4 | { 5 | public class SpryDelete 6 | { 7 | internal SpryDelete() { } 8 | 9 | private SpryDeleteTable _table; 10 | 11 | public SpryDeleteTable From(string tableName, string schema = "dbo") 12 | { 13 | _table = new SpryDeleteTable(this, tableName, schema); 14 | return _table; 15 | } 16 | 17 | public string Build() 18 | { 19 | return "DELETE FROM " + _table.BuildImpl(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Spry/Select/SprySelect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Spry.Select 4 | { 5 | public sealed class SprySelect 6 | { 7 | private SprySelectColumn _column; 8 | private SprySelect() 9 | { 10 | 11 | } 12 | 13 | public static SprySelectColumn Select() 14 | { 15 | var spry = new SprySelect(); 16 | return spry._column = new SprySelectColumn(spry); 17 | } 18 | 19 | public string Build() 20 | { 21 | return "SELECT" + Environment.NewLine + _column.BuildImpl(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Spry.Tests/Spry.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Spry/IExecutable.cs: -------------------------------------------------------------------------------- 1 | namespace Spry 2 | { 3 | using System.Collections.Generic; 4 | using System.Data; 5 | 6 | public interface IExecutable 7 | { 8 | int Execute(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null); 9 | 10 | IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null); 11 | 12 | IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null); 13 | 14 | TColType ExecuteScalar(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null); 15 | } 16 | } -------------------------------------------------------------------------------- /Spry/Spry.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard20 5 | 1.0.6 6 | Vignesh.N 7 | https://github.com/vigneshmsft/Spry/blob/master/LICENSE 8 | https://github.com/vigneshmsft/Spry 9 | https://github.com/vigneshmsft/Spry 10 | dapper spry database micro-orm orm sql-server sql query-builder fluent-query-builder query dapper-extensions 11 | Spry - fluent CRUD queries using Dapper 12 | v1.0.6 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Spry/Insert/SpryInsertTable.cs: -------------------------------------------------------------------------------- 1 | using Spry.Table; 2 | 3 | namespace Spry.Insert 4 | { 5 | internal class SpryInsertTable : SpryTable> 6 | { 7 | private readonly SpryInsert _spry; 8 | 9 | public SpryInsertTable(string tableName, string schema, SpryInsert spry) 10 | : base(tableName, schema) 11 | { 12 | _spry = spry; 13 | } 14 | 15 | protected override SpryInsertTable TableImpl 16 | { 17 | get { return this; } 18 | } 19 | 20 | public override string Build() 21 | { 22 | var returnString = _spry.Build(); 23 | 24 | if (string.IsNullOrWhiteSpace(ExtraQuery)) 25 | { 26 | returnString += ExtraQuery; 27 | } 28 | 29 | return returnString; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Spry/Table/IConditionItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Spry.Where; 4 | 5 | namespace Spry.Table 6 | { 7 | public interface IConditionItem where TTable : SpryTable 8 | { 9 | Where Where(Expression> columnExpression, string colPrefix = null); 10 | Where AndWhere(Expression> columnExpression, string colPrefix = null); 11 | Where OrWhere(Expression> columnExpression, string colPrefix = null); 12 | 13 | Where Where(string columnName); 14 | Where OrWhere(string columnName); 15 | Where AndWhere(string columnName); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Spry/Insert/SpryInsert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spry.Table; 3 | 4 | namespace Spry.Insert 5 | { 6 | public class SpryInsert 7 | { 8 | private InsertValue _value; 9 | private SpryInsertTable _spryTable; 10 | private string _table; 11 | private string _dbSchema; 12 | 13 | private SpryInsert() 14 | { 15 | 16 | } 17 | 18 | public static InsertValue Insert(string tableName, string dbSchema) 19 | { 20 | var spry = new SpryInsert 21 | { 22 | _table = tableName, 23 | _dbSchema = dbSchema, 24 | }; 25 | 26 | spry._spryTable = new SpryInsertTable(tableName, dbSchema, spry); 27 | return spry._value = new InsertValue(spry, spry._spryTable); 28 | } 29 | 30 | public string Build() 31 | { 32 | return "INSERT INTO " + _dbSchema + "." + _table + Environment.NewLine + _value.BuildImpl(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish_nuget: 9 | if: ${{ github.event.release.target_commitish == 'master' && !github.event.release.draft}} 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | name: Checkout Repository 15 | 16 | - name: Use .NET Core 3.1.301 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.301 20 | 21 | - name: dotnet restore 22 | run: dotnet restore /nologo 23 | 24 | - name: dotnet build & pack 25 | run: | 26 | dotnet build -c Release --no-restore --nologo 27 | dotnet pack Spry/Spry.csproj -c Release --no-build --nologo -o package 28 | 29 | - name: dotnet push to NuGet 30 | working-directory: package 31 | run: | 32 | dotnet nuget push ${PACKAGE_NAME} --api-key ${NUGET_API_KEY} --source https://api.nuget.org/v3/index.json 33 | env: 34 | PACKAGE_NAME: ${{ format('Spry.{0}.nupkg', github.event.release.tag_name) }} 35 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Vignesh.N 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Spry/Update/SpryUpdate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Spry.Update 4 | { 5 | public class SpryUpdate 6 | { 7 | private string _table; 8 | private string _dbSchema; 9 | private UpdateValue _value; 10 | private SpryUpdateTable _spryTable; 11 | 12 | internal SpryUpdate() 13 | { 14 | } 15 | 16 | public static UpdateValue Update(string tableName, string dbSchema) 17 | { 18 | var spry = new SpryUpdate 19 | { 20 | _table = tableName, 21 | _dbSchema = dbSchema, 22 | }; 23 | 24 | spry._spryTable = new SpryUpdateTable(spry, tableName, dbSchema); 25 | return spry._value = new UpdateValue(spry, spry._spryTable); 26 | } 27 | 28 | public string Build() 29 | { 30 | return "UPDATE " + _dbSchema + "." + _table + Environment.NewLine + "SET" + Environment.NewLine 31 | + _value.BuildImpl() 32 | + _spryTable.BuildImpl(); 33 | } 34 | } 35 | 36 | 37 | } -------------------------------------------------------------------------------- /Spry/Table/Join.cs: -------------------------------------------------------------------------------- 1 | using Spry.Select; 2 | 3 | namespace Spry.Table 4 | { 5 | public abstract class Join 6 | { 7 | protected readonly SprySelect Spry; 8 | protected readonly SprySelectTable TableOne; 9 | protected readonly SprySelectTable TableTwo; 10 | protected string OnCondition; 11 | 12 | protected Join(SprySelect spry, SprySelectTable tableOne, SprySelectTable tableTwo) 13 | { 14 | TableTwo = tableTwo; 15 | TableOne = tableOne; 16 | Spry = spry; 17 | } 18 | 19 | public virtual SprySelectTable On(string colOne, string colTwo) 20 | { 21 | OnCondition = " ON " + colOne + " = " + colTwo; 22 | return TableOne; 23 | } 24 | 25 | public virtual SprySelectTable On(string onCondition) 26 | { 27 | OnCondition = onCondition; 28 | return TableOne; 29 | } 30 | 31 | public virtual string Build() 32 | { 33 | return Spry.Build(); 34 | } 35 | 36 | internal abstract string BuildImpl(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Spry.Tests/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using System.Data.SqlClient; 2 | 3 | namespace Spry.Tests 4 | { 5 | public abstract class BaseTest 6 | { 7 | private const string CONNECTION_STRING = @"Data Source=(LocalDB)\v11.0;AttachDbFilename=C:\Workspace\Spry\Spry.Tests\TestDatabase.mdf;Integrated Security=True;Connect Timeout=30"; 8 | 9 | protected const string CUSTOMER_TABLE = @"Customer"; 10 | 11 | public SqlConnection CreateConnection() 12 | { 13 | var connection = new SqlConnection(CONNECTION_STRING); 14 | connection.Open(); 15 | return connection; 16 | } 17 | } 18 | 19 | public interface ISqlConnectionFactory 20 | { 21 | SqlConnection CreateConnection(); 22 | } 23 | 24 | public class TestConnectionFactory : ISqlConnectionFactory 25 | { 26 | private const string CONNECTION_STRING = @"Data Source=(LocalDB)\v11.0;AttachDbFilename=C:\Workspace\Spry\Spry.Tests\TestDatabase.mdf;Integrated Security=True;Connect Timeout=30"; 27 | 28 | public SqlConnection CreateConnection() 29 | { 30 | var connection = new SqlConnection(CONNECTION_STRING); 31 | connection.Open(); 32 | return connection; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Spry/Update/SpryUpdateTable.cs: -------------------------------------------------------------------------------- 1 | using Spry.Table; 2 | 3 | namespace Spry.Update 4 | { 5 | public class SpryUpdateTable : SpryTable> 6 | { 7 | private readonly SpryUpdate _spry; 8 | 9 | internal SpryUpdateTable(SpryUpdate spry, string tableName, string schema) 10 | : base(tableName, schema) 11 | { 12 | _spry = spry; 13 | } 14 | 15 | protected override SpryUpdateTable TableImpl 16 | { 17 | get { return this; } 18 | } 19 | 20 | public override string Build() 21 | { 22 | return _spry.Build(); 23 | } 24 | 25 | internal string BuildImpl() 26 | { 27 | string returnString = null; 28 | 29 | if (WhereCondition != null) 30 | { 31 | returnString += "WHERE " + WhereCondition.BuildImpl(); 32 | 33 | foreach (var andCondition in AndConditions) 34 | { 35 | returnString += " AND " + andCondition.BuildImpl(); 36 | } 37 | 38 | foreach (var orCondition in OrConditions) 39 | { 40 | returnString += " OR " + orCondition.BuildImpl(); 41 | } 42 | 43 | } 44 | 45 | return returnString; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Spry/SqlExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using Dapper; 4 | 5 | namespace Spry 6 | { 7 | public class SqlExecutor : IExecutable 8 | { 9 | private readonly string _query; 10 | 11 | public SqlExecutor(string query) 12 | { 13 | _query = query; 14 | } 15 | 16 | public int Execute(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 17 | { 18 | return connection.Execute(_query, parameters, commandType: commandType); 19 | } 20 | 21 | public IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 22 | { 23 | return connection.Query(_query, parameters, commandType: commandType); 24 | } 25 | 26 | public IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 27 | { 28 | return connection.Query(_query, parameters, commandType: commandType); 29 | } 30 | 31 | public TColType ExecuteScalar(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 32 | { 33 | return connection.ExecuteScalar(_query, parameters, commandType: commandType); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Spry/Spry.cs: -------------------------------------------------------------------------------- 1 | using Spry.Delete; 2 | using Spry.Insert; 3 | using Spry.Select; 4 | using Spry.Update; 5 | 6 | namespace Spry 7 | { 8 | public static class Spry 9 | { 10 | public static SprySelectColumn Select() 11 | { 12 | return SprySelect.Select(); 13 | } 14 | 15 | public static SprySelectColumn Select() 16 | { 17 | return SprySelect.Select(); 18 | } 19 | 20 | public static InsertValue InsertInto(string tableName, string dbSchema = "dbo") 21 | { 22 | return SpryInsert.Insert(tableName, dbSchema); 23 | } 24 | 25 | public static InsertValue InsertInto(string tableName, string dbSchema = "dbo") 26 | { 27 | return SpryInsert.Insert(tableName, dbSchema); 28 | } 29 | 30 | public static UpdateValue Update(string tableName, string dbSchema = "dbo") 31 | { 32 | return SpryUpdate.Update(tableName, dbSchema); 33 | } 34 | 35 | public static UpdateValue Update(string tableName, string dbSchema = "dbo") 36 | { 37 | return SpryUpdate.Update(tableName, dbSchema); 38 | } 39 | 40 | public static SpryDelete Delete() 41 | { 42 | return new SpryDelete(); 43 | } 44 | 45 | public static SpryDelete Delete() 46 | { 47 | return new SpryDelete(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Spry/Delete/SpryDeleteTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spry.Table; 3 | 4 | namespace Spry.Delete 5 | { 6 | public class SpryDeleteTable : SpryTable> 7 | { 8 | private readonly SpryDelete _spry; 9 | 10 | internal SpryDeleteTable(SpryDelete spry, string tableName, string schema = "dbo") 11 | : base(tableName, schema) 12 | { 13 | _spry = spry; 14 | } 15 | 16 | protected override SpryDeleteTable TableImpl 17 | { 18 | get { return this; } 19 | } 20 | 21 | public override string Build() 22 | { 23 | return _spry.Build(); 24 | } 25 | 26 | internal string BuildImpl() 27 | { 28 | string returnString = BuildImpl(this); 29 | 30 | returnString += Environment.NewLine; 31 | 32 | if (WhereCondition != null) 33 | { 34 | returnString += "WHERE " + WhereCondition.BuildImpl(); 35 | 36 | foreach (var andCondition in AndConditions) 37 | { 38 | returnString += " AND " + andCondition.BuildImpl(); 39 | } 40 | 41 | foreach (var orCondition in OrConditions) 42 | { 43 | returnString += " OR " + orCondition.BuildImpl(); 44 | } 45 | } 46 | 47 | if (!string.IsNullOrWhiteSpace(ExtraQuery)) 48 | { 49 | returnString += ExtraQuery; 50 | } 51 | 52 | return returnString; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Spry.Console/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Spry.Console 2 | { 3 | using System; 4 | 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | var myDto = new MyDto { Id = 1, IsDeleted = true }; 10 | 11 | Spry.Select().Column(_ => myDto.Id).Column(_ => myDto.IsDeleted).From("tt") 12 | .InnerJoin("table2", "audit").On("c1", "d1") 13 | .InnerJoin("table3", "audit").On("c2", "d2") 14 | .InnerJoin("table4", "audit").On("c4", "d2") 15 | .InSchema("review") 16 | .Where(_ => myDto.Id).EqualTo(1) 17 | .AndWhere(_ => myDto.Id).InBetween(1, 10) 18 | .AndWhere(_ => myDto.Id).GreaterThan(5) 19 | .Build(); 20 | 21 | Spry.InsertInto("tableOne", "review") 22 | .Value("One", 1) 23 | .Value(_ => myDto.Id) 24 | .OutputIdentity() 25 | .Execute(null); 26 | 27 | Spry.Update("tableOne") 28 | .Set(_ => myDto.Id) 29 | .Where("id").EqualTo(1) 30 | .Execute(null); 31 | 32 | Console.ReadLine(); 33 | } 34 | } 35 | 36 | public class MyDto 37 | { 38 | public int Id { get; set; } 39 | 40 | public string Name { get; set; } 41 | 42 | public DateTime Today { get; set; } 43 | 44 | public bool IsDeleted { get; set; } 45 | } 46 | 47 | public enum EvidenceStatus : uint 48 | { 49 | NotApplicable = 0, 50 | 51 | Requested = 1, 52 | 53 | Awaiting = 2, 54 | 55 | Received = 3 56 | } 57 | } -------------------------------------------------------------------------------- /Spry/SpryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Spry 2 | { 3 | using System; 4 | using System.Linq.Expressions; 5 | 6 | internal static class SpryExpression 7 | { 8 | internal static string GetColumnName(Expression> columnExpression) 9 | { 10 | var memberExpression = columnExpression.Body as MemberExpression; 11 | 12 | if (memberExpression == null) 13 | { 14 | var contantExpression = columnExpression.Body as ConstantExpression; 15 | 16 | if (contantExpression == null) 17 | throw new ArgumentException("Invalid Column Expression"); 18 | 19 | return Convert.ToString(contantExpression.Value); 20 | } 21 | 22 | return memberExpression.Member.Name; 23 | } 24 | 25 | internal static object GetColumnValue(Expression> columnExpression) 26 | { 27 | Expression memberExpression = columnExpression.Body as MemberExpression; 28 | 29 | if (memberExpression == null) 30 | { 31 | var contantExpression = columnExpression.Body as ConstantExpression; 32 | 33 | if (contantExpression == null) 34 | throw new ArgumentException("Invalid Column Expression"); 35 | 36 | memberExpression = contantExpression; 37 | } 38 | 39 | var valueExpression = Expression.Convert(memberExpression, typeof(TProperty)); 40 | 41 | var valueFunc = Expression.Lambda>(valueExpression).Compile(); 42 | 43 | return valueFunc(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Spry/SpryParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Spry 6 | { 7 | public class SpryParameter 8 | { 9 | public SpryParameter(string name, object value) 10 | { 11 | Name = name; 12 | Value = value; 13 | } 14 | 15 | public string Name { get; set; } 16 | 17 | public object Value { get; set; } 18 | } 19 | 20 | public class SpryParameters : IEnumerable> 21 | { 22 | private readonly List _parameters = new List(); 23 | 24 | public SpryParameters Add(string name, object value) 25 | { 26 | _parameters.Add(new SpryParameter(name, value)); 27 | return this; 28 | } 29 | 30 | public SpryParameters Add(SpryParameter parameter) 31 | { 32 | _parameters.Add(parameter); 33 | return this; 34 | } 35 | 36 | public IEnumerable Parameters 37 | { 38 | get { return _parameters; } 39 | } 40 | 41 | public void Add(SpryParameters parameters) 42 | { 43 | if (parameters != null && parameters._parameters.Any()) 44 | { 45 | _parameters.AddRange(parameters._parameters); 46 | } 47 | } 48 | 49 | public IEnumerator> GetEnumerator() 50 | { 51 | foreach (var spryParameter in _parameters) 52 | { 53 | yield return new KeyValuePair(spryParameter.Name, spryParameter.Value); 54 | } 55 | } 56 | 57 | IEnumerator IEnumerable.GetEnumerator() 58 | { 59 | return GetEnumerator(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Spry/Select/SprySelectColumn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Text; 4 | 5 | namespace Spry.Select 6 | { 7 | public sealed class SprySelectColumn 8 | { 9 | private readonly SprySelect _spry; 10 | private readonly StringBuilder _columnBuilder; 11 | private SprySelectTable _from; 12 | 13 | internal SprySelectColumn(SprySelect spry) 14 | { 15 | _columnBuilder = new StringBuilder(); 16 | _spry = spry; 17 | } 18 | 19 | public SprySelectColumn Column(Expression> columnExpression) 20 | { 21 | var columnName = SpryExpression.GetColumnName(columnExpression); 22 | AppendColumn(columnName); 23 | return this; 24 | } 25 | 26 | public SprySelectColumn Column(Expression> columnExpression, string colPrefix) 27 | { 28 | var columnName = SpryExpression.GetColumnName(columnExpression); 29 | AppendColumn(colPrefix + "." + columnName); 30 | return this; 31 | } 32 | 33 | public SprySelectColumn Column(string columnName) 34 | { 35 | AppendColumn(columnName); 36 | return this; 37 | } 38 | 39 | public SprySelectTable From(string tableName) 40 | { 41 | return _from = new SprySelectTable(_spry, tableName); 42 | } 43 | 44 | private void AppendColumn(string columnName) 45 | { 46 | _columnBuilder.Append(columnName + ", "); 47 | } 48 | 49 | internal string BuildImpl() 50 | { 51 | RemoveTrailingComma(); 52 | return _columnBuilder + Environment.NewLine + "FROM" + Environment.NewLine + _from.BuildImpl(); 53 | } 54 | 55 | private void RemoveTrailingComma() 56 | { 57 | _columnBuilder.Length -= 2; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Spry.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spry", "Spry\Spry.csproj", "{46AD06AC-F969-4402-8881-41C80607E756}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spry.Console", "Spry.Console\Spry.Console.csproj", "{A74CDA33-434D-4601-B0A9-9907475DAA51}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spry.Tests", "Spry.Tests\Spry.Tests.csproj", "{7D377906-64B6-4DC2-9F7E-489646B63CAC}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {46AD06AC-F969-4402-8881-41C80607E756}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {46AD06AC-F969-4402-8881-41C80607E756}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {46AD06AC-F969-4402-8881-41C80607E756}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {46AD06AC-F969-4402-8881-41C80607E756}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {A74CDA33-434D-4601-B0A9-9907475DAA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A74CDA33-434D-4601-B0A9-9907475DAA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A74CDA33-434D-4601-B0A9-9907475DAA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {A74CDA33-434D-4601-B0A9-9907475DAA51}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {7D377906-64B6-4DC2-9F7E-489646B63CAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {7D377906-64B6-4DC2-9F7E-489646B63CAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {7D377906-64B6-4DC2-9F7E-489646B63CAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {7D377906-64B6-4DC2-9F7E-489646B63CAC}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {B22E3757-463F-41A6-AA28-47B27EA167C9} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Spry.Tests/Benchmarks/DapperDirectVsSpryInsertBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using NUnit.Framework; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Running; 6 | using Spry.Tests.Dto; 7 | 8 | 9 | namespace Spry.Tests.Benchmarks 10 | { 11 | [TestFixture] 12 | public class DapperDirectVsSpryBenchmarkRunner 13 | { 14 | [Test] 15 | public void RunCustomerRepositoryBenchmarks() 16 | { 17 | var benchmark = BenchmarkRunner.Run(); 18 | } 19 | } 20 | 21 | public class DapperDirectVsSpryInsertBenchmark 22 | { 23 | private readonly TestConnectionFactory _connectionFactory = new TestConnectionFactory(); 24 | private readonly CustomerRepository _customerRepository; 25 | 26 | public DapperDirectVsSpryInsertBenchmark() 27 | { 28 | _customerRepository = new CustomerRepository(_connectionFactory); 29 | } 30 | 31 | [Benchmark] 32 | public void Spry_Insert_CheckOutputIdentity() 33 | { 34 | var customer = new Customer 35 | { 36 | DateOfBirth = DateTime.Today, 37 | Name = "John Doe" 38 | }; 39 | 40 | customer.CustomerId = _customerRepository.Create(customer.Name, customer.DateOfBirth); 41 | 42 | Assert.IsTrue(customer.CustomerId > 0); 43 | } 44 | 45 | [Benchmark] 46 | public void Dapper_Insert_CheckOutputIdentity() 47 | { 48 | var customer = new Customer 49 | { 50 | DateOfBirth = DateTime.Today, 51 | Name = "John Doe" 52 | }; 53 | 54 | using (var connection = _connectionFactory.CreateConnection()) 55 | { 56 | customer.CustomerId = connection.ExecuteScalar(@"INSERT INTO dbo.Customer 57 | (Name, DateofBirth) 58 | OUTPUT Inserted.CustomerId 59 | VALUES (@Name, @DateOfBirth);", customer); 60 | } 61 | 62 | Assert.IsTrue(customer.CustomerId > 0); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Spry/Table/SpryTableConditions.cs: -------------------------------------------------------------------------------- 1 | using Spry.Where; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace Spry.Table 6 | { 7 | public abstract partial class SpryTable where TTable : SpryTable 8 | { 9 | public Where Where(Expression> columnExpression, string colPrefix = null) 10 | { 11 | var columnName = SpryExpression.GetColumnName(columnExpression); 12 | if (!string.IsNullOrWhiteSpace(colPrefix)) 13 | { 14 | columnName = colPrefix + "." + columnName; 15 | } 16 | var where = new Where(TableImpl, Parameters, columnName); 17 | WhereCondition = where; 18 | return where; 19 | } 20 | 21 | public Where Where(string columnName) 22 | { 23 | var where = new Where(TableImpl, Parameters, columnName); 24 | WhereCondition = where; 25 | return where; 26 | } 27 | 28 | public Where AndWhere(Expression> columnExpression, string colPrefix = null) 29 | { 30 | var columnName = SpryExpression.GetColumnName(columnExpression); 31 | if (!string.IsNullOrWhiteSpace(colPrefix)) columnName = colPrefix + "." + columnName; 32 | var andWhere = new Where(TableImpl, Parameters, columnName); 33 | AndConditions.Add(andWhere); 34 | return andWhere; 35 | } 36 | 37 | public Where AndWhere(string columnName) 38 | { 39 | var andWhere = new Where(TableImpl, Parameters, columnName); 40 | AndConditions.Add(andWhere); 41 | return andWhere; 42 | } 43 | 44 | public Where OrWhere(Expression> columnExpression, string colPrefix = null) 45 | { 46 | var columnName = SpryExpression.GetColumnName(columnExpression); 47 | if (!string.IsNullOrWhiteSpace(colPrefix)) columnName = colPrefix + "." + columnName; 48 | var orWhere = new Where(TableImpl, Parameters, columnName); 49 | OrConditions.Add(orWhere); 50 | return orWhere; 51 | } 52 | 53 | public Where OrWhere(string columnName) 54 | { 55 | var orWhere = new Where(TableImpl, Parameters, columnName); 56 | OrConditions.Add(orWhere); 57 | return orWhere; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Spry/Update/UpdateValue.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System; 3 | using System.Text; 4 | using Spry.Table; 5 | using Spry.Where; 6 | 7 | namespace Spry.Update 8 | { 9 | public class UpdateValue : IConditionItem> 10 | { 11 | private readonly SpryUpdate _spry; 12 | private readonly SpryUpdateTable _table; 13 | private readonly StringBuilder _updateBuilder; 14 | 15 | public UpdateValue(SpryUpdate spry, SpryUpdateTable table) 16 | { 17 | _spry = spry; 18 | _table = table; 19 | _updateBuilder = new StringBuilder(); 20 | } 21 | 22 | public UpdateValue Set(Expression> valueExpression) 23 | { 24 | var columnName = SpryExpression.GetColumnName(valueExpression); 25 | var value = SpryExpression.GetColumnValue(valueExpression); 26 | SetValueImpl(columnName, value); 27 | return this; 28 | } 29 | 30 | public UpdateValue Set(string columnName, object value) 31 | { 32 | SetValueImpl(columnName, value); 33 | return this; 34 | } 35 | 36 | private void SetValueImpl(string columnName, object value) 37 | { 38 | _table.AddParameter(columnName, value); 39 | _updateBuilder.AppendLine(columnName + " = @" + columnName + ", "); 40 | } 41 | 42 | public string Build() 43 | { 44 | return _spry.Build(); 45 | } 46 | 47 | internal string BuildImpl() 48 | { 49 | RemoveTrailingCommas(); 50 | return _updateBuilder + Environment.NewLine; 51 | } 52 | 53 | private void RemoveTrailingCommas() 54 | { 55 | _updateBuilder.Length -= 4; 56 | } 57 | 58 | public Where> Where(Expression> columnExpression, string colPrefix = null) 59 | { 60 | return _table.Where(columnExpression, colPrefix); 61 | } 62 | 63 | public Where> AndWhere(Expression> columnExpression, string colPrefix = null) 64 | { 65 | return _table.AndWhere(columnExpression, colPrefix); 66 | } 67 | 68 | public Where> OrWhere(Expression> columnExpression, string colPrefix = null) 69 | { 70 | return _table.OrWhere(columnExpression, colPrefix); 71 | } 72 | 73 | public Where> Where(string columnName) 74 | { 75 | return _table.Where(columnName); 76 | } 77 | 78 | public Where> OrWhere(string columnName) 79 | { 80 | return _table.OrWhere(columnName); 81 | } 82 | 83 | public Where> AndWhere(string columnName) 84 | { 85 | return _table.AndWhere(columnName); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Spry/Where/Where.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Spry.Table; 5 | 6 | namespace Spry.Where 7 | { 8 | public class Where : Buildable where TTable : SpryTable 9 | { 10 | private readonly TTable _table; 11 | private readonly SpryParameters _parameters; 12 | private readonly string _columnName; 13 | private readonly string _columnParameterName; 14 | private readonly StringBuilder _whereBuilder; 15 | 16 | internal Where(TTable table, SpryParameters parameters, string columnName) 17 | { 18 | _whereBuilder = new StringBuilder(); 19 | _table = table; 20 | _parameters = parameters; 21 | _columnName = columnName.Replace(";", ""); 22 | _columnParameterName = CleanParameterName(_columnName); 23 | } 24 | 25 | public TTable EqualTo(TProperty value) 26 | { 27 | _parameters.Add(_columnParameterName, value); 28 | _whereBuilder.AppendFormat(@"{0} = @{1}", _columnName, _columnParameterName); 29 | return _table; 30 | } 31 | 32 | public TTable In(IEnumerable value) 33 | { 34 | _parameters.Add(_columnParameterName, value); 35 | _whereBuilder.AppendFormat(@"{0} IN @{1}", _columnName, _columnParameterName); 36 | return _table; 37 | } 38 | 39 | public TTable GreaterThan(TProperty value) 40 | { 41 | _parameters.Add(_columnParameterName, value); 42 | _whereBuilder.AppendFormat(@"{0} > @{1}", _columnName, _columnParameterName); 43 | return _table; 44 | } 45 | 46 | public TTable LessThan(TProperty value) 47 | { 48 | _parameters.Add(_columnParameterName, value); 49 | _whereBuilder.AppendFormat(@"{0} < @{1}", _columnName, _columnParameterName); 50 | return _table; 51 | } 52 | 53 | public TTable LessThanOrEqualTo(TProperty value) 54 | { 55 | _parameters.Add(_columnParameterName, value); 56 | _whereBuilder.AppendFormat(@"{0} <= @{1}", _columnName, _columnParameterName); 57 | return _table; 58 | } 59 | 60 | public TTable GreaterThanOrEqualTo(TProperty value) 61 | { 62 | _parameters.Add(_columnParameterName, value); 63 | _whereBuilder.AppendFormat(@"{0} >= @{1}", _columnName, _columnParameterName); 64 | return _table; 65 | } 66 | 67 | public TTable InBetween(TProperty valueOne, TProperty valueTwo) 68 | { 69 | _parameters.Add(_columnParameterName, valueOne); 70 | _parameters.Add(_columnParameterName + "valueTwo", valueTwo); 71 | _whereBuilder.AppendFormat(@"{0} BETWEEN @{1} AND @{1}valueTwo", _columnName, _columnParameterName); 72 | return _table; 73 | } 74 | 75 | internal override string BuildImpl() 76 | { 77 | return _whereBuilder + Environment.NewLine; 78 | } 79 | 80 | private static string CleanParameterName(string columnName) 81 | { 82 | return "p" + columnName.Replace("@", "").Replace(".", "").Replace(";", "").Replace("-", ""); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Spry/Select/SprySelectTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Spry.Table; 4 | 5 | namespace Spry.Select 6 | { 7 | public sealed class SprySelectTable : SpryTable> 8 | { 9 | private readonly SprySelect _spry; 10 | 11 | private readonly List> _joins; 12 | 13 | public SprySelectTable(SprySelect spry, string tableName, string tableAlias = null, string schema = "dbo") 14 | : base(tableName, schema, tableAlias) 15 | { 16 | _joins = new List>(); 17 | _spry = spry; 18 | } 19 | 20 | public Join InnerJoin(string tableName, string tableAlias = null, string dbSchema = "dbo") 21 | { 22 | var innerJoin = new InnerJoin(_spry, this, new SprySelectTable(_spry, tableName, tableAlias, dbSchema)); 23 | _joins.Add(innerJoin); 24 | return innerJoin; 25 | } 26 | 27 | public Join LeftOuterJoin(string tableName, string tableAlias = null, string dbSchema = "dbo") 28 | { 29 | var leftOuterJoin = new LeftOuterJoin(_spry, this, new SprySelectTable(_spry, tableName, tableAlias, dbSchema)); 30 | _joins.Add(leftOuterJoin); 31 | return leftOuterJoin; 32 | } 33 | 34 | public Join RightOuterJoin(string tableName, string tableAlias = null, string dbSchema = "dbo") 35 | { 36 | var rightOuterJoin = new RightOuterJoin(_spry, this, new SprySelectTable(_spry, tableName, tableAlias, dbSchema)); 37 | _joins.Add(rightOuterJoin); 38 | return rightOuterJoin; 39 | } 40 | 41 | public Join FullOuterJoin(string tableName, string tableAlias = null, string dbSchema = "dbo") 42 | { 43 | var fullOuterJoin = new FullOuterJoin(_spry, this, new SprySelectTable(_spry, tableName, tableAlias, dbSchema)); 44 | _joins.Add(fullOuterJoin); 45 | return fullOuterJoin; 46 | } 47 | 48 | protected override SprySelectTable TableImpl 49 | { 50 | get { return this; } 51 | } 52 | 53 | public override string Build() 54 | { 55 | return _spry.Build(); 56 | } 57 | 58 | internal string BuildImpl() 59 | { 60 | string returnString = BuildImpl(this); 61 | 62 | returnString += Environment.NewLine; 63 | 64 | foreach (var joins in _joins) 65 | { 66 | returnString += joins.BuildImpl(); 67 | } 68 | 69 | if (WhereCondition != null) 70 | { 71 | returnString += "WHERE " + WhereCondition.BuildImpl(); 72 | 73 | foreach (var andCondition in AndConditions) 74 | { 75 | returnString += " AND " + andCondition.BuildImpl(); 76 | } 77 | 78 | foreach (var orCondition in OrConditions) 79 | { 80 | returnString += " OR " + orCondition.BuildImpl(); 81 | } 82 | } 83 | 84 | if (!string.IsNullOrWhiteSpace(ExtraQuery)) 85 | { 86 | returnString += ExtraQuery; 87 | } 88 | 89 | return returnString; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Spry/Table/SpryTable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | 4 | namespace Spry.Table 5 | { 6 | public abstract partial class SpryTable : IExecutable, IConditionItem where TTable : SpryTable 7 | { 8 | protected string TableName; 9 | protected string Schema; 10 | protected string Alias; 11 | 12 | internal readonly SpryParameters Parameters = new SpryParameters(); 13 | protected Buildable WhereCondition; 14 | protected readonly List AndConditions = new List(); 15 | protected readonly List OrConditions = new List(); 16 | protected string ExtraQuery = null; 17 | 18 | protected abstract TTable TableImpl { get; } 19 | 20 | protected SpryTable(string tableName, string schema) 21 | : this(tableName, schema, null) 22 | { 23 | } 24 | 25 | protected SpryTable(string tableName, string schema, string alias) 26 | { 27 | TableName = tableName; 28 | Schema = schema; 29 | Alias = alias; 30 | } 31 | 32 | public virtual string Build() 33 | { 34 | return BuildImpl(this); 35 | } 36 | 37 | internal static string BuildImpl(SpryTable table) 38 | { 39 | string returnString = null; 40 | 41 | if (string.IsNullOrWhiteSpace(table.Alias)) 42 | { 43 | returnString = table.Schema + "." + table.TableName; 44 | } 45 | else 46 | { 47 | returnString = string.Format("{0}.{1} AS {2}", table.Schema, table.TableName, table.Alias); 48 | } 49 | return returnString; 50 | } 51 | 52 | public TTable InSchema(string dbSchema) 53 | { 54 | Schema = dbSchema; 55 | return TableImpl; 56 | } 57 | 58 | public TTable As(string tableAlias) 59 | { 60 | Alias = tableAlias; 61 | return TableImpl; 62 | } 63 | 64 | public SpryTable AppendQuery(string extraQuery) 65 | { 66 | ExtraQuery = extraQuery; 67 | return this; 68 | } 69 | 70 | public int Execute(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 71 | { 72 | Parameters.Add(parameters); 73 | return new SqlExecutor(Build()).Execute(connection, commandType, Parameters); 74 | } 75 | 76 | public IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 77 | { 78 | Parameters.Add(parameters); 79 | return new SqlExecutor(Build()).Query(connection, commandType, Parameters); 80 | } 81 | 82 | public IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 83 | { 84 | Parameters.Add(parameters); 85 | return new SqlExecutor(Build()).Query(connection, commandType, Parameters); 86 | } 87 | 88 | public TColType ExecuteScalar(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 89 | { 90 | Parameters.Add(parameters); 91 | return new SqlExecutor(Build()).ExecuteScalar(connection, commandType, Parameters); 92 | } 93 | 94 | internal void AddParameter(string name, object value) 95 | { 96 | Parameters.Add(name, value); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Spry/Insert/InsertValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace Spry.Insert 8 | { 9 | public class InsertValue : IExecutable 10 | { 11 | private readonly SpryInsert _spry; 12 | private readonly SpryInsertTable _table; 13 | private readonly SpryParameters _parameters; 14 | private readonly StringBuilder _insertColumnBuilder; 15 | private readonly StringBuilder _insertValueBuilder; 16 | private string _outputCol = null; 17 | private bool _outputIdentity = false; 18 | 19 | internal InsertValue(SpryInsert spry, SpryInsertTable table) 20 | { 21 | _spry = spry; 22 | _table = table; 23 | _insertColumnBuilder = new StringBuilder(); 24 | _insertValueBuilder = new StringBuilder(); 25 | _parameters = table.Parameters; 26 | } 27 | 28 | public InsertValue Value(Expression> valueExpression) 29 | { 30 | var columnName = SpryExpression.GetColumnName(valueExpression); 31 | var value = SpryExpression.GetColumnValue(valueExpression); 32 | InsertValueImpl(columnName, value); 33 | return this; 34 | } 35 | 36 | public InsertValue Value(string columnName, object value) 37 | { 38 | InsertValueImpl(columnName, value); 39 | return this; 40 | } 41 | 42 | public InsertValue OutputInserted(string outputColumnName) 43 | { 44 | _outputCol = outputColumnName; 45 | return this; 46 | } 47 | 48 | public InsertValue OutputInserted(Expression> columnExpression) 49 | { 50 | _outputCol = SpryExpression.GetColumnName(columnExpression); 51 | return this; 52 | } 53 | 54 | public InsertValue OutputIdentity() 55 | { 56 | _outputIdentity = true; 57 | return this; 58 | } 59 | 60 | private void InsertValueImpl(string columnName, object value) 61 | { 62 | _parameters.Add(columnName, value); 63 | _insertColumnBuilder.Append(columnName + ", "); 64 | _insertValueBuilder.Append("@" + columnName + ", "); 65 | } 66 | 67 | public string Build() 68 | { 69 | return _spry.Build(); 70 | } 71 | 72 | 73 | 74 | internal string BuildImpl() 75 | { 76 | RemoveTrailingCommas(); 77 | 78 | if (!string.IsNullOrWhiteSpace(_outputCol)) 79 | { 80 | return string.Format(@"({0}){1}OUTPUT Inserted.{3}{1}VALUES{1}({2})", _insertColumnBuilder, Environment.NewLine, _insertValueBuilder, _outputCol); 81 | } 82 | 83 | string returnValue = string.Format(@"({0}){1}VALUES{1}({2})", _insertColumnBuilder, Environment.NewLine, _insertValueBuilder); 84 | 85 | if (_outputIdentity) 86 | { 87 | returnValue += @"; SELECT SCOPE_IDENTITY() AS [Inserted];"; 88 | } 89 | 90 | return returnValue; 91 | } 92 | 93 | private void RemoveTrailingCommas() 94 | { 95 | _insertColumnBuilder.Length -= 2; 96 | _insertValueBuilder.Length -= 2; 97 | } 98 | 99 | public int Execute(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 100 | { 101 | return _table.Execute(connection, parameters: parameters); 102 | } 103 | 104 | public IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 105 | { 106 | return _table.Query(connection, parameters: parameters); 107 | } 108 | 109 | public IEnumerable Query(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 110 | { 111 | return _table.Query(connection, parameters: parameters); 112 | } 113 | 114 | public TColType ExecuteScalar(IDbConnection connection, CommandType commandType = CommandType.Text, SpryParameters parameters = null) 115 | { 116 | return _table.ExecuteScalar(connection, parameters: parameters); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | Spry - a simple CRUD query builder extension for Dapper 2 | ======================================================= 3 | 4 | [![Build status](https://dev.azure.com/vigneshmsft/Spry/_apis/build/status/Spry-CI)](https://dev.azure.com/vigneshmsft/Spry/_build/latest?definitionId=3) 5 | 6 | What is Spry 7 | ------------ 8 | [Spry](https://www.nuget.org/packages/Spry) is a simple .NET library which allows you to write fluent CRUD queries 9 | and execute the queries using [Dapper](https://github.com/StackExchange/dapper-dot-net). 10 | 11 | Why Spry ? 12 | ---------- 13 | In one of my projects we decided not to write stored procedures 14 | and keep the queries within code, similar to the popular approach being followed by StackOverflow. 15 | The upside of this approach is that one tends to keep only specific CRUD logic in queries and 16 | helps to ensure business logics do not get inside your stored procedures. 17 | It is also easier to debug this way when one sees the actual query being executed on the server. 18 | We also decided not to use any ORM like Entity Framework or NHibernate and only use Dapper. 19 | You start to see code like this. 20 | 21 | ```csharp 22 | public bool InsertCustomer(CustomerDto newCustomer){ 23 | 24 | const string insertCustomerQuery = @"INSERT INTO dbo.Customer 25 | ([CustomerId],[Name],[PhoneNumber]) 26 | VALUES 27 | (@customerId, @name, @phoneNumber)"; 28 | 29 | var count = _connection.Execute(insertCustomerQuery, new { @customerId = newCustomer.CustomerId, 30 | @name= newCustomer.Name, 31 | @phoneNumber = newCustomer.PhoneNumber}); 32 | 33 | return count == 1; 34 | } 35 | ``` 36 | 37 | This is a relatively straightforward code but beings to break once you start making changes to the `Customer` table. 38 | Believe me changes to the tables are very commom, especially when a project is in its nascent stages. 39 | The moment you decide to rename few columns or add new columns you need to remember to do a find and replace all occurances of 40 | those strings in your solution, which is not a very efficient way of doing things. 41 | This is precisely why most people decide to use an ORM and let if handle all these hassles for you. 42 | But what if you don't want to use a ORM and at the same time not have this problem ? 43 | 44 | What can Spry do ? 45 | ------------------- 46 | Simple Insert 47 | ```csharp 48 | var customerInserted = Spry.InsertInto(CUSTOMER_TABLE) 49 | .Value(_ => customer.CustomerId) 50 | .Value(_ => customer.Name) 51 | .Value(_ => customer.DateOfBirth) 52 | .ExecuteScalar(connection) > 0; 53 | ``` 54 | Insert and output inserted identity 55 | ```chsarp 56 | customer.CustomerId = Spry.InsertInto(CUSTOMER_TABLE) 57 | .Value(_ => customer.Name) 58 | .Value(_ => customer.DateOfBirth) 59 | .OutputInserted(_ => customer.CustomerId) // or .OutputIdentity() 60 | .ExecuteScalar(connection); 61 | ``` 62 | Simple Select 63 | ```csharp 64 | return Spry.Select() 65 | .Column(_ => _.CustomerId) 66 | .Column(_ => _.Name) 67 | .Column(_ => _.DateOfBirth) 68 | .From(CUSTOMER_TABLE).InSchema("dbo") 69 | .Where(_ => _.CustomerId).EqualTo(customerId) 70 | .Query(connection).SingleOrDefault(); 71 | ``` 72 | Select from multiple tables. 73 | ```csharp 74 | Spry.Select() 75 | .Column(_ => _.CustomerId, "C") 76 | .Column(_ => _.Name) 77 | .Column(_ => _.DateOfBirth) 78 | .Column(_ => _.Address.City) 79 | .Column(_ => _.Address.Country) 80 | .Column(_ => _.Address.PostCode) 81 | .Column(_ => _.Address.CustomerAddressId) 82 | .Column(_ => _.Address.LineOne) 83 | .From(CUSTOMER_TABLE).As("C").InSchema("dbo") 84 | .InnerJoin(CUSTOMER_ADDRESS_TABLE, "CA").On("CA.CustomerId", "C.CustomerId") 85 | .Where(_ => _.CustomerId, "C").EqualTo(customerId) 86 | .Query(connection).SingleOrDefault(); 87 | ``` 88 | Update 89 | ```csharp 90 | var rowsUpdated = Spry.Update(CUSTOMER_TABLE) 91 | .Set(_ => name) 92 | .Set(_ => dateOfBirth) 93 | .Where(_ => customerId).EqualTo(customerId) 94 | .Execute(connection); 95 | 96 | return rowsUpdated > 0; 97 | ``` 98 | Delete 99 | ```csharp 100 | var rowsDeleted = Spry.Delete() 101 | .From(CUSTOMER_TABLE) 102 | .Where(_ => customerId).EqualTo(customerId) 103 | .Execute(connection); 104 | 105 | return rowsDeleted == 1; 106 | ``` 107 | All the above types have a `.Build()` method that lets you view the actual generated query. 108 | 109 | Installation 110 | ------------ 111 | Download and try Spry for yourself and let [me](https://twitter.com/N_Vignesh) know how it works or does not work for you. :) 112 | ```shell 113 | PM > Install-Package Spry 114 | ``` 115 | -------------------------------------------------------------------------------- /Spry.Tests/RepositoryTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using NUnit.Framework; 4 | using Spry.Tests.Dto; 5 | 6 | namespace Spry.Tests 7 | { 8 | [TestFixture] 9 | public class RepositoryTest : BaseTest 10 | { 11 | private readonly TestConnectionFactory _connectionFactory = new TestConnectionFactory(); 12 | private CustomerRepository _customerRepository; 13 | 14 | [SetUp] 15 | public void TestMethodInitialize() 16 | { 17 | _customerRepository = new CustomerRepository(_connectionFactory); 18 | } 19 | 20 | [Test] 21 | public void Insert_CheckOutputIdentity() 22 | { 23 | var customer = new Customer 24 | { 25 | DateOfBirth = DateTime.Today, 26 | Name = "John Doe" 27 | }; 28 | 29 | customer.CustomerId = _customerRepository.Create(customer.Name, customer.DateOfBirth); 30 | 31 | Assert.IsTrue(customer.CustomerId > 0); 32 | } 33 | 34 | [Test] 35 | public void InnerJoin_CheckInnerJoin() 36 | { 37 | var customer = new Customer 38 | { 39 | CustomerId = _customerRepository.Create("John Doe", DateTime.Today) 40 | }; 41 | 42 | customer.Address = new CustomerAddress 43 | { 44 | CustomerId = customer.CustomerId, 45 | City = "MyCity", 46 | Country = "Isle of Man", 47 | LineOne = "Street of Man", 48 | PostCode = "PostCode" 49 | 50 | }; 51 | 52 | customer.Address.CustomerAddressId = _customerRepository.CreateAddress(customer.Address); 53 | 54 | var savedCustomer = _customerRepository.ReadComplete(customer.CustomerId); 55 | 56 | Assert.IsNotNull(savedCustomer.Address); 57 | 58 | Assert.AreEqual(customer.Address.City, savedCustomer.Address.City); 59 | Assert.AreEqual(customer.Address.PostCode, savedCustomer.Address.PostCode); 60 | Assert.AreEqual(customer.Address.Country, savedCustomer.Address.Country); 61 | Assert.AreEqual(customer.Address.LineOne, savedCustomer.Address.LineOne); 62 | } 63 | 64 | [Test] 65 | public void Insert_CheckOutputInserted() 66 | { 67 | var customer = new Customer 68 | { 69 | DateOfBirth = DateTime.Today, 70 | Name = "John Doe" 71 | }; 72 | 73 | using (var connection = CreateConnection()) 74 | { 75 | customer.CustomerId = Spry.InsertInto(CUSTOMER_TABLE) 76 | .Value(_ => customer.Name) 77 | .Value(_ => customer.DateOfBirth) 78 | .OutputInserted(_ => customer.CustomerId) 79 | .ExecuteScalar(connection); 80 | } 81 | 82 | Assert.IsTrue(customer.CustomerId > 0); 83 | } 84 | 85 | [Test] 86 | public void Update_CheckUpdated() 87 | { 88 | var customer = new Customer 89 | { 90 | CustomerId = _customerRepository.Create("John", DateTime.Today), 91 | DateOfBirth = DateTime.Today.AddDays(-10), 92 | Name = "Mr John Doe" 93 | }; 94 | 95 | var updated = _customerRepository.Update(customer.CustomerId, customer.Name, customer.DateOfBirth); 96 | 97 | Assert.IsTrue(updated); 98 | 99 | var updatedCustomer = _customerRepository.Read(customer.CustomerId); 100 | 101 | Assert.AreEqual(customer.DateOfBirth, updatedCustomer.DateOfBirth); 102 | Assert.AreEqual(customer.Name, updatedCustomer.Name); 103 | } 104 | 105 | [Test] 106 | public void UpdateColumInWhereClause_CheckUpdated() 107 | { 108 | var customer = new Customer 109 | { 110 | CustomerId = _customerRepository.Create("John", DateTime.Today), 111 | }; 112 | 113 | customer = _customerRepository.Read(customer.CustomerId); 114 | 115 | const string newName = "John Doe"; 116 | var updated = _customerRepository.UpdateByName("John", newName); 117 | 118 | Assert.IsTrue(updated); 119 | 120 | var updatedCustomer = _customerRepository.Read(customer.CustomerId); 121 | 122 | Assert.AreEqual(customer.DateOfBirth, updatedCustomer.DateOfBirth); 123 | Assert.AreEqual(newName, updatedCustomer.Name); 124 | } 125 | 126 | [Test] 127 | public void DeleteCustomer_CheckDeleted() 128 | { 129 | var customer = new Customer 130 | { 131 | DateOfBirth = DateTime.Today, 132 | Name = "John Doe", 133 | CustomerId = _customerRepository.Create("John Doe", DateTime.Today) 134 | }; 135 | 136 | var deleted = _customerRepository.Delete(customer.CustomerId); 137 | 138 | Assert.IsTrue(deleted); 139 | } 140 | 141 | [Test] 142 | public void CleanUp() 143 | { 144 | using (var connection = _connectionFactory.CreateConnection()) 145 | { 146 | Spry.Delete().From("CustomerAddress").Execute(connection); 147 | Spry.Delete().From("Customer").Execute(connection); 148 | } 149 | } 150 | 151 | [Test] 152 | public void SqlInjection_InWhereColumnCheckThrowsException() 153 | { 154 | var customer = new Customer 155 | { 156 | CustomerId = _customerRepository.Create("John", DateTime.Today), 157 | }; 158 | 159 | customer = _customerRepository.Read(customer.CustomerId); 160 | 161 | Assert.Throws(typeof(SqlException), () => _customerRepository.UpdateSqlInjection(customer.CustomerId, "Customer")); 162 | } 163 | 164 | [Test] 165 | public void SqlInjection_InParameterValueCheckThrowsException() 166 | { 167 | var customer = new Customer 168 | { 169 | CustomerId = _customerRepository.Create("John", DateTime.Today), 170 | }; 171 | 172 | customer = _customerRepository.Read(customer.CustomerId); 173 | 174 | Assert.Throws(typeof(SqlException), () => _customerRepository.Update(customer.CustomerId, ";DELETE FROM dbo.Customer; /*", DateTime.Today)); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /Spry.Tests/CustomerRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Spry.Table; 4 | using Spry.Tests.Dto; 5 | 6 | namespace Spry.Tests 7 | { 8 | public class CustomerRepository 9 | { 10 | private readonly ISqlConnectionFactory _connectionFactory; 11 | 12 | private const string CUSTOMER_TABLE = "Customer"; 13 | private const string CUSTOMER_ADDRESS_TABLE = "CustomerAddress"; 14 | 15 | public CustomerRepository(ISqlConnectionFactory connectionFactory) 16 | { 17 | _connectionFactory = connectionFactory; 18 | } 19 | 20 | public int Create(string name, DateTime dateOfBirth) 21 | { 22 | using (var connection = _connectionFactory.CreateConnection()) 23 | { 24 | var customer = new Customer 25 | { 26 | DateOfBirth = dateOfBirth, 27 | Name = name 28 | }; 29 | 30 | customer.CustomerId = Spry.InsertInto(CUSTOMER_TABLE) 31 | .Value(_ => customer.Name) 32 | .Value(_ => customer.DateOfBirth) 33 | .OutputIdentity() 34 | .ExecuteScalar(connection); 35 | 36 | return customer.CustomerId; 37 | } 38 | } 39 | 40 | public int CreateAddress(CustomerAddress address) 41 | { 42 | using (var connection = _connectionFactory.CreateConnection()) 43 | { 44 | address.CustomerAddressId = Spry.InsertInto(CUSTOMER_ADDRESS_TABLE) 45 | .Value(_ => address.CustomerId) 46 | .Value(_ => address.LineOne) 47 | .Value(_ => address.City) 48 | .Value(_ => address.Country) 49 | .Value(_ => address.PostCode) 50 | .ExecuteScalar(connection); 51 | 52 | return address.CustomerAddressId; 53 | } 54 | } 55 | 56 | public bool Update(int customerId, string name, DateTime dateOfBirth) 57 | { 58 | using (var connection = _connectionFactory.CreateConnection()) 59 | { 60 | var rowsUpdated = Spry.Update(CUSTOMER_TABLE) 61 | .Set(_ => name) 62 | .Set(_ => dateOfBirth) 63 | .Where(_ => customerId).EqualTo(customerId) 64 | .Execute(connection); 65 | 66 | return rowsUpdated > 0; 67 | } 68 | } 69 | 70 | public bool UpdateSqlInjection(int customerId, string name) 71 | { 72 | using (var connection = _connectionFactory.CreateConnection()) 73 | { 74 | var rowsUpdated = Spry.Update(CUSTOMER_TABLE) 75 | .Set(_ => name) 76 | .Where(_ => customerId).EqualTo(customerId) 77 | .AndWhere("1 =1;" + 78 | "DELETE FROM CUSTOMER; --").EqualTo(1) 79 | .Execute(connection); 80 | 81 | return rowsUpdated > 0; 82 | } 83 | } 84 | 85 | public bool UpdateByName(string oldName, string name) 86 | { 87 | using (var connection = _connectionFactory.CreateConnection()) 88 | { 89 | var rowsUpdated = Spry.Update(CUSTOMER_TABLE) 90 | .Set(_ => name) 91 | .Where(_ => _.Name).EqualTo(oldName) 92 | .Execute(connection); 93 | 94 | return rowsUpdated > 0; 95 | } 96 | } 97 | 98 | public bool Delete(int customerId) 99 | { 100 | using (var connection = _connectionFactory.CreateConnection()) 101 | { 102 | var rowsDeleted = Spry.Delete() 103 | .From(CUSTOMER_TABLE) 104 | .Where(_ => customerId).EqualTo(customerId) 105 | .Execute(connection); 106 | 107 | return rowsDeleted == 1; 108 | } 109 | } 110 | 111 | public Customer ReadComplete(int customerId) 112 | { 113 | using (var connection = _connectionFactory.CreateConnection()) 114 | { 115 | var customer = Spry.Select() 116 | .Column(_ => _.CustomerId, "C") 117 | .Column(_ => _.Name) 118 | .Column(_ => _.DateOfBirth) 119 | .Column(_ => _.Address.City) 120 | .Column(_ => _.Address.Country) 121 | .Column(_ => _.Address.PostCode) 122 | .Column(_ => _.Address.CustomerAddressId) 123 | .Column(_ => _.Address.LineOne) 124 | .From(CUSTOMER_TABLE).As("C").InSchema("dbo") 125 | .InnerJoin(CUSTOMER_ADDRESS_TABLE, "CA").On("CA.CustomerId", "C.CustomerId") 126 | .Where(_ => _.CustomerId, "C").EqualTo(customerId) 127 | .Query(connection).SingleOrDefault(); 128 | 129 | return ToCustomer(customer); 130 | } 131 | } 132 | 133 | public Customer Read(int customerId) 134 | { 135 | using (var connection = _connectionFactory.CreateConnection()) 136 | { 137 | return Spry.Select() 138 | .Column(_ => _.CustomerId, "C") 139 | .Column(_ => _.Name, "C") 140 | .Column(_ => _.DateOfBirth, "C") 141 | .From(CUSTOMER_TABLE).As("C").InSchema("dbo") 142 | .Where(_ => _.CustomerId, "C").EqualTo(customerId) 143 | .Query(connection).SingleOrDefault(); 144 | } 145 | } 146 | 147 | private Customer ToCustomer(dynamic customerRow) 148 | { 149 | if (customerRow == null) 150 | return null; 151 | 152 | var customer = new Customer 153 | { 154 | CustomerId = customerRow.CustomerId, 155 | Name = customerRow.Name, 156 | DateOfBirth = customerRow.DateOfBirth 157 | }; 158 | 159 | if (customerRow.CustomerAddressId > 0) 160 | { 161 | customer.Address = new CustomerAddress 162 | { 163 | City = customerRow.City, 164 | PostCode = customerRow.PostCode, 165 | Country = customerRow.Country, 166 | LineOne = customerRow.LineOne, 167 | CustomerAddressId = customerRow.CustomerAddressId, 168 | CustomerId = customerRow.CustomerId 169 | }; 170 | } 171 | 172 | return customer; 173 | } 174 | } 175 | } 176 | --------------------------------------------------------------------------------