├── .gitignore ├── AsyncPoco.Tests ├── App.config ├── AsyncPoco.Tests.csproj ├── ColumnMapper.cs ├── DatabaseTests.cs ├── MySql │ ├── MySqlTests.cs │ ├── mysql_done.sql │ └── mysql_init.sql ├── PostgreSql │ ├── PostgreSqlTests.cs │ ├── postgresql_done.sql │ └── postgresql_init.sql ├── SQLite │ ├── SQLiteTests.cs │ ├── sqlite_done.sql │ └── sqlite_init.sql ├── SqlBuilderTests.cs ├── SqlServer │ ├── SqlServerTests.cs │ ├── sqlserver_done.sql │ └── sqlserver_init.sql ├── SqlServerCE │ ├── SqlServerCETests.cs │ ├── petapoco.sdf │ ├── sqlserverce_done.sql │ └── sqlserverce_init.sql ├── Utils.cs └── poco.cs ├── AsyncPoco.sln ├── AsyncPoco ├── AsyncPoco.csproj ├── Attributes │ ├── ColumnAttribute.cs │ ├── ComputedColumnAttribute.cs │ ├── ExplicitColumnsAttribute.cs │ ├── IgnoreAttribute.cs │ ├── PrimaryKeyAttribute.cs │ ├── ResultColumnAttribute.cs │ └── TableNameAttribute.cs ├── Core │ ├── AnsiString.cs │ ├── ColumnInfo.cs │ ├── DatabaseType.cs │ ├── ExpandoColumn.cs │ ├── IMapper.cs │ ├── Mappers.cs │ ├── MultiPocoFactory.cs │ ├── Page.cs │ ├── PocoColumn.cs │ ├── PocoData.cs │ ├── Sql.cs │ ├── StandardMapper.cs │ ├── TableInfo.cs │ └── Transaction.cs ├── Database.cs ├── DatabaseTypes │ ├── MySqlDatabaseType.cs │ ├── OracleDatabaseType.cs │ ├── PostgreSQLDatabaseType.cs │ ├── SQLiteDatabaseType.cs │ ├── SqlServerCEDatabaseType.cs │ └── SqlServerDatabaseType.cs ├── IDatabase.cs └── Utilities │ ├── ArrayKey.cs │ ├── AutoSelectHelper.cs │ ├── Cache.cs │ ├── EnumMapper.cs │ ├── PagingHelper.cs │ ├── ParametersHelper.cs │ └── Singleton.cs ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | *.suo 4 | *.nupkg 5 | packages 6 | csj/csj.exe 7 | csj/csj.pdb 8 | *.user 9 | .vs -------------------------------------------------------------------------------- /AsyncPoco.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/AsyncPoco.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net45;netcoreapp1.0;netcoreapp2.0 5 | 6 | 7 | 8 | 9 | PreserveNewest 10 | petapoco.sdf 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/ColumnMapper.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace AsyncPoco.Tests 6 | { 7 | public class Poco2 8 | { 9 | public string prop1 { get; set; } 10 | public string prop2 { get; set; } 11 | public string prop3 { get; set; } 12 | public string prop4 { get; set; } 13 | } 14 | 15 | public class MyColumnMapper : AsyncPoco.IMapper 16 | { 17 | public TableInfo GetTableInfo(Type t) 18 | { 19 | var ti = TableInfo.FromPoco(t); 20 | 21 | if (t == typeof(Poco2)) 22 | { 23 | ti.TableName = "petapoco"; 24 | ti.PrimaryKey = "id"; 25 | } 26 | 27 | return ti; 28 | } 29 | public ColumnInfo GetColumnInfo(System.Reflection.PropertyInfo pi) 30 | { 31 | var ci = ColumnInfo.FromProperty(pi); 32 | if (ci == null) 33 | return null; 34 | 35 | if (pi.DeclaringType == typeof(Poco2)) 36 | { 37 | switch (pi.Name) 38 | { 39 | case "prop1": 40 | // Leave this property as is 41 | break; 42 | 43 | case "prop2": 44 | // Rename this column 45 | ci.ColumnName = "remapped2"; 46 | break; 47 | 48 | case "prop3": 49 | // Mark this as a result column 50 | ci.ResultColumn = true; 51 | break; 52 | 53 | case "prop4": 54 | // Ignore this property 55 | return null; 56 | } 57 | } 58 | 59 | // Do default property mapping 60 | return ci; 61 | } 62 | 63 | 64 | public Func GetFromDbConverter(System.Reflection.PropertyInfo pi, Type SourceType) 65 | { 66 | return null; 67 | } 68 | 69 | public Func GetToDbConverter(PropertyInfo SourceProperty) 70 | { 71 | return null; 72 | } 73 | } 74 | 75 | [TestFixture] 76 | public class ColumnMapper 77 | { 78 | [Test] 79 | public void NoColumnMapper() 80 | { 81 | AsyncPoco.Mappers.Register(GetType().GetTypeInfo().Assembly, new MyColumnMapper()); 82 | var pd = AsyncPoco.Internal.PocoData.ForType(typeof(Poco2)); 83 | 84 | Assert.AreEqual(pd.Columns.Count, 3); 85 | Assert.AreEqual(pd.Columns["prop1"].PropertyInfo.Name, "prop1"); 86 | Assert.AreEqual(pd.Columns["remapped2"].ColumnName, "remapped2"); 87 | Assert.AreEqual(pd.Columns["prop3"].ColumnName, "prop3"); 88 | Assert.AreEqual(string.Join(", ", pd.QueryColumns), "prop1, remapped2"); 89 | Assert.AreEqual(pd.TableInfo.PrimaryKey, "id"); 90 | Assert.AreEqual(pd.TableInfo.TableName, "petapoco"); 91 | 92 | AsyncPoco.Mappers.Revoke(GetType().GetTypeInfo().Assembly); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/DatabaseTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | 8 | namespace AsyncPoco.Tests 9 | { 10 | [TestFixture] 11 | public abstract class DatabaseTests where TConnection : DbConnection, new() 12 | { 13 | protected abstract string ConnStrName { get; } 14 | protected abstract string ConnStr { get; } 15 | protected abstract string DbProviderName { get; } 16 | 17 | protected static Random rand = new Random(); 18 | protected Database db; 19 | 20 | private Func[] _dbFactories; 21 | 22 | protected DatabaseTests() { 23 | _dbFactories = new Func[] { 24 | // one for each supported Database ctor or static Create method 25 | () => Database.Create(ConnStr), 26 | () => Database.Create(() => new TConnection { ConnectionString = ConnStr }), 27 | () => { 28 | var conn = new TConnection { ConnectionString = ConnStr }; 29 | conn.Open(); 30 | return new Database(conn); 31 | }, 32 | #if NET45 33 | () => new Database(ConnStrName), 34 | () => new Database(ConnStr, DbProviderFactories.GetFactory(DbProviderName)), 35 | () => new Database(ConnStr, DbProviderName) 36 | #endif 37 | }; 38 | } 39 | 40 | private static int _dbFactoryCounter = rand.Next(10); 41 | 42 | [SetUp] 43 | public virtual void CreateDatabase() { 44 | // There are 6 different ways to create a Database object. But having 6 variations of each test, for each db 45 | // platform and each .NET flavor, would be obnoxious. Instead, pick one at random for the first test, then 46 | // cycle through them round-robin for the rest. 47 | var i = Interlocked.Increment(ref _dbFactoryCounter) % _dbFactories.Length; 48 | Console.WriteLine("use factory " + i); 49 | db = _dbFactories[i](); 50 | } 51 | 52 | [OneTimeSetUp] 53 | public virtual async Task CreateDbAsync() 54 | { 55 | var all = Utils.LoadTextResource($"{GetType().Namespace}.{ConnStrName}_init.sql"); 56 | CreateDatabase(); 57 | foreach (var sql in all.Split(';').Select(s => s.Trim()).Where(s => s.Length > 0)) 58 | await db.ExecuteAsync(sql); 59 | } 60 | 61 | [OneTimeTearDown] 62 | public virtual async Task DeleteDbAsync() { 63 | var all = Utils.LoadTextResource($"{GetType().Namespace}.{ConnStrName}_done.sql"); 64 | CreateDatabase(); 65 | foreach (var sql in all.Split(';').Select(s => s.Trim()).Where(s => s.Length > 0)) 66 | await db.ExecuteAsync(sql); 67 | } 68 | 69 | Task GetRecordCountAsync() 70 | { 71 | return db.ExecuteScalarAsync("SELECT COUNT(*) FROM petapoco"); 72 | } 73 | 74 | [TearDown] 75 | public async Task TeardownAsync() 76 | { 77 | // Delete everything 78 | await db.DeleteAsync(""); 79 | await db.DeleteAsync(""); 80 | 81 | // Should be clean 82 | Assert.AreEqual(await GetRecordCountAsync(), 0); 83 | } 84 | 85 | poco CreatePoco() 86 | { 87 | // Need a rounded date as DB can't store milliseconds 88 | var now = DateTime.UtcNow; 89 | now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); 90 | 91 | // Setup a record 92 | var o = new poco(); 93 | o.title = string.Format("insert {0}", rand.Next()); 94 | o.draft = true; 95 | o.content = string.Format("insert {0}", rand.Next()); 96 | o.date_created = now; 97 | o.date_edited = now; 98 | o.state = State.Yes; 99 | o.state2 = null; 100 | o.col_w_space = 23; 101 | o.nullreal = 24; 102 | 103 | return o; 104 | } 105 | 106 | deco CreateDeco() 107 | { 108 | // Need a rounded date as DB can't store milliseconds 109 | var now = DateTime.UtcNow; 110 | now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); 111 | 112 | // Setup a record 113 | var o = new deco(); 114 | o.title = string.Format("insert {0}", rand.Next()); 115 | o.draft = true; 116 | o.content = string.Format("insert {0}", rand.Next()); 117 | o.date_created = now; 118 | o.date_edited = now; 119 | o.state = State.Maybe; 120 | o.state2 = State.No; 121 | o.col_w_space = 23; 122 | o.nullreal = 24; 123 | 124 | return o; 125 | } 126 | 127 | void AssertPocos(poco a, poco b) 128 | { 129 | Assert.AreEqual(a.id, b.id); 130 | Assert.AreEqual(a.title, b.title); 131 | Assert.AreEqual(a.draft, b.draft); 132 | Assert.AreEqual(a.content, b.content); 133 | Assert.AreEqual(a.date_created, b.date_created); 134 | Assert.AreEqual(a.date_edited, b.date_edited); 135 | Assert.AreEqual(a.state, b.state); 136 | Assert.AreEqual(a.state2, b.state2); 137 | Assert.AreEqual(a.col_w_space, b.col_w_space); 138 | Assert.AreEqual(a.nullreal, b.nullreal); 139 | } 140 | 141 | void AssertPocos(deco a, deco b) 142 | { 143 | Assert.AreEqual(a.id, b.id); 144 | Assert.AreEqual(a.title, b.title); 145 | Assert.AreEqual(a.draft, b.draft); 146 | Assert.AreEqual(a.content, b.content); 147 | Assert.AreEqual(a.date_created, b.date_created); 148 | Assert.AreEqual(a.state, b.state); 149 | Assert.AreEqual(a.state2, b.state2); 150 | Assert.AreEqual(a.col_w_space, b.col_w_space); 151 | Assert.AreEqual(a.nullreal, b.nullreal); 152 | } 153 | 154 | // Insert some records, return the id of the first 155 | async Task InsertRecordsAsync(int count) 156 | { 157 | long lFirst = 0; 158 | for (int i = 0; i < count; i++) 159 | { 160 | var o = CreatePoco(); 161 | await db.InsertAsync("petapoco", "id", o); 162 | 163 | var lc = db.LastCommand; 164 | 165 | if (i == 0) 166 | { 167 | lFirst = o.id; 168 | Assert.AreNotEqual(o.id, 0); 169 | } 170 | } 171 | 172 | return lFirst; 173 | } 174 | 175 | [Test] 176 | public async Task poco_Crud() 177 | { 178 | // Create a random record 179 | var o = CreatePoco(); 180 | 181 | Assert.IsTrue(db.IsNew("id", o)); 182 | 183 | // Insert it 184 | await db.InsertAsync("petapoco", "id", o); 185 | Assert.AreNotEqual(o.id, 0); 186 | 187 | Assert.IsFalse(db.IsNew("id", o)); 188 | 189 | // Retrieve it 190 | var o2 = await db.SingleAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 191 | 192 | Assert.IsFalse(db.IsNew("id", o2)); 193 | 194 | // Check it 195 | AssertPocos(o, o2); 196 | 197 | // Update it 198 | o2.title = "New Title"; 199 | await db.SaveAsync("petapoco", "id", o2); 200 | 201 | // Retrieve it again 202 | var o3 = await db.SingleAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 203 | 204 | // Check it 205 | AssertPocos(o2, o3); 206 | 207 | // Delete it 208 | await db.DeleteAsync("petapoco", "id", o3); 209 | 210 | // Should be gone! 211 | var o4 = await db.SingleOrDefaultAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 212 | Assert.IsNull(o4); 213 | } 214 | 215 | [Test] 216 | public async Task deco_Crud() 217 | { 218 | // Create a random record 219 | var o = CreateDeco(); 220 | Assert.IsTrue(db.IsNew(o)); 221 | 222 | // Insert it 223 | await db.InsertAsync(o); 224 | Assert.AreNotEqual(o.id, 0); 225 | 226 | Assert.IsFalse(db.IsNew(o)); 227 | 228 | // Retrieve it 229 | var o2 = await db.SingleAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 230 | 231 | Assert.IsFalse(db.IsNew(o2)); 232 | 233 | // Check it 234 | AssertPocos(o, o2); 235 | 236 | // Update it 237 | o2.title = "New Title"; 238 | await db.SaveAsync(o2); 239 | 240 | // Retrieve it again 241 | var o3 = await db.SingleAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 242 | 243 | // Check it 244 | AssertPocos(o2, o3); 245 | 246 | // Delete it 247 | await db.DeleteAsync(o3); 248 | 249 | // Should be gone! 250 | var o4 = await db.SingleOrDefaultAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 251 | Assert.IsNull(o4); 252 | } 253 | 254 | [Test] 255 | public async Task Fetch() 256 | { 257 | // Create some records 258 | const int count = 5; 259 | long id = await InsertRecordsAsync(count); 260 | 261 | // Fetch em 262 | var r = await db.FetchAsync("SELECT * from petapoco ORDER BY id"); 263 | Assert.AreEqual(r.Count, count); 264 | 265 | // Check em 266 | for (int i = 0; i < count; i++) 267 | { 268 | Assert.AreEqual(r[i].id, id + i); 269 | } 270 | 271 | } 272 | 273 | [Test] 274 | public async Task Query() 275 | { 276 | // Create some records 277 | const int count = 5; 278 | long id = await InsertRecordsAsync(count); 279 | 280 | int i = 0; 281 | await db.QueryAsync("SELECT * from petapoco ORDER BY id", p => 282 | { 283 | Assert.AreEqual(p.id, id + i); 284 | i++; 285 | }); 286 | 287 | Assert.AreEqual(i, count); 288 | } 289 | 290 | [Test] 291 | public async Task Page() 292 | { 293 | // In this test we're checking that the page count is correct when there are 294 | // not-exactly pagesize*N records (ie: a partial page at the end) 295 | 296 | // Create some records 297 | const int count = 13; 298 | long id = await InsertRecordsAsync(count); 299 | 300 | // Fetch em 301 | var r = await db.PageAsync(2, 5, "SELECT * from petapoco ORDER BY id"); 302 | 303 | // Check em 304 | int i = 0; 305 | foreach (var p in r.Items) 306 | { 307 | Assert.AreEqual(p.id, id + i + 5); 308 | i++; 309 | } 310 | 311 | // Check other stats 312 | Assert.AreEqual(r.Items.Count, 5); 313 | Assert.AreEqual(r.CurrentPage, 2); 314 | Assert.AreEqual(r.ItemsPerPage, 5); 315 | Assert.AreEqual(r.TotalItems, 13); 316 | Assert.AreEqual(r.TotalPages, 3); 317 | } 318 | 319 | [Test] 320 | public async Task Page_NoOrderBy() 321 | { 322 | // Unordered paging not supported by Compact Edition 323 | if (ConnStrName == "sqlserverce") 324 | return; 325 | // In this test we're checking that the page count is correct when there are 326 | // not-exactly pagesize*N records (ie: a partial page at the end) 327 | 328 | // Create some records 329 | const int count = 13; 330 | long id = await InsertRecordsAsync(count); 331 | 332 | // Fetch em 333 | var r = await db.PageAsync(2, 5, "SELECT * from petapoco"); 334 | 335 | // Check em 336 | int i = 0; 337 | foreach (var p in r.Items) 338 | { 339 | Assert.AreEqual(p.id, id + i + 5); 340 | i++; 341 | } 342 | 343 | // Check other stats 344 | Assert.AreEqual(r.Items.Count, 5); 345 | Assert.AreEqual(r.CurrentPage, 2); 346 | Assert.AreEqual(r.ItemsPerPage, 5); 347 | Assert.AreEqual(r.TotalItems, 13); 348 | Assert.AreEqual(r.TotalPages, 3); 349 | } 350 | 351 | [Test] 352 | public async Task Page_Distinct() 353 | { 354 | // Unordered paging not supported by Compact Edition 355 | if (ConnStrName == "sqlserverce") 356 | return; 357 | // In this test we're checking that the page count is correct when there are 358 | // not-exactly pagesize*N records (ie: a partial page at the end) 359 | 360 | // Create some records 361 | const int count = 13; 362 | long id = await InsertRecordsAsync(count); 363 | 364 | // Fetch em 365 | var r = await db.PageAsync(2, 5, "SELECT DISTINCT id from petapoco ORDER BY id"); 366 | 367 | // Check em 368 | int i = 0; 369 | foreach (var p in r.Items) 370 | { 371 | Assert.AreEqual(p.id, id + i + 5); 372 | i++; 373 | } 374 | 375 | // Check other stats 376 | Assert.AreEqual(r.Items.Count, 5); 377 | Assert.AreEqual(r.CurrentPage, 2); 378 | Assert.AreEqual(r.ItemsPerPage, 5); 379 | Assert.AreEqual(r.TotalItems, 13); 380 | Assert.AreEqual(r.TotalPages, 3); 381 | } 382 | 383 | [Test] 384 | public async Task FetchPage() 385 | { 386 | // Create some records 387 | const int count = 13; 388 | long id = await InsertRecordsAsync(count); 389 | 390 | // Fetch em 391 | var r = await db.FetchAsync(2, 5, "SELECT * from petapoco ORDER BY id"); 392 | 393 | // Check em 394 | int i = 0; 395 | foreach (var p in r) 396 | { 397 | Assert.AreEqual(p.id, id + i + 5); 398 | i++; 399 | } 400 | 401 | // Check other stats 402 | Assert.AreEqual(r.Count, 5); 403 | } 404 | 405 | [Test] 406 | public async Task Page_boundary() 407 | { 408 | // In this test we're checking that the page count is correct when there are 409 | // exactly pagesize*N records. 410 | 411 | // Create some records 412 | const int count = 15; 413 | long id = await InsertRecordsAsync(count); 414 | 415 | // Fetch em 416 | var r = await db.PageAsync(3, 5, "SELECT * from petapoco ORDER BY id"); 417 | 418 | // Check other stats 419 | Assert.AreEqual(r.Items.Count, 5); 420 | Assert.AreEqual(r.CurrentPage, 3); 421 | Assert.AreEqual(r.ItemsPerPage, 5); 422 | Assert.AreEqual(r.TotalItems, 15); 423 | Assert.AreEqual(r.TotalPages, 3); 424 | } 425 | 426 | [Test] 427 | public async Task deco_Delete() 428 | { 429 | // Create some records 430 | const int count = 15; 431 | long id = await InsertRecordsAsync(count); 432 | 433 | // Delete some 434 | await db.DeleteAsync("WHERE id>=@0", id + 5); 435 | 436 | // Check they match 437 | Assert.AreEqual(await GetRecordCountAsync(), 5); 438 | } 439 | 440 | [Test] 441 | public async Task deco_Update() 442 | { 443 | // Create some records 444 | const int count = 15; 445 | long id = await InsertRecordsAsync(count); 446 | 447 | // Update some 448 | await db.UpdateAsync("SET title=@0 WHERE id>=@1", "zap", id + 5); 449 | 450 | // Check some updated 451 | await db.QueryAsync("ORDER BY Id", d => 452 | { 453 | if (d.id >= id + 5) 454 | { 455 | Assert.AreEqual(d.title, "zap"); 456 | } 457 | else 458 | { 459 | Assert.AreNotEqual(d.title, "zap"); 460 | } 461 | }); 462 | } 463 | 464 | [Test] 465 | public async Task deco_ExplicitAttribute() 466 | { 467 | // Create a records 468 | long id = await InsertRecordsAsync(1); 469 | 470 | // Retrieve it in two different ways 471 | var a = await db.SingleOrDefaultAsync("WHERE id=@0", id); 472 | var b = await db.SingleOrDefaultAsync("WHERE id=@0", id); 473 | var c = await db.SingleOrDefaultAsync("SELECT * FROM petapoco WHERE id=@0", id); 474 | 475 | // b record should have ignored the content 476 | Assert.IsNotNull(a.content); 477 | Assert.IsNull(b.content); 478 | Assert.IsNull(c.content); 479 | } 480 | 481 | [Test] 482 | public async Task deco_IgnoreAttribute() 483 | { 484 | // Create a records 485 | long id = await InsertRecordsAsync(1); 486 | 487 | // Retrieve it in two different ways 488 | var a = await db.SingleOrDefaultAsync("WHERE id=@0", id); 489 | var b = await db.SingleOrDefaultAsync("WHERE id=@0", id); 490 | var c = await db.SingleOrDefaultAsync("SELECT * FROM petapoco WHERE id=@0", id); 491 | 492 | // b record should have ignored the content 493 | Assert.IsNotNull(a.content); 494 | Assert.IsNull(b.content); 495 | Assert.IsNull(c.content); 496 | } 497 | 498 | [Test] 499 | public async Task Transaction_complete() 500 | { 501 | using (var scope = await db.GetTransactionAsync()) 502 | { 503 | await InsertRecordsAsync(10); 504 | scope.Complete(); 505 | } 506 | 507 | Assert.AreEqual(await GetRecordCountAsync(), 10); 508 | } 509 | 510 | [Test] 511 | public async Task Transaction_cancelled() 512 | { 513 | using (var scope = await db.GetTransactionAsync()) 514 | { 515 | await InsertRecordsAsync(10); 516 | } 517 | 518 | Assert.AreEqual(await GetRecordCountAsync(), 0); 519 | } 520 | 521 | [Test] 522 | public async Task Transaction_nested_nn() 523 | { 524 | using (var scope1 = await db.GetTransactionAsync()) 525 | { 526 | await InsertRecordsAsync(10); 527 | 528 | using (var scope2 = await db.GetTransactionAsync()) 529 | { 530 | await InsertRecordsAsync(10); 531 | } 532 | } 533 | 534 | Assert.AreEqual(await GetRecordCountAsync(), 0); 535 | } 536 | 537 | [Test] 538 | public async Task Transaction_nested_yn() 539 | { 540 | using (var scope1 = await db.GetTransactionAsync()) 541 | { 542 | await InsertRecordsAsync(10); 543 | 544 | using (var scope2 = await db.GetTransactionAsync()) 545 | { 546 | await InsertRecordsAsync(10); 547 | } 548 | scope1.Complete(); 549 | } 550 | 551 | Assert.AreEqual(await GetRecordCountAsync(), 0); 552 | } 553 | 554 | [Test] 555 | public async Task Transaction_nested_ny() 556 | { 557 | using (var scope1 = await db.GetTransactionAsync()) 558 | { 559 | await InsertRecordsAsync(10); 560 | 561 | using (var scope2 = await db.GetTransactionAsync()) 562 | { 563 | await InsertRecordsAsync(10); 564 | scope2.Complete(); 565 | } 566 | } 567 | 568 | Assert.AreEqual(await GetRecordCountAsync(), 0); 569 | } 570 | 571 | [Test] 572 | public async Task Transaction_nested_yy() 573 | { 574 | using (var scope1 = await db.GetTransactionAsync()) 575 | { 576 | await InsertRecordsAsync(10); 577 | 578 | using (var scope2 = await db.GetTransactionAsync()) 579 | { 580 | await InsertRecordsAsync(10); 581 | scope2.Complete(); 582 | } 583 | 584 | scope1.Complete(); 585 | } 586 | 587 | Assert.AreEqual(await GetRecordCountAsync(), 20); 588 | } 589 | 590 | [Test] 591 | public async Task Transaction_nested_yny() 592 | { 593 | using (var scope1 = await db.GetTransactionAsync()) 594 | { 595 | await InsertRecordsAsync(10); 596 | 597 | using (var scope2 = await db.GetTransactionAsync()) 598 | { 599 | await InsertRecordsAsync(10); 600 | //scope2.Complete(); 601 | } 602 | 603 | using (var scope3 = await db.GetTransactionAsync()) 604 | { 605 | await InsertRecordsAsync(10); 606 | scope3.Complete(); 607 | } 608 | 609 | scope1.Complete(); 610 | } 611 | 612 | Assert.AreEqual(await GetRecordCountAsync(), 0); 613 | } 614 | 615 | [Test] 616 | public async Task DateTimesAreUtc() 617 | { 618 | var id = await InsertRecordsAsync(1); 619 | var a2 = await db.SingleOrDefaultAsync("WHERE id=@0", id); 620 | Assert.AreEqual(DateTimeKind.Utc, a2.date_created.Kind); 621 | Assert.AreEqual(DateTimeKind.Utc, a2.date_edited.Value.Kind); 622 | } 623 | 624 | [Test] 625 | public async Task DateTimeNullable() 626 | { 627 | // Need a rounded date as DB can't store milliseconds 628 | var now = DateTime.UtcNow; 629 | now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); 630 | 631 | // Setup a record 632 | var a = new deco(); 633 | a.title = string.Format("insert {0}", rand.Next()); 634 | a.draft = true; 635 | a.content = string.Format("insert {0}", rand.Next()); 636 | a.date_created = now; 637 | a.date_edited = null; 638 | 639 | await db.InsertAsync(a); 640 | 641 | // Retrieve it 642 | var b = await db.SingleOrDefaultAsync("WHERE id=@0", a.id); 643 | Assert.AreEqual(b.id, a.id); 644 | Assert.AreEqual(b.date_edited.HasValue, false); 645 | 646 | // Update it to NULL 647 | b.date_edited = now; 648 | await db.UpdateAsync(b); 649 | var c = await db.SingleOrDefaultAsync("WHERE id=@0", a.id); 650 | Assert.AreEqual(c.id, a.id); 651 | Assert.AreEqual(c.date_edited.HasValue, true); 652 | 653 | // Update it to not NULL 654 | c.date_edited = null; 655 | await db.UpdateAsync(c); 656 | var d = await db.SingleOrDefaultAsync("WHERE id=@0", a.id); 657 | Assert.AreEqual(d.id, a.id); 658 | Assert.AreEqual(d.date_edited.HasValue, false); 659 | } 660 | 661 | [Test] 662 | public async Task NamedArgs() 663 | { 664 | long first = await InsertRecordsAsync(10); 665 | 666 | var items = await db.FetchAsync("WHERE id >= @min_id AND id <= @max_id", 667 | new 668 | { 669 | min_id = first + 3, 670 | max_id = first + 6 671 | } 672 | ); 673 | Assert.AreEqual(items.Count, 4); 674 | } 675 | 676 | [Test] 677 | public async Task SingleOrDefault_Empty() 678 | { 679 | Assert.IsNull(await db.SingleOrDefaultAsync("WHERE id=@0", 0)); 680 | } 681 | 682 | [Test] 683 | public async Task SingleOrDefault_Single() 684 | { 685 | var id = await InsertRecordsAsync(1); 686 | Assert.IsNotNull(await db.SingleOrDefaultAsync("WHERE id=@0", id)); 687 | } 688 | 689 | [Test] 690 | public void SingleOrDefault_Multiple() 691 | { 692 | Assert.ThrowsAsync(async () => 693 | { 694 | var id = await InsertRecordsAsync(2); 695 | await db.SingleOrDefaultAsync("WHERE id>=@0", id); 696 | }); 697 | } 698 | 699 | [Test] 700 | public async Task FirstOrDefault_Empty() 701 | { 702 | Assert.IsNull(await db.FirstOrDefaultAsync("WHERE id=@0", 0)); 703 | } 704 | 705 | [Test] 706 | public async Task FirstOrDefault_First() 707 | { 708 | var id = await InsertRecordsAsync(1); 709 | Assert.IsNotNull(await db.FirstOrDefaultAsync("WHERE id=@0", id)); 710 | } 711 | 712 | [Test] 713 | public async Task FirstOrDefault_Multiple() 714 | { 715 | var id = await InsertRecordsAsync(2); 716 | Assert.IsNotNull(await db.FirstOrDefaultAsync("WHERE id>=@0", id)); 717 | } 718 | 719 | [Test] 720 | public void Single_Empty() 721 | { 722 | Assert.ThrowsAsync(async () => 723 | { 724 | await db.SingleAsync("WHERE id=@0", 0); 725 | }); 726 | } 727 | 728 | [Test] 729 | public async Task Single_Single() 730 | { 731 | var id = await InsertRecordsAsync(1); 732 | Assert.IsNotNull(await db.SingleAsync("WHERE id=@0", id)); 733 | } 734 | 735 | [Test] 736 | public void Single_Multiple() 737 | { 738 | Assert.ThrowsAsync(async () => 739 | { 740 | var id = await InsertRecordsAsync(2); 741 | await db.SingleAsync("WHERE id>=@0", id); 742 | }); 743 | } 744 | 745 | [Test] 746 | public void First_Empty() 747 | { 748 | Assert.ThrowsAsync(async () => 749 | { 750 | await db.FirstAsync("WHERE id=@0", 0); 751 | }); 752 | } 753 | 754 | [Test] 755 | public async Task First_First() 756 | { 757 | var id = await InsertRecordsAsync(1); 758 | Assert.IsNotNull(await db.FirstAsync("WHERE id=@0", id)); 759 | } 760 | 761 | [Test] 762 | public async Task First_Multiple() 763 | { 764 | var id = await InsertRecordsAsync(2); 765 | Assert.IsNotNull(await db.FirstAsync("WHERE id>=@0", id)); 766 | } 767 | 768 | [Test] 769 | public async Task SingleOrDefault_PK_Empty() 770 | { 771 | Assert.IsNull(await db.SingleOrDefaultAsync(0)); 772 | } 773 | 774 | [Test] 775 | public async Task SingleOrDefault_PK_Single() 776 | { 777 | var id = await InsertRecordsAsync(1); 778 | Assert.IsNotNull(await db.SingleOrDefaultAsync(id)); 779 | } 780 | 781 | [Test] 782 | public void Single_PK_Empty() 783 | { 784 | Assert.ThrowsAsync(async () => 785 | { 786 | await db.SingleAsync(0); 787 | }); 788 | } 789 | 790 | [Test] 791 | public async Task Single_PK_Single() 792 | { 793 | var id = await InsertRecordsAsync(1); 794 | Assert.IsNotNull(await db.SingleAsync(id)); 795 | } 796 | 797 | [Test] 798 | public async Task AutoSelect_SelectPresent() 799 | { 800 | var id = await InsertRecordsAsync(1); 801 | var a = await db.SingleOrDefaultAsync("SELECT * FROM petapoco WHERE id=@0", id); 802 | Assert.IsNotNull(a); 803 | Assert.AreEqual(a.id, id); 804 | } 805 | 806 | [Test] 807 | public async Task AutoSelect_SelectMissingFromMissing() 808 | { 809 | var id = await InsertRecordsAsync(1); 810 | var a = await db.SingleOrDefaultAsync("WHERE id=@0", id); 811 | Assert.IsNotNull(a); 812 | Assert.AreEqual(a.id, id); 813 | } 814 | 815 | [Test] 816 | public async Task AutoSelect_SelectMissingFromPresent() 817 | { 818 | var id = await InsertRecordsAsync(1); 819 | var a = await db.SingleOrDefaultAsync("FROM petapoco WHERE id=@0", id); 820 | Assert.IsNotNull(a); 821 | Assert.AreEqual(a.id, id); 822 | } 823 | 824 | void AssertDynamic(dynamic a, dynamic b) 825 | { 826 | Assert.AreEqual(a.id, b.id); 827 | Assert.AreEqual(a.title, b.title); 828 | Assert.AreEqual(a.draft, b.draft); 829 | Assert.AreEqual(a.content, b.content); 830 | Assert.AreEqual(a.date_created, b.date_created); 831 | Assert.AreEqual(a.state, b.state); 832 | Assert.AreEqual(a.state2, b.state2); 833 | } 834 | 835 | dynamic CreateExpando() 836 | { 837 | // Need a rounded date as DB can't store milliseconds 838 | var now = DateTime.UtcNow; 839 | now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second); 840 | 841 | // Setup a record 842 | dynamic o = new System.Dynamic.ExpandoObject(); 843 | o.title = string.Format("insert {0}", rand.Next()); 844 | o.draft = true; 845 | o.content = string.Format("insert {0}", rand.Next()); 846 | o.date_created = now; 847 | o.date_edited = now; 848 | o.state = (int)State.Maybe; 849 | o.state2 = (int?)null; 850 | 851 | return o; 852 | } 853 | 854 | [Test] 855 | public async Task Dynamic_Query() 856 | { 857 | // Create a random record 858 | var o = CreateExpando(); 859 | 860 | Assert.IsTrue(db.IsNew("id", o)); 861 | 862 | // Insert it 863 | await db.InsertAsync("petapoco", "id", o); 864 | Assert.AreNotEqual(o.id, 0); 865 | 866 | Assert.IsFalse(db.IsNew("id", o)); 867 | 868 | // Retrieve it 869 | var o2 = await db.SingleAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 870 | 871 | Assert.IsFalse(db.IsNew("id", o2)); 872 | 873 | // Check it 874 | AssertDynamic(o, o2); 875 | 876 | // Update it 877 | o2.title = "New Title"; 878 | await db.SaveAsync("petapoco", "id", o2); 879 | 880 | // Retrieve it again 881 | var o3 = await db.SingleAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 882 | 883 | // Check it 884 | AssertDynamic(o2, o3); 885 | 886 | // Delete it 887 | await db.DeleteAsync("petapoco", "id", o3); 888 | 889 | // Should be gone! 890 | var o4 = await db.SingleOrDefaultAsync("SELECT * FROM petapoco WHERE id=@0", o.id); 891 | Assert.IsNull(o4); 892 | } 893 | 894 | [Test] 895 | public async Task Manual_PrimaryKey() 896 | { 897 | var o = new petapoco2(); 898 | o.email = "blah@blah.com"; 899 | o.name = "Mr Blah"; 900 | await db.InsertAsync(o); 901 | 902 | var o2 = await db.SingleOrDefaultAsync("WHERE email=@0", "blah@blah.com"); 903 | Assert.AreEqual(o2.name, "Mr Blah"); 904 | } 905 | 906 | [Test] 907 | public async Task SingleValueRequest() 908 | { 909 | var id = await InsertRecordsAsync(1); 910 | var id2 = await db.SingleOrDefaultAsync("SELECT id from petapoco WHERE id=@0", id); 911 | Assert.AreEqual(id, id2); 912 | } 913 | 914 | [Test] 915 | public async Task Exists_Query_Does() 916 | { 917 | var id = await InsertRecordsAsync(10); 918 | Assert.IsTrue(await db.ExistsAsync("id = @0", id)); 919 | Assert.IsTrue(await db.ExistsAsync(id)); 920 | } 921 | 922 | [Test] 923 | public async Task Exists_Query_DoesNot() 924 | { 925 | var id = await InsertRecordsAsync(10); 926 | Assert.IsFalse(await db.ExistsAsync("id = @0", id + 100)); 927 | Assert.IsFalse(await db.ExistsAsync(id + 100)); 928 | } 929 | 930 | [Test] 931 | public async Task UpdateByObjectCompositePK() 932 | { 933 | await db.ExecuteAsync("DELETE FROM composite_pk"); 934 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 935 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 936 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 1, value = "fizz" }); 937 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 938 | 939 | await db.UpdateAsync("composite_pk", "id1,id2", new composite_pk { id1 = 2, id2 = 1, value = "buzz" }); 940 | 941 | var rows = await db.FetchAsync("SELECT * FROM composite_pk WHERE value = 'buzz'"); 942 | Assert.AreEqual(1, rows.Count); 943 | Assert.AreEqual(2, rows[0].id1); 944 | Assert.AreEqual(1, rows[0].id2); 945 | } 946 | 947 | [Test] 948 | public async Task UpdateByKeyCompositePK() 949 | { 950 | await db.ExecuteAsync("DELETE FROM composite_pk"); 951 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 952 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 953 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 1, value = "fizz" }); 954 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 955 | 956 | await db.UpdateAsync("composite_pk", "id1,id2", new { value = "buzz" }, new { id1 = 2, id2 = 1 }); 957 | 958 | var rows = await db.FetchAsync("SELECT * FROM composite_pk WHERE value = 'buzz'"); 959 | Assert.AreEqual(1, rows.Count); 960 | Assert.AreEqual(2, rows[0].id1); 961 | Assert.AreEqual(1, rows[0].id2); 962 | } 963 | 964 | [Test] 965 | public async Task UpdateByColumnsCompositePK() 966 | { 967 | await db.ExecuteAsync("DELETE FROM composite_pk"); 968 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 969 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 970 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 1, value = "fizz" }); 971 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 972 | 973 | await db.UpdateAsync("composite_pk", "id1,id2", new { value = "buzz" }, new { id1 = 2, id2 = 1 }, new[] { "value" }); 974 | 975 | var rows = await db.FetchAsync("SELECT * FROM composite_pk WHERE value = 'buzz'"); 976 | Assert.AreEqual(1, rows.Count); 977 | Assert.AreEqual(2, rows[0].id1); 978 | Assert.AreEqual(1, rows[0].id2); 979 | } 980 | 981 | [Test] 982 | public async Task DeleteByObjectCompositePK() 983 | { 984 | await db.ExecuteAsync("DELETE FROM composite_pk"); 985 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 986 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 987 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 1, value = "buzz" }); 988 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 989 | 990 | await db.DeleteAsync("composite_pk", "id1,id2", new composite_pk { id1 = 2, id2 = 1 }); 991 | 992 | var rows = await db.FetchAsync("SELECT * FROM composite_pk"); 993 | Assert.AreEqual(3, rows.Count); 994 | Assert.IsTrue(rows.All(x => x.value == "fizz")); 995 | } 996 | 997 | [Test] 998 | public async Task DeleteByKeyCompositePK() 999 | { 1000 | await db.ExecuteAsync("DELETE FROM composite_pk"); 1001 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 1002 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 1003 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 1, value = "buzz" }); 1004 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 1005 | 1006 | await db.DeleteAsync("composite_pk", "id1,id2", null, new { id1 = 2, id2 = 1 }); 1007 | 1008 | var rows = await db.FetchAsync("SELECT * FROM composite_pk"); 1009 | Assert.AreEqual(3, rows.Count); 1010 | Assert.IsTrue(rows.All(x => x.value == "fizz")); 1011 | } 1012 | 1013 | [Test] 1014 | public async Task ExistsCompositePK() 1015 | { 1016 | await db.ExecuteAsync("DELETE FROM composite_pk"); 1017 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 1018 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 1019 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 1020 | 1021 | Assert.IsTrue(await db.ExistsAsync(new { id1 = 1, id2 = 2 })); 1022 | Assert.IsFalse(await db.ExistsAsync(new { id1 = 2, id2 = 1 })); 1023 | } 1024 | 1025 | [Test] 1026 | public async Task SingleCompositePK() 1027 | { 1028 | await db.ExecuteAsync("DELETE FROM composite_pk"); 1029 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 1, value = "fizz" }); 1030 | await db.InsertAsync(new composite_pk { id1 = 1, id2 = 2, value = "fizz" }); 1031 | await db.InsertAsync(new composite_pk { id1 = 2, id2 = 2, value = "fizz" }); 1032 | 1033 | Assert.IsNotNull(await db.SingleAsync(new { id1 = 1, id2 = 2 })); 1034 | } 1035 | 1036 | [Test] 1037 | public async Task NullableInt() 1038 | { 1039 | var x = await db.ExecuteScalarAsync(Sql.Builder.Select("NULL")); 1040 | var y = await db.ExecuteScalarAsync(Sql.Builder.Select("8")); 1041 | Assert.AreEqual(null, x); 1042 | Assert.AreEqual(8, y); 1043 | } 1044 | } 1045 | } 1046 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/MySql/MySqlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | using NUnit.Framework; 8 | 9 | namespace AsyncPoco.Tests.MySql 10 | { 11 | public class MySqlTests : DatabaseTests 12 | { 13 | protected override string ConnStrName { get; } = "mysql"; 14 | protected override string ConnStr { get; } = @"server=localhost;database=asyncpoco;user id=asyncpoco;password=asyncpoco;Allow User Variables=true"; 15 | protected override string DbProviderName { get; } = "MySql.Data.MySqlClient"; 16 | 17 | [TableName("posts")] 18 | [PrimaryKey("id")] 19 | public class post 20 | { 21 | public long id { get; set; } 22 | public string title { get; set; } 23 | public long author { get; set; } 24 | 25 | [ResultColumn] public author author_obj { get; set; } 26 | } 27 | 28 | [TableName("authors")] 29 | [PrimaryKey("id")] 30 | public class author 31 | { 32 | public long id { get; set; } 33 | public string name { get; set; } 34 | 35 | [ResultColumn] 36 | public List posts { get; set; } 37 | } 38 | 39 | public override async Task CreateDbAsync() { 40 | await base.CreateDbAsync(); 41 | 42 | await db.ExecuteAsync(@" 43 | 44 | DROP TABLE IF EXISTS posts; 45 | DROP TABLE IF EXISTS authors; 46 | 47 | CREATE TABLE posts ( 48 | id bigint AUTO_INCREMENT NOT NULL, 49 | title varchar(127) NOT NULL, 50 | author bigint NOT NULL, 51 | PRIMARY KEY (id) 52 | ) ENGINE=INNODB; 53 | 54 | CREATE TABLE authors ( 55 | id bigint AUTO_INCREMENT NOT NULL, 56 | name varchar(127) NOT NULL, 57 | PRIMARY KEY (id) 58 | ) ENGINE=INNODB; 59 | 60 | "); 61 | 62 | var a1 = new author(); 63 | a1.name = "Bill"; 64 | await db.InsertAsync(a1); 65 | 66 | var a2 = new author(); 67 | a2.name = "Ted"; 68 | await db.InsertAsync(a2); 69 | 70 | var p = new post(); 71 | p.title = "post1"; 72 | p.author = a1.id; 73 | await db.InsertAsync(p); 74 | 75 | p = new post(); 76 | p.title = "post2"; 77 | p.author = a1.id; 78 | await db.InsertAsync(p); 79 | 80 | p = new post(); 81 | p.title = "post3"; 82 | p.author = a2.id; 83 | await db.InsertAsync(p); 84 | } 85 | 86 | public override async Task DeleteDbAsync() { 87 | await base.DeleteDbAsync(); 88 | 89 | await db.ExecuteAsync(@" 90 | DROP TABLE IF EXISTS posts; 91 | DROP TABLE IF EXISTS authors; 92 | "); 93 | } 94 | 95 | [Test] 96 | public async Task Basic() { 97 | var posts = await db.FetchAsync("SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id"); 98 | Assert.AreEqual(posts.Count, 3); 99 | 100 | Assert.AreEqual(posts[0].id, 1); 101 | Assert.AreEqual(posts[0].title, "post1"); 102 | Assert.AreEqual(posts[0].author, 1); 103 | Assert.AreEqual(posts[0].author_obj.name, "Bill"); 104 | Assert.AreEqual(posts[1].id, 2); 105 | Assert.AreEqual(posts[1].title, "post2"); 106 | Assert.AreEqual(posts[1].author, 1); 107 | Assert.AreEqual(posts[1].author_obj.name, "Bill"); 108 | Assert.AreEqual(posts[2].id, 3); 109 | Assert.AreEqual(posts[2].title, "post3"); 110 | Assert.AreEqual(posts[2].author, 2); 111 | Assert.AreEqual(posts[2].author_obj.name, "Ted"); 112 | } 113 | 114 | [Test] 115 | public async Task CustomRelator() { 116 | var posts = await db.FetchAsync( 117 | (p, a) => 118 | { 119 | p.author_obj = a; 120 | return p; 121 | }, 122 | "SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id"); 123 | 124 | Assert.AreEqual(posts.Count, 3); 125 | Assert.AreNotSame(posts[0].author_obj, posts[1].author_obj); 126 | Assert.AreEqual(posts[0].id, 1); 127 | Assert.AreEqual(posts[0].title, "post1"); 128 | Assert.AreEqual(posts[0].author, 1); 129 | Assert.AreEqual(posts[0].author_obj.name, "Bill"); 130 | Assert.AreEqual(posts[1].id, 2); 131 | Assert.AreEqual(posts[1].title, "post2"); 132 | Assert.AreEqual(posts[1].author, 1); 133 | Assert.AreEqual(posts[1].author_obj.name, "Bill"); 134 | Assert.AreEqual(posts[2].id, 3); 135 | Assert.AreEqual(posts[2].title, "post3"); 136 | Assert.AreEqual(posts[2].author, 2); 137 | Assert.AreEqual(posts[2].author_obj.name, "Ted"); 138 | } 139 | 140 | // Relator callback to do many to one relationship mapping 141 | class PostAuthorRelator 142 | { 143 | // A dictionary of known authors 144 | Dictionary authors = new Dictionary(); 145 | 146 | public post MapIt(post p, author a) { 147 | // Get existing author object, or if not found store this one 148 | author aExisting; 149 | if (authors.TryGetValue(a.id, out aExisting)) 150 | a = aExisting; 151 | else 152 | authors.Add(a.id, a); 153 | 154 | // Wire up objects 155 | p.author_obj = a; 156 | return p; 157 | } 158 | } 159 | 160 | [Test] 161 | public async Task ManyToOne() { 162 | // This test uses a custom relator callback to connect posts to existing author instances 163 | // Note that for each row, an author object is still created - it's just that the duplicates 164 | // are discarded 165 | 166 | var posts = await db.FetchAsync(new PostAuthorRelator().MapIt, 167 | "SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id" 168 | ); 169 | 170 | 171 | Assert.AreEqual(posts.Count, 3); 172 | Assert.AreSame(posts[0].author_obj, posts[1].author_obj); 173 | 174 | Assert.AreEqual(posts[0].id, 1); 175 | Assert.AreEqual(posts[0].title, "post1"); 176 | Assert.AreEqual(posts[0].author, 1); 177 | Assert.AreEqual(posts[0].author_obj.name, "Bill"); 178 | 179 | Assert.AreEqual(posts[1].id, 2); 180 | Assert.AreEqual(posts[1].title, "post2"); 181 | Assert.AreEqual(posts[1].author, 1); 182 | Assert.AreEqual(posts[1].author_obj.name, "Bill"); 183 | 184 | Assert.AreEqual(posts[2].id, 3); 185 | Assert.AreEqual(posts[2].title, "post3"); 186 | Assert.AreEqual(posts[2].author, 2); 187 | Assert.AreEqual(posts[2].author_obj.name, "Ted"); 188 | } 189 | 190 | class AuthorPostRelator 191 | { 192 | 193 | /* 194 | * In order to support OneToMany relationship mapping, we need to be able to 195 | * delay returning an LHS object until we've processed its many RHS objects 196 | * 197 | * To support this, PetaPoco allows a relator callback to return null - indicating 198 | * that the object isn't yet fully populated. 199 | * 200 | * In order to flush the final object, PetaPoco will call the relator function 201 | * one final time with all parameters set to null. It only does this if the callback 202 | * returned null at least once during the processing of the result set (this saves 203 | * simple lamba mapping functions from having to deal with nulls). 204 | * 205 | */ 206 | public author current; 207 | public author MapIt(author a, post p) { 208 | // Terminating call. Since we can return null from this function 209 | // we need to be ready for PetaPoco to callback later with null 210 | // parameters 211 | if (a == null) 212 | return current; 213 | 214 | // Is this the same author as the current one we're processing 215 | if (current != null && current.id == a.id) { 216 | // Yes, just add this post to the current author's collection of posts 217 | current.posts.Add(p); 218 | 219 | // Return null to indicate we're not done with this author yet 220 | return null; 221 | } 222 | 223 | // This is a different author to the current one, or this is the 224 | // first time through and we don't have an author yet 225 | 226 | // Save the current author 227 | var prev = current; 228 | 229 | // Setup the new current author 230 | current = a; 231 | current.posts = new List(); 232 | current.posts.Add(p); 233 | 234 | // Return the now populated previous author (or null if first time through) 235 | return prev; 236 | } 237 | } 238 | 239 | [Test] 240 | public async Task OneToMany() { 241 | // Example of OneToMany mappings 242 | 243 | var authors = await db.FetchAsync(new AuthorPostRelator().MapIt, 244 | "SELECT * FROM authors LEFT JOIN posts ON posts.author = authors.id ORDER BY posts.id" 245 | ); 246 | 247 | Assert.AreEqual(authors.Count, 2); 248 | Assert.AreEqual(authors[0].name, "Bill"); 249 | Assert.AreEqual(authors[0].posts.Count, 2); 250 | Assert.AreEqual(authors[0].posts[0].title, "post1"); 251 | Assert.AreEqual(authors[0].posts[1].title, "post2"); 252 | Assert.AreEqual(authors[1].name, "Ted"); 253 | Assert.AreEqual(authors[1].posts.Count, 1); 254 | Assert.AreEqual(authors[1].posts[0].title, "post3"); 255 | } 256 | 257 | [Test] 258 | public async Task ManyToOne_Lambda() { 259 | // same as ManyToOne test case above, but uses a lambda method as the callback 260 | var authors = new Dictionary(); 261 | var posts = await db.FetchAsync( 262 | (p, a) => { 263 | // Get existing author object 264 | author aExisting; 265 | if (authors.TryGetValue(a.id, out aExisting)) 266 | a = aExisting; 267 | else 268 | authors.Add(a.id, a); 269 | 270 | // Wire up objects 271 | p.author_obj = a; 272 | return p; 273 | }, 274 | "SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id" 275 | ); 276 | 277 | 278 | Assert.AreEqual(posts.Count, 3); 279 | Assert.AreSame(posts[0].author_obj, posts[1].author_obj); 280 | Assert.AreEqual(posts[0].id, 1); 281 | Assert.AreEqual(posts[0].title, "post1"); 282 | Assert.AreEqual(posts[0].author, 1); 283 | Assert.AreEqual(posts[0].author_obj.name, "Bill"); 284 | Assert.AreEqual(posts[1].id, 2); 285 | Assert.AreEqual(posts[1].title, "post2"); 286 | Assert.AreEqual(posts[1].author, 1); 287 | Assert.AreEqual(posts[1].author_obj.name, "Bill"); 288 | Assert.AreEqual(posts[2].id, 3); 289 | Assert.AreEqual(posts[2].title, "post3"); 290 | Assert.AreEqual(posts[2].author, 2); 291 | Assert.AreEqual(posts[2].author_obj.name, "Ted"); 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/MySql/mysql_done.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS petapoco; 2 | DROP TABLE IF EXISTS petapoco2; 3 | DROP TABLE IF EXISTS composite_pk; 4 | 5 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/MySql/mysql_init.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS petapoco; 2 | 3 | CREATE TABLE petapoco ( 4 | 5 | id serial, 6 | title varchar(127) NOT NULL, 7 | draft BOOL NOT NULL, 8 | date_created datetime NOT NULL, 9 | date_edited datetime NULL, 10 | content longtext NOT NULL, 11 | state smallint UNSIGNED NOT NULL, 12 | state2 smallint UNSIGNED NULL, 13 | `col w space` int, 14 | nullreal float NULL, 15 | 16 | PRIMARY KEY (id) 17 | ) ENGINE=INNODB; 18 | 19 | DROP TABLE IF EXISTS petapoco2; 20 | 21 | CREATE TABLE petapoco2 ( 22 | email varchar(127) NOT NULL, 23 | name varchar(127) NOT NULL, 24 | PRIMARY KEY (email) 25 | ) ENGINE=INNODB; 26 | 27 | DROP TABLE IF EXISTS composite_pk; 28 | 29 | CREATE TABLE composite_pk ( 30 | id1 int NOT NULL, 31 | id2 int NOT NULL, 32 | value varchar(100) NOT NULL, 33 | PRIMARY KEY (id1, id2) 34 | ) ENGINE=INNODB; 35 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/PostgreSql/PostgreSqlTests.cs: -------------------------------------------------------------------------------- 1 | #if !NETCOREAPP1_0 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Npgsql; 8 | 9 | namespace AsyncPoco.Tests.PostgreSql 10 | { 11 | public class PostgreSqlTests : DatabaseTests 12 | { 13 | protected override string ConnStrName { get; } = "postgresql"; 14 | protected override string ConnStr { get; } = @"Server=127.0.0.1;User id=postgres;password=password01;Database=postgres;"; 15 | protected override string DbProviderName { get; } = "Npgsql"; 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/PostgreSql/postgresql_done.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS petapoco; 2 | DROP TABLE IF EXISTS petapoco2; 3 | DROP TABLE IF EXISTS composite_pk; 4 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/PostgreSql/postgresql_init.sql: -------------------------------------------------------------------------------- 1 |  2 | DROP TABLE IF EXISTS petapoco; 3 | 4 | CREATE TABLE petapoco ( 5 | id bigserial NOT NULL, 6 | title varchar(127) NOT NULL, 7 | draft boolean NOT NULL, 8 | date_created timestamp NOT NULL, 9 | date_edited timestamp NULL, 10 | content text NOT NULL, 11 | state int NOT NULL, 12 | state2 int NULL, 13 | "col w space" int, 14 | nullreal real NULL, 15 | 16 | PRIMARY KEY (id) 17 | ); 18 | 19 | DROP TABLE IF EXISTS petapoco2; 20 | 21 | CREATE TABLE petapoco2 ( 22 | email varchar(127) NOT NULL, 23 | name varchar(127) NOT NULL, 24 | PRIMARY KEY (email) 25 | ); 26 | 27 | DROP TABLE IF EXISTS composite_pk; 28 | 29 | CREATE TABLE composite_pk ( 30 | id1 int NOT NULL, 31 | id2 int NOT NULL, 32 | value varchar(100) NOT NULL, 33 | PRIMARY KEY (id1, id2) 34 | ); 35 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/SQLite/SQLiteTests.cs: -------------------------------------------------------------------------------- 1 | #if !NETCOREAPP1_0 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Data.SQLite; 8 | 9 | namespace AsyncPoco.Tests.SQLite 10 | { 11 | public class SQLiteTests : DatabaseTests 12 | { 13 | protected override string ConnStrName { get; } = "sqlite"; 14 | protected override string ConnStr { get; } = @"Data Source=.\asyncpoco.sqlite;DateTimeKind=Utc"; 15 | protected override string DbProviderName { get; } = "System.Data.SQLite"; 16 | } 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/SQLite/sqlite_done.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS petapoco; 2 | DROP TABLE IF EXISTS petapoco2; 3 | DROP TABLE IF EXISTS composite_pk; -------------------------------------------------------------------------------- /AsyncPoco.Tests/SQLite/sqlite_init.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS petapoco; 2 | DROP TABLE IF EXISTS petapoco2; 3 | DROP TABLE IF EXISTS composite_pk; 4 | 5 | CREATE TABLE petapoco ( 6 | 7 | id INTEGER PRIMARY KEY AUTOINCREMENT, 8 | title TEXT NOT NULL, 9 | draft BOOLEAN NOT NULL, 10 | date_created DATETIME NOT NULL, 11 | date_edited DATETIME NULL, 12 | content TEXT NOT NULL, 13 | state INTEGER NOT NULL, 14 | state2 INTEGER NULL, 15 | [col w space] INTEGER, 16 | nullreal REAL NULL 17 | ); 18 | 19 | CREATE TABLE petapoco2 ( 20 | email TEXT NOT NULL, 21 | name TEXT NOT NULL, 22 | PRIMARY KEY (email) 23 | ); 24 | 25 | CREATE TABLE composite_pk ( 26 | id1 INT NOT NULL, 27 | id2 INT NOT NULL, 28 | value TEXT NOT NULL, 29 | PRIMARY KEY (id1, id2) 30 | ); 31 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace AsyncPoco.Tests 5 | { 6 | public class SqlBuilderTests 7 | { 8 | [Test] 9 | public void simple_append() 10 | { 11 | var sql = new Sql(); 12 | sql.Append("LINE 1"); 13 | sql.Append("LINE 2"); 14 | sql.Append("LINE 3"); 15 | 16 | Assert.AreEqual("LINE 1\nLINE 2\nLINE 3", sql.SQL); 17 | Assert.AreEqual(0, sql.Arguments.Length); 18 | } 19 | 20 | [Test] 21 | public void single_arg() 22 | { 23 | var sql = new Sql(); 24 | sql.Append("arg @0", "a1"); 25 | 26 | Assert.AreEqual("arg @0", sql.SQL); 27 | Assert.AreEqual(1, sql.Arguments.Length); 28 | Assert.AreEqual("a1", sql.Arguments[0]); 29 | } 30 | 31 | [Test] 32 | public void multiple_args() 33 | { 34 | var sql = new Sql(); 35 | sql.Append("arg @0 @1", "a1", "a2"); 36 | 37 | Assert.AreEqual("arg @0 @1", sql.SQL); 38 | Assert.AreEqual(2, sql.Arguments.Length); 39 | Assert.AreEqual("a1", sql.Arguments[0]); 40 | Assert.AreEqual("a2", sql.Arguments[1]); 41 | } 42 | 43 | [Test] 44 | public void unused_args() 45 | { 46 | var sql = new Sql(); 47 | sql.Append("arg @0 @2", "a1", "a2", "a3"); 48 | 49 | Assert.AreEqual("arg @0 @1", sql.SQL); 50 | Assert.AreEqual(2, sql.Arguments.Length); 51 | Assert.AreEqual("a1", sql.Arguments[0]); 52 | Assert.AreEqual("a3", sql.Arguments[1]); 53 | } 54 | 55 | [Test] 56 | public void unordered_args() 57 | { 58 | var sql = new Sql(); 59 | sql.Append("arg @2 @1", "a1", "a2", "a3"); 60 | 61 | Assert.AreEqual("arg @0 @1", sql.SQL); 62 | Assert.AreEqual(2, sql.Arguments.Length); 63 | Assert.AreEqual("a3", sql.Arguments[0]); 64 | Assert.AreEqual("a2", sql.Arguments[1]); 65 | } 66 | 67 | [Test] 68 | public void repeated_args() 69 | { 70 | var sql = new Sql(); 71 | sql.Append("arg @0 @1 @0 @1", "a1", "a2"); 72 | 73 | Assert.AreEqual("arg @0 @1 @2 @3", sql.SQL); 74 | Assert.AreEqual(4, sql.Arguments.Length); 75 | Assert.AreEqual("a1", sql.Arguments[0]); 76 | Assert.AreEqual("a2", sql.Arguments[1]); 77 | Assert.AreEqual("a1", sql.Arguments[2]); 78 | Assert.AreEqual("a2", sql.Arguments[3]); 79 | } 80 | 81 | [Test] 82 | public void mysql_user_vars() 83 | { 84 | var sql = new Sql(); 85 | sql.Append("arg @@user1 @2 @1 @@@system1", "a1", "a2", "a3"); 86 | 87 | Assert.AreEqual("arg @@user1 @0 @1 @@@system1", sql.SQL); 88 | Assert.AreEqual(2, sql.Arguments.Length); 89 | Assert.AreEqual("a3", sql.Arguments[0]); 90 | Assert.AreEqual("a2", sql.Arguments[1]); 91 | } 92 | 93 | [Test] 94 | public void named_args() 95 | { 96 | var sql = new Sql(); 97 | sql.Append("arg @name @password", new { name = "n", password = "p" }); 98 | 99 | Assert.AreEqual("arg @0 @1", sql.SQL); 100 | Assert.AreEqual(2, sql.Arguments.Length); 101 | Assert.AreEqual("n", sql.Arguments[0]); 102 | Assert.AreEqual("p", sql.Arguments[1]); 103 | } 104 | 105 | [Test] 106 | public void mixed_named_and_numbered_args() 107 | { 108 | var sql = new Sql(); 109 | sql.Append("arg @0 @name @1 @password @2", "a1", "a2", "a3", new { name = "n", password = "p" }); 110 | 111 | Assert.AreEqual("arg @0 @1 @2 @3 @4", sql.SQL); 112 | Assert.AreEqual(5, sql.Arguments.Length); 113 | Assert.AreEqual("a1", sql.Arguments[0]); 114 | Assert.AreEqual("n", sql.Arguments[1]); 115 | Assert.AreEqual("a2", sql.Arguments[2]); 116 | Assert.AreEqual("p", sql.Arguments[3]); 117 | Assert.AreEqual("a3", sql.Arguments[4]); 118 | } 119 | 120 | [Test] 121 | public void append_with_args() 122 | { 123 | var sql = new Sql(); 124 | sql.Append("l1 @0", "a0"); 125 | sql.Append("l2 @0", "a1"); 126 | sql.Append("l3 @0", "a2"); 127 | 128 | Assert.AreEqual("l1 @0\nl2 @1\nl3 @2", sql.SQL); 129 | Assert.AreEqual(3, sql.Arguments.Length); 130 | Assert.AreEqual("a0", sql.Arguments[0]); 131 | Assert.AreEqual("a1", sql.Arguments[1]); 132 | Assert.AreEqual("a2", sql.Arguments[2]); 133 | } 134 | 135 | [Test] 136 | public void append_with_args2() 137 | { 138 | var sql = new Sql(); 139 | sql.Append("l1"); 140 | sql.Append("l2 @0 @1", "a1", "a2"); 141 | sql.Append("l3 @0", "a3"); 142 | 143 | Assert.AreEqual("l1\nl2 @0 @1\nl3 @2", sql.SQL); 144 | Assert.AreEqual(3, sql.Arguments.Length); 145 | Assert.AreEqual("a1", sql.Arguments[0]); 146 | Assert.AreEqual("a2", sql.Arguments[1]); 147 | Assert.AreEqual("a3", sql.Arguments[2]); 148 | } 149 | 150 | [Test] 151 | public void invalid_arg_index() 152 | { 153 | Assert.Throws(() => 154 | { 155 | var sql = new Sql(); 156 | sql.Append("arg @0 @1", "a0"); 157 | Assert.AreEqual("arg @0 @1", sql.SQL); 158 | }); 159 | } 160 | 161 | [Test] 162 | public void invalid_arg_name() 163 | { 164 | Assert.Throws(() => 165 | { 166 | var sql = new Sql(); 167 | sql.Append("arg @name1 @name2", new { x = 1, y = 2 }); 168 | Assert.AreEqual("arg @0 @1", sql.SQL); 169 | }); 170 | } 171 | 172 | [Test] 173 | public void append_instances() 174 | { 175 | var sql = new Sql("l0 @0", "a0"); 176 | var sql1 = new Sql("l1 @0", "a1"); 177 | var sql2 = new Sql("l2 @0", "a2"); 178 | 179 | Assert.AreSame(sql, sql.Append(sql1)); 180 | Assert.AreSame(sql, sql.Append(sql2)); 181 | 182 | Assert.AreEqual("l0 @0\nl1 @1\nl2 @2", sql.SQL); 183 | Assert.AreEqual(3, sql.Arguments.Length); 184 | Assert.AreEqual("a0", sql.Arguments[0]); 185 | Assert.AreEqual("a1", sql.Arguments[1]); 186 | Assert.AreEqual("a2", sql.Arguments[2]); 187 | } 188 | 189 | [Test] 190 | public void ConsecutiveWhere() 191 | { 192 | var sql = new Sql() 193 | .Append("SELECT * FROM blah"); 194 | 195 | sql.Append("WHERE x"); 196 | sql.Append("WHERE y"); 197 | 198 | Assert.AreEqual("SELECT * FROM blah\nWHERE x\nAND y", sql.SQL); 199 | } 200 | 201 | [Test] 202 | public void ConsecutiveOrderBy() 203 | { 204 | var sql = new Sql() 205 | .Append("SELECT * FROM blah"); 206 | 207 | sql.Append("ORDER BY x"); 208 | sql.Append("ORDER BY y"); 209 | 210 | Assert.AreEqual("SELECT * FROM blah\nORDER BY x\n, y", sql.SQL); 211 | } 212 | 213 | [Test] 214 | public void param_expansion_1() 215 | { 216 | // Simple collection parameter expansion 217 | var sql = Sql.Builder.Append("@0 IN (@1) @2", 20, new int[] { 1, 2, 3 }, 30); 218 | Assert.AreEqual("@0 IN (@1,@2,@3) @4", sql.SQL); 219 | Assert.AreEqual(5, sql.Arguments.Length); 220 | Assert.AreEqual(20, sql.Arguments[0]); 221 | Assert.AreEqual(1, sql.Arguments[1]); 222 | Assert.AreEqual(2, sql.Arguments[2]); 223 | Assert.AreEqual(3, sql.Arguments[3]); 224 | Assert.AreEqual(30, sql.Arguments[4]); 225 | } 226 | 227 | [Test] 228 | public void param_expansion_2() 229 | { 230 | // Out of order expansion 231 | var sql = Sql.Builder.Append("IN (@3) (@1)", null, new int[] { 1, 2, 3 }, null, new int[] { 4, 5, 6 }); 232 | Assert.AreEqual("IN (@0,@1,@2) (@3,@4,@5)", sql.SQL); 233 | Assert.AreEqual(6, sql.Arguments.Length); 234 | Assert.AreEqual(4, sql.Arguments[0]); 235 | Assert.AreEqual(5, sql.Arguments[1]); 236 | Assert.AreEqual(6, sql.Arguments[2]); 237 | Assert.AreEqual(1, sql.Arguments[3]); 238 | Assert.AreEqual(2, sql.Arguments[4]); 239 | Assert.AreEqual(3, sql.Arguments[5]); 240 | } 241 | 242 | [Test] 243 | public void param_expansion_named() 244 | { 245 | // Expand a named parameter 246 | var sql = Sql.Builder.Append("IN (@numbers)", new { numbers = (new int[] { 1, 2, 3 }) }); 247 | Assert.AreEqual("IN (@0,@1,@2)", sql.SQL); 248 | Assert.AreEqual(3, sql.Arguments.Length); 249 | Assert.AreEqual(1, sql.Arguments[0]); 250 | Assert.AreEqual(2, sql.Arguments[1]); 251 | Assert.AreEqual(3, sql.Arguments[2]); 252 | } 253 | 254 | [Test] 255 | public void select_two_columns() 256 | { 257 | var sql = Sql.Builder 258 | .Select("FirstName", "LastName"); 259 | 260 | // TODO: May want to consider tests with spaces in column names (for each DB type). 261 | Assert.AreEqual("SELECT FirstName, LastName", sql.SQL); 262 | } 263 | 264 | [Test] 265 | public void left_join() 266 | { 267 | var sql = Sql.Builder 268 | .Select("*") 269 | .From("articles") 270 | .LeftJoin("comments").On("articles.article_id=comments.article_id"); 271 | Assert.AreEqual("SELECT *\nFROM articles\nLEFT JOIN comments\nON articles.article_id=comments.article_id", sql.SQL); 272 | } 273 | } 274 | 275 | } -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServer/SqlServerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Data.SqlClient; 7 | using NUnit.Framework; 8 | 9 | namespace AsyncPoco.Tests.SqlServer 10 | { 11 | public class SqlServerTests : DatabaseTests 12 | { 13 | protected override string ConnStrName { get; } = "sqlserver"; 14 | protected override string ConnStr { get; } = @"Server=(LocalDB)\MSSQLLocalDB; Integrated Security=True"; 15 | protected override string DbProviderName { get; } = "System.Data.SqlClient"; 16 | 17 | [Test] 18 | public void EscapeColumnName() { 19 | Assert.AreEqual(db._dbType.EscapeSqlIdentifier("column.name"), "[column.name]"); 20 | Assert.AreEqual(db._dbType.EscapeSqlIdentifier("column name"), "[column name]"); 21 | } 22 | 23 | [Test] 24 | public void EscapeTableName() { 25 | Assert.AreEqual(db._dbType.EscapeTableName("column.name"), "column.name"); 26 | Assert.AreEqual(db._dbType.EscapeTableName("column name"), "[column name]"); 27 | } 28 | 29 | enum Fruits 30 | { 31 | Apples, 32 | Pears, 33 | Bananas 34 | } 35 | 36 | enum Fruits2 37 | { 38 | Oranges 39 | } 40 | 41 | [Test] 42 | public void EnumMapper() { 43 | Assert.AreEqual(Fruits.Apples, Internal.EnumMapper.EnumFromString(typeof(Fruits), "Apples")); 44 | Assert.AreEqual(Fruits.Pears, Internal.EnumMapper.EnumFromString(typeof(Fruits), "pears")); 45 | Assert.AreEqual(Fruits.Bananas, Internal.EnumMapper.EnumFromString(typeof(Fruits), "BANANAS")); 46 | Assert.AreEqual(Fruits2.Oranges, Internal.EnumMapper.EnumFromString(typeof(Fruits2), "Oranges")); 47 | 48 | // nullable enums 49 | Assert.AreEqual(Fruits.Apples, Internal.EnumMapper.EnumFromString(typeof(Fruits?), "Apples")); 50 | Assert.AreEqual(null, Internal.EnumMapper.EnumFromString(typeof(Fruits?), null)); 51 | 52 | Assert.Throws(() => Internal.EnumMapper.EnumFromString(typeof(Fruits2), "Apples")); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServer/sqlserver_done.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('dbo.petapoco','U') IS NOT NULL 2 | DROP TABLE dbo.petapoco; 3 | IF OBJECT_ID('dbo.petapoco2','U') IS NOT NULL 4 | DROP TABLE dbo.petapoco2; 5 | IF OBJECT_ID('dbo.composite_pk','U') IS NOT NULL 6 | DROP TABLE dbo.composite_pk; 7 | 8 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServer/sqlserver_init.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID('dbo.petapoco','U') IS NOT NULL 2 | DROP TABLE dbo.petapoco; 3 | 4 | CREATE TABLE petapoco ( 5 | 6 | id bigint IDENTITY(1,1) NOT NULL, 7 | title varchar(127) NOT NULL, 8 | draft bit NOT NULL, 9 | date_created datetime NOT NULL, 10 | date_edited datetime NULL, 11 | content VARCHAR(MAX) NOT NULL, 12 | state int NOT NULL, 13 | state2 int NULL, 14 | [col w space] int, 15 | nullreal real NULL, 16 | 17 | PRIMARY KEY (id) 18 | ); 19 | 20 | IF OBJECT_ID('dbo.petapoco2','U') IS NOT NULL 21 | DROP TABLE dbo.petapoco2; 22 | 23 | CREATE TABLE petapoco2 ( 24 | email varchar(127) NOT NULL, 25 | name varchar(127) NOT NULL, 26 | PRIMARY KEY (email) 27 | ); 28 | 29 | IF OBJECT_ID('dbo.composite_pk','U') IS NOT NULL 30 | DROP TABLE dbo.composite_pk; 31 | 32 | CREATE TABLE composite_pk ( 33 | id1 int NOT NULL, 34 | id2 int NOT NULL, 35 | value varchar(100) NOT NULL, 36 | PRIMARY KEY (id1, id2) 37 | ); 38 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServerCE/SqlServerCETests.cs: -------------------------------------------------------------------------------- 1 | #if NET45 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Data.SqlServerCe; 8 | 9 | namespace AsyncPoco.Tests.SqlServerCE 10 | { 11 | public class SqlServerCETests : DatabaseTests 12 | { 13 | protected override string ConnStrName { get; } = "sqlserverce"; 14 | protected override string ConnStr { get; } = @"Data Source=|DataDirectory|\petapoco.sdf"; 15 | protected override string DbProviderName { get; } = "System.Data.SqlServerCe"; 16 | } 17 | } 18 | #endif -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServerCE/petapoco.sdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmenier/AsyncPoco/7311f4c70b7bb2df518accfbf0d9871a7e65b33b/AsyncPoco.Tests/SqlServerCE/petapoco.sdf -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServerCE/sqlserverce_done.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM petapoco; 2 | DELETE FROM petapoco2; 3 | DELETE FROM composite_pk; -------------------------------------------------------------------------------- /AsyncPoco.Tests/SqlServerCE/sqlserverce_init.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM petapoco; 2 | DELETE FROM petapoco2; 3 | DELETE FROM composite_pk; -------------------------------------------------------------------------------- /AsyncPoco.Tests/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace AsyncPoco.Tests 7 | { 8 | class Utils 9 | { 10 | public static string LoadTextResource(string name) 11 | { 12 | string result = ""; 13 | var assembly = typeof(Utils).GetTypeInfo().Assembly; 14 | if (assembly.GetManifestResourceNames().Contains(name)) 15 | { 16 | using (var stream = assembly.GetManifestResourceStream(name)) 17 | { 18 | using (var reader = new StreamReader(stream)) 19 | { 20 | result = reader.ReadToEnd(); 21 | } 22 | } 23 | } 24 | return result; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AsyncPoco.Tests/poco.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AsyncPoco.Tests 4 | { 5 | 6 | enum State 7 | { 8 | Yes, 9 | No, 10 | Maybe, 11 | } 12 | 13 | // Non-decorated true poco 14 | class poco 15 | { 16 | public long id { get; set; } 17 | public string title { get; set; } 18 | public bool draft { get; set; } 19 | public DateTime date_created { get; set; } 20 | public DateTime? date_edited { get; set; } 21 | public string content { get; set; } 22 | public State state { get; set; } 23 | public State? state2 { get; set; } 24 | [Column("col w space")] 25 | public int col_w_space { get; set; } 26 | public float? nullreal { get; set; } 27 | } 28 | 29 | 30 | // Attributed not-so-true poco 31 | [TableName("petapoco")] 32 | [PrimaryKey("id", sequenceName = "article_id_seq")] 33 | [ExplicitColumns] 34 | class deco 35 | { 36 | [Column] public long id { get; set; } 37 | [Column] public string title { get; set; } 38 | [Column] public bool draft { get; set; } 39 | [Column(ForceToUtc = true)] public DateTime date_created { get; set; } 40 | [Column(ForceToUtc = true)] public DateTime? date_edited { get; set; } 41 | [Column] public string content { get; set; } 42 | [Column] public State state { get; set; } 43 | [Column] public State? state2 { get; set; } 44 | [Column("col w space")] 45 | public int col_w_space { get; set; } 46 | [Column] public float? nullreal { get; set; } 47 | } 48 | // Attributed not-so-true poco 49 | [TableName("petapoco")] 50 | [PrimaryKey("id")] 51 | [ExplicitColumns] 52 | class deco_explicit 53 | { 54 | [Column] public long id { get; set; } 55 | [Column] public string title { get; set; } 56 | [Column] public bool draft { get; set; } 57 | [Column] public DateTime date_created { get; set; } 58 | [Column] public State state { get; set; } 59 | [Column] public State? state2 { get; set; } 60 | public string content { get; set; } 61 | [Column("col w space")] public int col_w_space { get; set; } 62 | [Column] public float? nullreal { get; set; } 63 | } 64 | 65 | // Attributed not-so-true poco 66 | [TableName("petapoco")] 67 | [PrimaryKey("id")] 68 | class deco_non_explicit 69 | { 70 | public long id { get; set; } 71 | public string title { get; set; } 72 | public bool draft { get; set; } 73 | public DateTime date_created { get; set; } 74 | public State state { get; set; } 75 | [Ignore] public string content { get; set; } 76 | [Column("col w space")] public int col_w_space { get; set; } 77 | public float? nullreal { get; set; } 78 | } 79 | 80 | [TableName("petapoco2")] 81 | [PrimaryKey("email", autoIncrement = false)] 82 | class petapoco2 83 | { 84 | public string email { get; set; } 85 | public string name { get; set; } 86 | } 87 | 88 | [TableName("composite_pk")] 89 | [PrimaryKey("id1, id2")] 90 | class composite_pk 91 | { 92 | public int id1 { get; set; } 93 | public int id2 { get; set; } 94 | public string value { get; set; } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /AsyncPoco.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncPoco", "AsyncPoco\AsyncPoco.csproj", "{33699753-151D-4100-8F12-9AE1AEB5C5BD}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncPoco.Tests", "AsyncPoco.Tests\AsyncPoco.Tests.csproj", "{E6787C52-7549-44CD-AA6E-AB9D55946AB9}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E35A3DC6-96E9-4049-99F4-F061ECE64B07}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE.txt = LICENSE.txt 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|Mixed Platforms = Debug|Mixed Platforms 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|Mixed Platforms = Release|Mixed Platforms 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 29 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 30 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 34 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU 35 | {33699753-151D-4100-8F12-9AE1AEB5C5BD}.Release|x86.ActiveCfg = Release|Any CPU 36 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 39 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 40 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 44 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Release|Mixed Platforms.Build.0 = Release|Any CPU 45 | {E6787C52-7549-44CD-AA6E-AB9D55946AB9}.Release|x86.ActiveCfg = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(ExtensibilityGlobals) = postSolution 51 | SolutionGuid = {7274F101-8074-40C7-9673-F1C33FB76242} 52 | EndGlobalSection 53 | EndGlobal 54 | -------------------------------------------------------------------------------- /AsyncPoco/AsyncPoco.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | AsyncPoco 5 | 2.0.2 6 | net45;netstandard1.3;netstandard2.0 7 | True 8 | Todd Menier 9 | A cross-platform, fully asynchronous fork of the popular PetaPoco micro-ORM. Supports SQL Server, Oracle, MySQL, PostgreSQL, SQLite, and SQL Server CE. Runs on .NET Framework (4.5 and above), .NET Core (1.0 and 2.0), Mono, Xamarin (iOS, Mac, and Android), and UWP. 10 | Copyright 2018 Todd Menier 11 | https://github.com/tmenier/AsyncPoco 12 | https://raw.githubusercontent.com/CollaboratingPlatypus/PetaPoco/development/LICENSE.txt 13 | orm micro-orm database sql async asynchronous netcore dotnetcore 14 | https://github.com/tmenier/AsyncPoco/releases 15 | https://github.com/tmenier/AsyncPoco 16 | git 17 | 18 | 19 | 20 | bin\Release\AsyncPoco.xml 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/ColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// For explicit poco properties, marks the property as a column and optionally 11 | /// supplies the DB column name. 12 | /// 13 | [AttributeUsage(AttributeTargets.Property)] 14 | public class ColumnAttribute : Attribute 15 | { 16 | public ColumnAttribute() 17 | { 18 | ForceToUtc = false; 19 | } 20 | 21 | public ColumnAttribute(string Name) 22 | { 23 | this.Name = Name; 24 | ForceToUtc = false; 25 | } 26 | 27 | public string Name 28 | { 29 | get; 30 | set; 31 | } 32 | 33 | public bool ForceToUtc 34 | { 35 | get; 36 | set; 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/ComputedColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | 10 | /// 11 | /// Marks a poco property as a computed column that is populated in queries 12 | /// but not used for updates or inserts. 13 | /// 14 | [AttributeUsage(AttributeTargets.Property)] 15 | public class ComputedColumnAttribute : ColumnAttribute 16 | { 17 | public ComputedColumnAttribute() 18 | { 19 | } 20 | 21 | public ComputedColumnAttribute(string name) 22 | : base(name) 23 | { 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/ExplicitColumnsAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// Poco classes marked with the Explicit attribute require all column properties to 11 | /// be marked with the Column attribute 12 | /// 13 | [AttributeUsage(AttributeTargets.Class)] 14 | public class ExplicitColumnsAttribute : Attribute 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/IgnoreAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// Use the Ignore attribute on POCO class properties that shouldn't be mapped 11 | /// by AsyncPoco. 12 | /// 13 | [AttributeUsage(AttributeTargets.Property)] 14 | public class IgnoreAttribute : Attribute 15 | { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/PrimaryKeyAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// Specifies the primary key column of a poco class, whether the column is auto incrementing 11 | /// and the sequence name for Oracle sequence columns. 12 | /// 13 | [AttributeUsage(AttributeTargets.Class)] 14 | public class PrimaryKeyAttribute : Attribute 15 | { 16 | public PrimaryKeyAttribute(string primaryKey) 17 | { 18 | Value = primaryKey; 19 | autoIncrement = !primaryKey.Contains(","); 20 | } 21 | 22 | public string Value 23 | { 24 | get; 25 | private set; 26 | } 27 | 28 | public string sequenceName 29 | { 30 | get; 31 | set; 32 | } 33 | 34 | public bool autoIncrement 35 | { 36 | get; 37 | set; 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/ResultColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | 10 | /// 11 | /// Marks a poco property as a result only column that is populated in queries 12 | /// when explicitly named 13 | /// but not used for updates or inserts. 14 | /// 15 | [AttributeUsage(AttributeTargets.Property)] 16 | public class ResultColumnAttribute : ColumnAttribute 17 | { 18 | public ResultColumnAttribute() 19 | { 20 | } 21 | 22 | public ResultColumnAttribute(string name) 23 | : base(name) 24 | { 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /AsyncPoco/Attributes/TableNameAttribute.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// Sets the DB table name to be used for a Poco class. 11 | /// 12 | [AttributeUsage(AttributeTargets.Class)] 13 | public class TableNameAttribute : Attribute 14 | { 15 | public TableNameAttribute(string tableName) 16 | { 17 | Value = tableName; 18 | } 19 | 20 | public string Value 21 | { 22 | get; 23 | private set; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /AsyncPoco/Core/AnsiString.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// Wrap strings in an instance of this class to force use of DBType.AnsiString 11 | /// 12 | public class AnsiString 13 | { 14 | /// 15 | /// Constructs an AnsiString 16 | /// 17 | /// The C# string to be converted to ANSI before being passed to the DB 18 | public AnsiString(string str) 19 | { 20 | Value = str; 21 | } 22 | 23 | /// 24 | /// The string value 25 | /// 26 | public string Value 27 | { 28 | get; 29 | private set; 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /AsyncPoco/Core/ColumnInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reflection; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// Hold information about a column in the database. 11 | /// 12 | /// 13 | /// Typically ColumnInfo is automatically populated from the attributes on a POCO object and it's properties. It can 14 | /// however also be returned from the IMapper interface to provide your owning bindings between the DB and your POCOs. 15 | /// 16 | public class ColumnInfo 17 | { 18 | /// 19 | /// The SQL name of the column 20 | /// 21 | public string ColumnName 22 | { 23 | get; 24 | set; 25 | } 26 | 27 | /// 28 | /// True if this column returns a calculated value from the database and shouldn't be used in Insert and Update operations. 29 | /// The column will not be included in Select operations by default. 30 | /// 31 | public bool ResultColumn 32 | { 33 | get; 34 | set; 35 | } 36 | 37 | /// 38 | /// True if this column returns a calculated value from the database and shouldn't be used in Insert and Update operations. 39 | /// The column will be included in Select operations by default. 40 | /// 41 | public bool ComputedColumn 42 | { 43 | get; 44 | set; 45 | } 46 | 47 | /// 48 | /// True if time and date values returned through this column should be forced to UTC DateTimeKind. (no conversion is applied - the Kind of the DateTime property 49 | /// is simply set to DateTimeKind.Utc instead of DateTimeKind.Unknown. 50 | /// 51 | public bool ForceToUtc 52 | { 53 | get; 54 | set; 55 | } 56 | 57 | /// 58 | /// Creates and populates a ColumnInfo from the attributes of a POCO property. 59 | /// 60 | /// The property whose column info is required 61 | /// A ColumnInfo instance 62 | public static ColumnInfo FromProperty(PropertyInfo pi) 63 | { 64 | // Check if declaring poco has [Explicit] attribute 65 | bool ExplicitColumns = pi.DeclaringType.GetTypeInfo().GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Any(); 66 | 67 | // Check for [Column]/[Ignore] Attributes 68 | var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true).ToArray(); 69 | if (ExplicitColumns) 70 | { 71 | if (ColAttrs.Length == 0) 72 | return null; 73 | } 74 | else 75 | { 76 | if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Any()) 77 | return null; 78 | } 79 | 80 | ColumnInfo ci = new ColumnInfo(); 81 | 82 | // Read attribute 83 | if (ColAttrs.Length > 0) 84 | { 85 | var colattr = (ColumnAttribute)ColAttrs[0]; 86 | 87 | ci.ColumnName = colattr.Name==null ? pi.Name : colattr.Name; 88 | ci.ForceToUtc = colattr.ForceToUtc; 89 | if ((colattr as ResultColumnAttribute) != null) 90 | ci.ResultColumn = true; 91 | if ((colattr as ComputedColumnAttribute) != null) 92 | ci.ComputedColumn = true; 93 | 94 | } 95 | else 96 | { 97 | ci.ColumnName = pi.Name; 98 | ci.ForceToUtc = false; 99 | ci.ResultColumn = false; 100 | ci.ComputedColumn = false; 101 | } 102 | 103 | return ci; 104 | 105 | 106 | 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /AsyncPoco/Core/DatabaseType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Data; 7 | using System.Globalization; 8 | using System.Threading.Tasks; 9 | using AsyncPoco.DatabaseTypes; 10 | using AsyncPoco.Internal; 11 | 12 | namespace AsyncPoco.Internal 13 | { 14 | /// 15 | /// Base class for DatabaseType handlers - provides default/common handling for different database engines 16 | /// 17 | abstract class DatabaseType 18 | { 19 | /// 20 | /// Returns the prefix used to delimit parameters in SQL query strings. 21 | /// 22 | public virtual string ParameterPrefix { get; } = "@"; 23 | 24 | /// 25 | /// Converts a supplied C# object value into a value suitable for passing to the database 26 | /// 27 | /// The value to convert 28 | /// The converted value 29 | public virtual object MapParameterValue(object value) 30 | { 31 | // Cast bools to integer 32 | if (value.GetType() == typeof(bool)) 33 | { 34 | return ((bool)value) ? 1 : 0; 35 | } 36 | 37 | // Leave it 38 | return value; 39 | } 40 | 41 | /// 42 | /// Called immediately before a command is executed, allowing for modification of the IDbCommand before it's passed to the database provider 43 | /// 44 | /// 45 | public virtual void PreExecute(IDbCommand cmd) 46 | { 47 | } 48 | 49 | /// 50 | /// Builds an SQL query suitable for performing page based queries to the database 51 | /// 52 | /// The number of rows that should be skipped by the query 53 | /// The number of rows that should be retruend by the query 54 | /// The original SQL query after being parsed into it's component parts 55 | /// Arguments to any embedded parameters in the SQL query 56 | /// The final SQL query that should be executed. 57 | public virtual string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) 58 | { 59 | var sql = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", parts.sql, args.Length, args.Length + 1); 60 | args = args.Concat(new object[] { take, skip }).ToArray(); 61 | return sql; 62 | } 63 | 64 | /// 65 | /// Returns an SQL Statement that can check for the existance of a row in the database. 66 | /// 67 | /// 68 | public virtual string GetExistsSql() 69 | { 70 | return "SELECT COUNT(*) FROM {0} WHERE {1}"; 71 | } 72 | 73 | /// 74 | /// Escape a tablename into a suitable format for the associated database provider. 75 | /// 76 | /// The name of the table (as specified by the client program, or as attributes on the associated POCO class. 77 | /// The escaped table name 78 | public virtual string EscapeTableName(string tableName) 79 | { 80 | // Assume table names with "dot" are already escaped 81 | return tableName.IndexOf('.') >= 0 ? tableName : EscapeSqlIdentifier(tableName); 82 | } 83 | 84 | /// 85 | /// Escape and arbitary SQL identifier into a format suitable for the associated database provider 86 | /// 87 | /// The SQL identifier to be escaped 88 | /// The escaped identifier 89 | public virtual string EscapeSqlIdentifier(string str) 90 | { 91 | return string.Format("[{0}]", str); 92 | } 93 | 94 | /// 95 | /// Return an SQL expression that can be used to populate the primary key column of an auto-increment column. 96 | /// 97 | /// Table info describing the table 98 | /// An SQL expressions 99 | /// See the Oracle database type for an example of how this method is used. 100 | public virtual string GetAutoIncrementExpression(TableInfo ti) 101 | { 102 | return null; 103 | } 104 | 105 | /// 106 | /// Returns an SQL expression that can be used to specify the return value of auto incremented columns. 107 | /// 108 | /// The primary key of the row being inserted. 109 | /// An expression describing how to return the new primary key value 110 | /// See the SQLServer database provider for an example of how this method is used. 111 | public virtual string GetInsertOutputClause(string primaryKeyName) 112 | { 113 | return string.Empty; 114 | } 115 | 116 | /// 117 | /// Performs an Insert operation 118 | /// 119 | /// The calling Database object 120 | /// The insert command to be executed 121 | /// The primary key of the table being inserted into 122 | /// The ID of the newly inserted record 123 | public virtual Task ExecuteInsertAsync(Database db, DbCommand cmd, string PrimaryKeyName) 124 | { 125 | cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; 126 | return db.ExecuteScalarHelperAsync(cmd); 127 | } 128 | 129 | 130 | /// 131 | /// Look at the type and provider name being used and instantiate a suitable DatabaseType instance. 132 | /// 133 | /// 134 | /// 135 | /// 136 | public static DatabaseType Resolve(string TypeName, string ProviderName) 137 | { 138 | // Try using type name first (more reliable) 139 | if (!string.IsNullOrEmpty(TypeName)) { 140 | if (TypeName.StartsWith("MySql", StringComparison.OrdinalIgnoreCase)) 141 | return Singleton.Instance; 142 | if (TypeName.StartsWith("SqlCe", StringComparison.OrdinalIgnoreCase)) 143 | return Singleton.Instance; 144 | if (TypeName.StartsWith("Npgsql", StringComparison.OrdinalIgnoreCase) || TypeName.StartsWith("PgSql", StringComparison.OrdinalIgnoreCase)) 145 | return Singleton.Instance; 146 | if (TypeName.StartsWith("Oracle", StringComparison.OrdinalIgnoreCase)) 147 | return Singleton.Instance; 148 | if (TypeName.StartsWith("SQLite", StringComparison.OrdinalIgnoreCase)) 149 | return Singleton.Instance; 150 | if (TypeName.StartsWith("System.Data.SqlClient.", StringComparison.OrdinalIgnoreCase)) 151 | return Singleton.Instance; 152 | } 153 | 154 | // Try again with provider name 155 | if (!string.IsNullOrEmpty(ProviderName)) { 156 | if (ProviderName.IndexOf("MySql", StringComparison.OrdinalIgnoreCase) >= 0) 157 | return Singleton.Instance; 158 | if (ProviderName.IndexOf("SqlServerCe", StringComparison.OrdinalIgnoreCase) >= 0) 159 | return Singleton.Instance; 160 | if (ProviderName.IndexOf("pgsql", StringComparison.OrdinalIgnoreCase) >= 0) 161 | return Singleton.Instance; 162 | if (ProviderName.IndexOf("Oracle", StringComparison.OrdinalIgnoreCase) >= 0) 163 | return Singleton.Instance; 164 | if (ProviderName.IndexOf("SQLite", StringComparison.OrdinalIgnoreCase) >= 0) 165 | return Singleton.Instance; 166 | } 167 | 168 | // Assume SQL Server 169 | return Singleton.Instance; 170 | } 171 | 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /AsyncPoco/Core/ExpandoColumn.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace AsyncPoco.Internal 9 | { 10 | internal class ExpandoColumn : PocoColumn 11 | { 12 | public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName] = val; } 13 | public override object GetValue(object target) 14 | { 15 | object val = null; 16 | (target as IDictionary).TryGetValue(ColumnName, out val); 17 | return val; 18 | } 19 | public override object ChangeType(object val) { return val; } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /AsyncPoco/Core/IMapper.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Reflection; 7 | 8 | namespace AsyncPoco 9 | { 10 | /// 11 | /// IMapper provides a way to hook into PetaPoco's Database to POCO mapping mechanism to either 12 | /// customize or completely replace it. 13 | /// 14 | /// 15 | /// To use this functionality, instantiate a class that implements IMapper and then pass it to 16 | /// PetaPoco through the static method Mappers.Register() 17 | /// 18 | public interface IMapper 19 | { 20 | /// 21 | /// Get information about the table associated with a POCO class 22 | /// 23 | /// 24 | /// A TableInfo instance 25 | /// 26 | /// This method must return a valid TableInfo. 27 | /// To create a TableInfo from a POCO's attributes, use TableInfo.FromPoco 28 | /// 29 | TableInfo GetTableInfo(Type pocoType); 30 | 31 | /// 32 | /// Get information about the column associated with a property of a POCO 33 | /// 34 | /// The PropertyInfo of the property being queried 35 | /// A reference to a ColumnInfo instance, or null to ignore this property 36 | /// 37 | /// To create a ColumnInfo from a property's attributes, use PropertyInfo.FromProperty 38 | /// 39 | ColumnInfo GetColumnInfo(PropertyInfo pocoProperty); 40 | 41 | /// 42 | /// Supply a function to convert a database value to the correct property value 43 | /// 44 | /// The target property 45 | /// The type of data returned by the DB 46 | /// A Func that can do the conversion, or null for no conversion 47 | Func GetFromDbConverter(PropertyInfo TargetProperty, Type SourceType); 48 | 49 | /// 50 | /// Supply a function to convert a property value into a database value 51 | /// 52 | /// The property to be converted 53 | /// A Func that can do the conversion 54 | /// 55 | /// This conversion is only used for converting values from POCO's that are 56 | /// being Inserted or Updated. 57 | /// Conversion is not available for parameter values passed directly to queries. 58 | /// 59 | Func GetToDbConverter(PropertyInfo SourceProperty); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /AsyncPoco/Core/Mappers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reflection; 6 | using System.Threading; 7 | using AsyncPoco.Internal; 8 | 9 | namespace AsyncPoco 10 | { 11 | /// 12 | /// This static manages registation of IMapper instances with PetaPoco 13 | /// 14 | public static class Mappers 15 | { 16 | /// 17 | /// Registers a mapper for all types in a specific assembly 18 | /// 19 | /// The assembly whose types are to be managed by this mapper 20 | /// The IMapper implementation 21 | public static void Register(Assembly assembly, IMapper mapper) 22 | { 23 | RegisterInternal(assembly, mapper); 24 | } 25 | 26 | /// 27 | /// Registers a mapper for a single POCO type 28 | /// 29 | /// The type to be managed by this mapper 30 | /// The IMapper implementation 31 | public static void Register(Type type, IMapper mapper) 32 | { 33 | RegisterInternal(type, mapper); 34 | } 35 | 36 | /// 37 | /// Remove all mappers for all types in a specific assembly 38 | /// 39 | /// The assembly whose mappers are to be revoked 40 | public static void Revoke(Assembly assembly) 41 | { 42 | RevokeInternal(assembly); 43 | } 44 | 45 | /// 46 | /// Remove the mapper for a specific type 47 | /// 48 | /// The type whose mapper is to be removed 49 | public static void Revoke(Type type) 50 | { 51 | RevokeInternal(type); 52 | } 53 | 54 | /// 55 | /// Revoke an instance of a mapper 56 | /// 57 | /// The IMapper to be revkoed 58 | public static void Revoke(IMapper mapper) 59 | { 60 | _lock.EnterWriteLock(); 61 | try 62 | { 63 | foreach (var i in _mappers.Where(kvp => kvp.Value == mapper).ToList()) 64 | _mappers.Remove(i.Key); 65 | } 66 | finally 67 | { 68 | _lock.ExitWriteLock(); 69 | FlushCaches(); 70 | } 71 | } 72 | 73 | /// 74 | /// Retrieve the IMapper implementation to be used for a specified POCO type 75 | /// 76 | /// 77 | /// 78 | public static IMapper GetMapper(Type t) 79 | { 80 | _lock.EnterReadLock(); 81 | try 82 | { 83 | IMapper val; 84 | if (_mappers.TryGetValue(t, out val)) 85 | return val; 86 | if (_mappers.TryGetValue(t.GetTypeInfo().Assembly, out val)) 87 | return val; 88 | 89 | return Singleton.Instance; 90 | } 91 | finally 92 | { 93 | _lock.ExitReadLock(); 94 | } 95 | } 96 | 97 | 98 | static void RegisterInternal(object typeOrAssembly, IMapper mapper) 99 | { 100 | _lock.EnterWriteLock(); 101 | try 102 | { 103 | _mappers.Add(typeOrAssembly, mapper); 104 | } 105 | finally 106 | { 107 | _lock.ExitWriteLock(); 108 | FlushCaches(); 109 | } 110 | } 111 | 112 | static void RevokeInternal(object typeOrAssembly) 113 | { 114 | _lock.EnterWriteLock(); 115 | try 116 | { 117 | _mappers.Remove(typeOrAssembly); 118 | } 119 | finally 120 | { 121 | _lock.ExitWriteLock(); 122 | FlushCaches(); 123 | } 124 | } 125 | 126 | static void FlushCaches() 127 | { 128 | // Whenever a mapper is registered or revoked, we have to assume any generated code is no longer valid. 129 | // Since this should be a rare occurance, the simplest approach is to simply dump everything and start over. 130 | MultiPocoFactory.FlushCaches(); 131 | PocoData.FlushCaches(); 132 | } 133 | 134 | static Dictionary _mappers = new Dictionary(); 135 | static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /AsyncPoco/Core/MultiPocoFactory.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Text; 7 | using System.Linq; 8 | using System.Collections.Generic; 9 | using System.Reflection.Emit; 10 | using System.Linq.Expressions; 11 | using System.Data; 12 | using System.Reflection; 13 | 14 | namespace AsyncPoco.Internal 15 | { 16 | class MultiPocoFactory 17 | { 18 | public static IEqualityComparer FieldNameComparer { get; set; } = StringComparer.OrdinalIgnoreCase; 19 | 20 | // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call 21 | List _delegates; 22 | public Delegate GetItem(int index) { return _delegates[index]; } 23 | 24 | // Automagically guess the property relationships between various POCOs and create a delegate that will set them up 25 | public static object GetAutoMapper(Type[] types) 26 | { 27 | // Build a key 28 | var key = new ArrayKey(types); 29 | 30 | return AutoMappers.Get(key, () => 31 | { 32 | // Create a method 33 | var m = new DynamicMethod("petapoco_automapper", types[0], types, true); 34 | var il = m.GetILGenerator(); 35 | 36 | for (int i = 1; i < types.Length; i++) 37 | { 38 | bool handled = false; 39 | for (int j = i - 1; j >= 0; j--) 40 | { 41 | // Find the property 42 | var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p; 43 | if (candidates.Count() == 0) 44 | continue; 45 | if (candidates.Count() > 1) 46 | throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j])); 47 | 48 | // Generate code 49 | il.Emit(OpCodes.Ldarg_S, j); 50 | il.Emit(OpCodes.Ldarg_S, i); 51 | il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true)); 52 | handled = true; 53 | } 54 | 55 | if (!handled) 56 | throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i])); 57 | } 58 | 59 | il.Emit(OpCodes.Ldarg_0); 60 | il.Emit(OpCodes.Ret); 61 | 62 | // Cache it 63 | return m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray())); 64 | } 65 | ); 66 | } 67 | 68 | // Find the split point in a result set for two different pocos and return the poco factory for the first 69 | static Delegate FindSplitPoint(Type typeThis, Type typeNext, string ConnectionString, string sql, IDataReader r, ref int pos) 70 | { 71 | // Last? 72 | if (typeNext == null) 73 | return PocoData.ForType(typeThis).GetFactory(sql, ConnectionString, pos, r.FieldCount - pos, r); 74 | 75 | // Get PocoData for the two types 76 | PocoData pdThis = PocoData.ForType(typeThis); 77 | PocoData pdNext = PocoData.ForType(typeNext); 78 | 79 | // Find split point 80 | int firstColumn = pos; 81 | var usedColumns = new Dictionary(FieldNameComparer); 82 | for (; pos < r.FieldCount; pos++) 83 | { 84 | // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next 85 | string fieldName = r.GetName(pos); 86 | if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName))) 87 | { 88 | return pdThis.GetFactory(sql, ConnectionString, firstColumn, pos - firstColumn, r); 89 | } 90 | usedColumns.Add(fieldName, true); 91 | } 92 | 93 | throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); 94 | } 95 | 96 | // Create a multi-poco factory 97 | static Func CreateMultiPocoFactory(Type[] types, string ConnectionString, string sql, IDataReader r) 98 | { 99 | var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new Type[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory)); 100 | var il = m.GetILGenerator(); 101 | 102 | // Load the callback 103 | il.Emit(OpCodes.Ldarg_2); 104 | 105 | // Call each delegate 106 | var dels = new List(); 107 | int pos = 0; 108 | for (int i = 0; i < types.Length; i++) 109 | { 110 | // Add to list of delegates to call 111 | var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, ConnectionString, sql, r, ref pos); 112 | dels.Add(del); 113 | 114 | // Get the delegate 115 | il.Emit(OpCodes.Ldarg_0); // callback,this 116 | il.Emit(OpCodes.Ldc_I4, i); // callback,this,Index 117 | il.Emit(OpCodes.Callvirt, typeof(MultiPocoFactory).GetMethod("GetItem")); // callback,Delegate 118 | il.Emit(OpCodes.Ldarg_1); // callback,delegate, datareader 119 | 120 | // Call Invoke 121 | var tDelInvoke = del.GetType().GetMethod("Invoke"); 122 | il.Emit(OpCodes.Callvirt, tDelInvoke); // Poco left on stack 123 | } 124 | 125 | // By now we should have the callback and the N pocos all on the stack. Call the callback and we're done 126 | il.Emit(OpCodes.Callvirt, Expression.GetFuncType(types.Concat(new Type[] { typeof(TRet) }).ToArray()).GetMethod("Invoke")); 127 | il.Emit(OpCodes.Ret); 128 | 129 | // Finish up 130 | return (Func)m.CreateDelegate(typeof(Func), new MultiPocoFactory() { _delegates = dels }); 131 | } 132 | 133 | // Various cached stuff 134 | static Cache, string, string>, object> MultiPocoFactories = new Cache, string, string>, object>(); 135 | static Cache, object> AutoMappers = new Cache, object>(); 136 | 137 | internal static void FlushCaches() 138 | { 139 | MultiPocoFactories.Flush(); 140 | AutoMappers.Flush(); 141 | } 142 | 143 | // Get (or create) the multi-poco factory for a query 144 | public static Func GetFactory(Type[] types, string ConnectionString, string sql, IDataReader r) 145 | { 146 | var key = Tuple.Create, string, string>(typeof(TRet), new ArrayKey(types), ConnectionString, sql); 147 | 148 | return (Func)MultiPocoFactories.Get(key, () => 149 | { 150 | return CreateMultiPocoFactory(types, ConnectionString, sql, r); 151 | } 152 | ); 153 | } 154 | 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /AsyncPoco/Core/Page.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace AsyncPoco 9 | { 10 | /// 11 | /// Holds the results of a paged request. 12 | /// 13 | /// The type of Poco in the returned result set 14 | public class Page 15 | { 16 | /// 17 | /// The current page number contained in this page of result set 18 | /// 19 | public long CurrentPage 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | /// 26 | /// The total number of pages in the full result set 27 | /// 28 | public long TotalPages 29 | { 30 | get; 31 | set; 32 | } 33 | 34 | /// 35 | /// The total number of records in the full result set 36 | /// 37 | public long TotalItems 38 | { 39 | get; 40 | set; 41 | } 42 | 43 | /// 44 | /// The number of items per page 45 | /// 46 | public long ItemsPerPage 47 | { 48 | get; 49 | set; 50 | } 51 | 52 | /// 53 | /// The actual records on this page 54 | /// 55 | public List Items 56 | { 57 | get; 58 | set; 59 | } 60 | 61 | /// 62 | /// User property to hold anything. 63 | /// 64 | public object Context 65 | { 66 | get; 67 | set; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AsyncPoco/Core/PocoColumn.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Reflection; 7 | 8 | namespace AsyncPoco.Internal 9 | { 10 | internal class PocoColumn 11 | { 12 | public string ColumnName; 13 | public PropertyInfo PropertyInfo; 14 | public bool ResultColumn; 15 | public bool ComputedColumn; 16 | public bool ForceToUtc; 17 | public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); } 18 | public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); } 19 | public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AsyncPoco/Core/PocoData.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using System.Data; 9 | using System.Reflection.Emit; 10 | using System.Linq; 11 | using System.Linq.Expressions; 12 | 13 | namespace AsyncPoco.Internal 14 | { 15 | class PocoData 16 | { 17 | public static IEqualityComparer ColumnComparer { get;set;} = StringComparer.OrdinalIgnoreCase; 18 | 19 | public static PocoData ForObject(object o, string primaryKeyName) 20 | { 21 | var t = o.GetType(); 22 | if (t == typeof(System.Dynamic.ExpandoObject)) 23 | { 24 | var pd = new PocoData(); 25 | pd.TableInfo = new TableInfo(); 26 | pd.Columns = new Dictionary(ColumnComparer); 27 | pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); 28 | pd.TableInfo.PrimaryKey = primaryKeyName; 29 | pd.TableInfo.AutoIncrement = true; 30 | foreach (var col in (o as IDictionary).Keys) 31 | { 32 | if (col != primaryKeyName) 33 | pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); 34 | } 35 | return pd; 36 | } 37 | else 38 | return ForType(t); 39 | } 40 | 41 | public static PocoData ForType(Type t) 42 | { 43 | if (t == typeof(System.Dynamic.ExpandoObject)) 44 | throw new InvalidOperationException("Can't use dynamic types with this method"); 45 | 46 | return _pocoDatas.Get(t, () => new PocoData(t)); 47 | } 48 | 49 | public PocoData() 50 | { 51 | } 52 | 53 | public PocoData(Type t) 54 | { 55 | type = t; 56 | 57 | // Get the mapper for this type 58 | var mapper = Mappers.GetMapper(t); 59 | 60 | // Get the table info 61 | TableInfo = mapper.GetTableInfo(t); 62 | 63 | // Work out bound properties 64 | Columns = new Dictionary(ColumnComparer); 65 | foreach (var pi in t.GetProperties()) 66 | { 67 | ColumnInfo ci = mapper.GetColumnInfo(pi); 68 | if (ci == null) 69 | continue; 70 | 71 | var pc = new PocoColumn(); 72 | pc.PropertyInfo = pi; 73 | pc.ColumnName = ci.ColumnName; 74 | pc.ResultColumn = ci.ResultColumn; 75 | pc.ComputedColumn = ci.ComputedColumn; 76 | pc.ForceToUtc = ci.ForceToUtc; 77 | 78 | // Store it 79 | Columns.Add(pc.ColumnName, pc); 80 | } 81 | 82 | // Build column list for automatic select 83 | QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray(); 84 | 85 | } 86 | 87 | static bool IsIntegralType(Type t) { 88 | if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) 89 | t = Nullable.GetUnderlyingType(t); 90 | 91 | return 92 | t == typeof(byte) || 93 | t == typeof(sbyte) || 94 | t == typeof(short) || 95 | t == typeof(ushort) || 96 | t == typeof(int) || 97 | t == typeof(uint) || 98 | t == typeof(long) || 99 | t == typeof(ulong); 100 | } 101 | 102 | // true for enumns and nullable enums 103 | static bool IsEnumType(Type t) 104 | { 105 | if (t.GetTypeInfo().IsEnum) return true; 106 | var underlying = Nullable.GetUnderlyingType(t); 107 | return underlying != null && underlying.GetTypeInfo().IsEnum; 108 | } 109 | 110 | // Create factory function that can convert a IDataReader record into a POCO 111 | public Delegate GetFactory(string sql, string connString, int firstColumn, int countColumns, IDataReader r) 112 | { 113 | // Check cache 114 | var key = Tuple.Create(sql, connString, firstColumn, countColumns); 115 | 116 | return PocoFactories.Get(key, () => 117 | { 118 | // Create the method 119 | var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true); 120 | var il = m.GetILGenerator(); 121 | var mapper = Mappers.GetMapper(type); 122 | 123 | if (type == typeof(object)) 124 | { 125 | il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj 126 | 127 | MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); 128 | 129 | // Enumerate all fields generating a set assignment for the column 130 | for (int i = firstColumn; i < firstColumn + countColumns; i++) 131 | { 132 | var srcType = r.GetFieldType(i); 133 | 134 | il.Emit(OpCodes.Dup); // obj, obj 135 | il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname 136 | 137 | // Get the converter 138 | Func converter = mapper.GetFromDbConverter((PropertyInfo)null, srcType); 139 | 140 | /* 141 | if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) 142 | converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; 143 | */ 144 | 145 | // Setup stack for call to converter 146 | AddConverterToStack(il, converter); 147 | 148 | // r[i] 149 | il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr 150 | il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i 151 | il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value 152 | 153 | // Convert DBNull to null 154 | il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value 155 | il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) 156 | var lblNotNull = il.DefineLabel(); 157 | il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value 158 | il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? 159 | if (converter != null) 160 | il.Emit(OpCodes.Pop); // obj, obj, fieldname, 161 | il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null 162 | if (converter != null) 163 | { 164 | var lblReady = il.DefineLabel(); 165 | il.Emit(OpCodes.Br_S, lblReady); 166 | il.MarkLabel(lblNotNull); 167 | il.Emit(OpCodes.Callvirt, fnInvoke); 168 | il.MarkLabel(lblReady); 169 | } 170 | else 171 | { 172 | il.MarkLabel(lblNotNull); 173 | } 174 | 175 | il.Emit(OpCodes.Callvirt, fnAdd); 176 | } 177 | } 178 | else 179 | if (type.GetTypeInfo().IsValueType || type == typeof(string) || type == typeof(byte[])) 180 | { 181 | // Do we need to install a converter? 182 | var srcType = r.GetFieldType(0); 183 | var converter = GetConverter(mapper, null, srcType, type); 184 | 185 | // "if (!rdr.IsDBNull(i))" 186 | il.Emit(OpCodes.Ldarg_0); // rdr 187 | il.Emit(OpCodes.Ldc_I4_0); // rdr,0 188 | il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool 189 | var lblCont = il.DefineLabel(); 190 | il.Emit(OpCodes.Brfalse_S, lblCont); 191 | il.Emit(OpCodes.Ldnull); // null 192 | var lblFin = il.DefineLabel(); 193 | il.Emit(OpCodes.Br_S, lblFin); 194 | 195 | il.MarkLabel(lblCont); 196 | 197 | // Setup stack for call to converter 198 | AddConverterToStack(il, converter); 199 | 200 | il.Emit(OpCodes.Ldarg_0); // rdr 201 | il.Emit(OpCodes.Ldc_I4_0); // rdr,0 202 | il.Emit(OpCodes.Callvirt, fnGetValue); // value 203 | 204 | // Call the converter 205 | if (converter != null) 206 | il.Emit(OpCodes.Callvirt, fnInvoke); 207 | 208 | il.MarkLabel(lblFin); 209 | il.Emit(OpCodes.Unbox_Any, type); // value converted 210 | } 211 | else 212 | { 213 | // var poco=new T() 214 | il.Emit(OpCodes.Newobj, type.GetConstructor(new Type[0])); 215 | 216 | // Enumerate all fields generating a set assignment for the column 217 | for (int i = firstColumn; i < firstColumn + countColumns; i++) 218 | { 219 | // Get the PocoColumn for this db column, ignore if not known 220 | PocoColumn pc; 221 | if (!Columns.TryGetValue(r.GetName(i), out pc)) 222 | continue; 223 | 224 | // Get the source type for this column 225 | var srcType = r.GetFieldType(i); 226 | var dstType = pc.PropertyInfo.PropertyType; 227 | 228 | // "if (!rdr.IsDBNull(i))" 229 | il.Emit(OpCodes.Ldarg_0); // poco,rdr 230 | il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i 231 | il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool 232 | var lblNext = il.DefineLabel(); 233 | il.Emit(OpCodes.Brtrue_S, lblNext); // poco 234 | 235 | il.Emit(OpCodes.Dup); // poco,poco 236 | 237 | // Do we need to install a converter? 238 | var converter = GetConverter(mapper, pc, srcType, dstType); 239 | 240 | // Fast 241 | bool Handled = false; 242 | if (converter == null) 243 | { 244 | var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); 245 | if (valuegetter != null 246 | && valuegetter.ReturnType == srcType 247 | && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) 248 | { 249 | il.Emit(OpCodes.Ldarg_0); // *,rdr 250 | il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i 251 | il.Emit(OpCodes.Callvirt, valuegetter); // *,value 252 | 253 | // Convert to Nullable 254 | if (Nullable.GetUnderlyingType(dstType) != null) 255 | { 256 | il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); 257 | } 258 | 259 | il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco 260 | Handled = true; 261 | } 262 | } 263 | 264 | // Not so fast 265 | if (!Handled) 266 | { 267 | // Setup stack for call to converter 268 | AddConverterToStack(il, converter); 269 | 270 | // "value = rdr.GetValue(i)" 271 | il.Emit(OpCodes.Ldarg_0); // *,rdr 272 | il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i 273 | il.Emit(OpCodes.Callvirt, fnGetValue); // *,value 274 | 275 | // Call the converter 276 | if (converter != null) 277 | il.Emit(OpCodes.Callvirt, fnInvoke); 278 | 279 | // Assign it 280 | il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value 281 | il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco 282 | } 283 | 284 | il.MarkLabel(lblNext); 285 | } 286 | 287 | var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", new Type[0])); 288 | if (fnOnLoaded != null) 289 | { 290 | il.Emit(OpCodes.Dup); 291 | il.Emit(OpCodes.Callvirt, fnOnLoaded); 292 | } 293 | } 294 | 295 | il.Emit(OpCodes.Ret); 296 | 297 | // Cache it, return it 298 | return m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); 299 | } 300 | ); 301 | } 302 | 303 | private static void AddConverterToStack(ILGenerator il, Func converter) 304 | { 305 | if (converter != null) 306 | { 307 | // Add the converter 308 | int converterIndex = _converters.Count; 309 | _converters.Add(converter); 310 | 311 | // Generate IL to push the converter onto the stack 312 | il.Emit(OpCodes.Ldsfld, fldConverters); 313 | il.Emit(OpCodes.Ldc_I4, converterIndex); 314 | il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter 315 | } 316 | } 317 | 318 | private static Func GetConverter(IMapper mapper, PocoColumn pc, Type srcType, Type dstType) 319 | { 320 | Func converter = null; 321 | 322 | // Get converter from the mapper 323 | if (pc != null) 324 | { 325 | converter = mapper.GetFromDbConverter(pc.PropertyInfo, srcType); 326 | if (converter != null) 327 | return converter; 328 | } 329 | 330 | // Standard DateTime->Utc mapper 331 | if (pc!=null && pc.ForceToUtc && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) 332 | { 333 | return src => new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); 334 | } 335 | 336 | // Forced type conversion including integral types -> enum 337 | if (IsEnumType(dstType) && IsIntegralType(srcType)) 338 | { 339 | var fromInt = (srcType == typeof(int)); 340 | var toNullable = !dstType.GetTypeInfo().IsEnum; 341 | 342 | if (fromInt && toNullable) 343 | { 344 | return src => EnumMapper.EnumFromNullableInt(dstType, (int?)src); 345 | } 346 | else if (toNullable) 347 | { 348 | return src => { 349 | var i = Convert.ChangeType(src, typeof(int), null); 350 | return EnumMapper.EnumFromNullableInt(dstType, i as int?); 351 | }; 352 | } 353 | else if (!fromInt) 354 | { 355 | return src => Convert.ChangeType(src, typeof(int), null); 356 | } 357 | } 358 | else if (!dstType.IsAssignableFrom(srcType)) 359 | { 360 | if (IsEnumType(dstType) && srcType == typeof(string)) 361 | { 362 | return src => EnumMapper.EnumFromString(dstType, (string)src); 363 | } 364 | return src => { 365 | var actualDstType = Nullable.GetUnderlyingType(dstType) ?? dstType; 366 | return Convert.ChangeType(src, actualDstType, null); 367 | }; 368 | } 369 | 370 | return null; 371 | } 372 | 373 | 374 | static T RecurseInheritedTypes(Type t, Func cb) 375 | { 376 | while (t != null) 377 | { 378 | T info = cb(t); 379 | if (info != null) 380 | return info; 381 | t = t.GetTypeInfo().BaseType; 382 | } 383 | return default(T); 384 | } 385 | 386 | 387 | internal static void FlushCaches() 388 | { 389 | _pocoDatas.Flush(); 390 | } 391 | 392 | static Cache _pocoDatas = new Cache(); 393 | static List> _converters = new List>(); 394 | static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); 395 | static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); 396 | static FieldInfo fldConverters = typeof(PocoData).GetField("_converters", BindingFlags.Static | BindingFlags.NonPublic); 397 | static MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); 398 | static MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); 399 | public Type type; 400 | public string[] QueryColumns { get; private set; } 401 | public TableInfo TableInfo { get; private set; } 402 | public Dictionary Columns { get; private set; } 403 | Cache, Delegate> PocoFactories = new Cache, Delegate>(); 404 | } 405 | 406 | } 407 | -------------------------------------------------------------------------------- /AsyncPoco/Core/Sql.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Text; 7 | using System.Linq; 8 | using System.Collections.Generic; 9 | using AsyncPoco.Internal; 10 | 11 | namespace AsyncPoco 12 | { 13 | /// 14 | /// A simple helper class for build SQL statements 15 | /// 16 | public class Sql 17 | { 18 | /// 19 | /// Default, empty constructor 20 | /// 21 | public Sql() 22 | { 23 | } 24 | 25 | /// 26 | /// Construct an SQL statement with the supplied SQL and arguments 27 | /// 28 | /// The SQL statement or fragment 29 | /// Arguments to any parameters embedded in the SQL 30 | public Sql(string sql, params object[] args) 31 | { 32 | _sql = sql; 33 | _args = args; 34 | } 35 | 36 | /// 37 | /// Instantiate a new SQL Builder object. Weirdly implemented as a property but makes 38 | /// for more elegantly readble fluent style construction of SQL Statements 39 | /// eg: db.Query(Sql.Builder.Append(....)) 40 | /// 41 | public static Sql Builder 42 | { 43 | get { return new Sql(); } 44 | } 45 | 46 | string _sql; 47 | object[] _args; 48 | Sql _rhs; 49 | string _sqlFinal; 50 | object[] _argsFinal; 51 | 52 | private void Build() 53 | { 54 | // already built? 55 | if (_sqlFinal != null) 56 | return; 57 | 58 | // Build it 59 | var sb = new StringBuilder(); 60 | var args = new List(); 61 | Build(sb, args, null); 62 | _sqlFinal = sb.ToString(); 63 | _argsFinal = args.ToArray(); 64 | } 65 | 66 | /// 67 | /// Returns the final SQL statement represented by this builder 68 | /// 69 | public string SQL 70 | { 71 | get 72 | { 73 | Build(); 74 | return _sqlFinal; 75 | } 76 | } 77 | 78 | /// 79 | /// Gets the complete, final set of arguments collected by this builder. 80 | /// 81 | public object[] Arguments 82 | { 83 | get 84 | { 85 | Build(); 86 | return _argsFinal; 87 | } 88 | } 89 | 90 | /// 91 | /// Append another SQL builder instance to the right-hand-side of this SQL builder 92 | /// 93 | /// A reference to another SQL builder instance 94 | /// A reference to this builder, allowing for fluent style concatenation 95 | public Sql Append(Sql sql) 96 | { 97 | // Ensure the sql is rebuilt if changed 98 | _sqlFinal = null; 99 | 100 | if (_rhs != null) 101 | _rhs.Append(sql); 102 | else 103 | _rhs = sql; 104 | 105 | return this; 106 | } 107 | 108 | /// 109 | /// Append an SQL fragement to the right-hand-side of this SQL builder 110 | /// 111 | /// The SQL statement or fragment 112 | /// Arguments to any parameters embedded in the SQL 113 | /// A reference to this builder, allowing for fluent style concatenation 114 | public Sql Append(string sql, params object[] args) 115 | { 116 | return Append(new Sql(sql, args)); 117 | } 118 | 119 | static bool Is(Sql sql, string sqltype) 120 | { 121 | return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.OrdinalIgnoreCase); 122 | } 123 | 124 | private void Build(StringBuilder sb, List args, Sql lhs) 125 | { 126 | if (!String.IsNullOrEmpty(_sql)) 127 | { 128 | // Add SQL to the string 129 | if (sb.Length > 0) 130 | { 131 | sb.Append("\n"); 132 | } 133 | 134 | var sql = ParametersHelper.ProcessParams(_sql, _args, args); 135 | 136 | if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) 137 | sql = "AND " + sql.Substring(6); 138 | if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) 139 | sql = ", " + sql.Substring(9); 140 | 141 | sb.Append(sql); 142 | } 143 | 144 | // Now do rhs 145 | if (_rhs != null) 146 | _rhs.Build(sb, args, this); 147 | } 148 | 149 | /// 150 | /// Appends an SQL WHERE clause to this SQL builder 151 | /// 152 | /// The condition of the WHERE clause 153 | /// Arguments to any parameters embedded in the supplied SQL 154 | /// A reference to this builder, allowing for fluent style concatenation 155 | public Sql Where(string sql, params object[] args) 156 | { 157 | return Append(new Sql("WHERE (" + sql + ")", args)); 158 | } 159 | 160 | /// 161 | /// Appends an SQL ORDER BY clause to this SQL builder 162 | /// 163 | /// A collection of SQL column names to order by 164 | /// A reference to this builder, allowing for fluent style concatenation 165 | public Sql OrderBy(params object[] columns) 166 | { 167 | return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); 168 | } 169 | 170 | /// 171 | /// Appends an SQL SELECT clause to this SQL builder 172 | /// 173 | /// A collection of SQL column names to select 174 | /// A reference to this builder, allowing for fluent style concatenation 175 | public Sql Select(params object[] columns) 176 | { 177 | return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); 178 | } 179 | 180 | /// 181 | /// Appends an SQL FROM clause to this SQL builder 182 | /// 183 | /// A collection of table names to be used in the FROM clause 184 | /// A reference to this builder, allowing for fluent style concatenation 185 | public Sql From(params object[] tables) 186 | { 187 | return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray()))); 188 | } 189 | 190 | /// 191 | /// Appends an SQL GROUP BY clause to this SQL builder 192 | /// 193 | /// A collection of column names to be grouped by 194 | /// A reference to this builder, allowing for fluent style concatenation 195 | public Sql GroupBy(params object[] columns) 196 | { 197 | return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); 198 | } 199 | 200 | private SqlJoinClause Join(string JoinType, string table) 201 | { 202 | return new SqlJoinClause(Append(new Sql(JoinType + table))); 203 | } 204 | 205 | /// 206 | /// Appends an SQL INNER JOIN clause to this SQL builder 207 | /// 208 | /// The name of the table to join 209 | /// A reference an SqlJoinClause through which the join condition can be specified 210 | public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); } 211 | 212 | /// 213 | /// Appends an SQL LEFT JOIN clause to this SQL builder 214 | /// 215 | /// The name of the table to join 216 | /// A reference an SqlJoinClause through which the join condition can be specified 217 | public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); } 218 | 219 | /// 220 | /// The SqlJoinClause is a simple helper class used in the construction of SQL JOIN statements with the SQL builder 221 | /// 222 | public class SqlJoinClause 223 | { 224 | private readonly Sql _sql; 225 | 226 | public SqlJoinClause(Sql sql) 227 | { 228 | _sql = sql; 229 | } 230 | 231 | /// 232 | /// Appends a SQL ON clause after a JOIN statement 233 | /// 234 | /// The ON clause to be appended 235 | /// Arguments to any parameters embedded in the supplied SQL 236 | /// A reference to the parent SQL builder, allowing for fluent style concatenation 237 | public Sql On(string onClause, params object[] args) 238 | { 239 | return _sql.Append("ON " + onClause, args); 240 | } 241 | } 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /AsyncPoco/Core/StandardMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reflection; 6 | 7 | namespace AsyncPoco 8 | { 9 | /// 10 | /// StandardMapper is the default implementation of IMapper used by PetaPoco 11 | /// 12 | public class StandardMapper : IMapper 13 | { 14 | /// 15 | /// Constructs a TableInfo for a POCO by reading its attribute data 16 | /// 17 | /// The POCO Type 18 | /// 19 | public TableInfo GetTableInfo(Type pocoType) 20 | { 21 | return TableInfo.FromPoco(pocoType); 22 | } 23 | 24 | /// 25 | /// Constructs a ColumnInfo for a POCO property by reading its attribute data 26 | /// 27 | /// 28 | /// 29 | public ColumnInfo GetColumnInfo(PropertyInfo pocoProperty) 30 | { 31 | return ColumnInfo.FromProperty(pocoProperty); 32 | } 33 | 34 | public Func GetFromDbConverter(PropertyInfo TargetProperty, Type SourceType) 35 | { 36 | return null; 37 | } 38 | 39 | public Func GetToDbConverter(PropertyInfo SourceProperty) 40 | { 41 | return null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AsyncPoco/Core/TableInfo.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace AsyncPoco 10 | { 11 | /// 12 | /// Use by IMapper to override table bindings for an object 13 | /// 14 | public class TableInfo 15 | { 16 | /// 17 | /// The database table name 18 | /// 19 | public string TableName 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | /// 26 | /// The name of the primary key column of the table 27 | /// 28 | public string PrimaryKey 29 | { 30 | get; 31 | set; 32 | } 33 | 34 | /// 35 | /// True if the primary key column is an auto-incrementing 36 | /// 37 | public bool AutoIncrement 38 | { 39 | get; 40 | set; 41 | } 42 | 43 | /// 44 | /// The name of the sequence used for auto-incrementing Oracle primary key fields 45 | /// 46 | public string SequenceName 47 | { 48 | get; 49 | set; 50 | } 51 | 52 | 53 | /// 54 | /// Creates and populates a TableInfo from the attributes of a POCO 55 | /// 56 | /// The POCO type 57 | /// A TableInfo instance 58 | public static TableInfo FromPoco(Type t) 59 | { 60 | TableInfo ti = new TableInfo(); 61 | 62 | // Get the table name 63 | var a = t.GetTypeInfo().GetCustomAttributes(typeof(TableNameAttribute), true).ToArray(); 64 | ti.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value; 65 | 66 | // Get the primary key 67 | a = t.GetTypeInfo().GetCustomAttributes(typeof(PrimaryKeyAttribute), true).ToArray(); 68 | ti.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value; 69 | ti.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName; 70 | ti.AutoIncrement = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).autoIncrement; 71 | 72 | return ti; 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /AsyncPoco/Core/Transaction.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace AsyncPoco 9 | { 10 | public interface ITransaction : IDisposable 11 | { 12 | void Complete(); 13 | } 14 | 15 | /// 16 | /// Transaction object helps maintain transaction depth counts 17 | /// 18 | public class Transaction : ITransaction 19 | { 20 | public static async Task BeginAsync(Database db) 21 | { 22 | var trans = new Transaction(db); 23 | await db.BeginTransactionAsync().ConfigureAwait(false); 24 | return trans; 25 | } 26 | 27 | private Transaction(Database db) 28 | { 29 | _db = db; 30 | } 31 | 32 | public void Complete() 33 | { 34 | _db.CompleteTransaction(); 35 | _db = null; 36 | } 37 | 38 | public void Dispose() 39 | { 40 | if (_db != null) 41 | _db.AbortTransaction(); 42 | } 43 | 44 | Database _db; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /AsyncPoco/DatabaseTypes/MySqlDatabaseType.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using AsyncPoco.Internal; 7 | 8 | 9 | namespace AsyncPoco.DatabaseTypes 10 | { 11 | class MySqlDatabaseType : DatabaseType 12 | { 13 | public override string EscapeSqlIdentifier(string str) 14 | { 15 | return string.Format("`{0}`", str); 16 | } 17 | 18 | public override string GetExistsSql() 19 | { 20 | return "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AsyncPoco/DatabaseTypes/OracleDatabaseType.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Data; 7 | using System.Data.Common; 8 | using System.Reflection; 9 | using System.Threading.Tasks; 10 | using AsyncPoco.Internal; 11 | 12 | 13 | namespace AsyncPoco.DatabaseTypes 14 | { 15 | class OracleDatabaseType : DatabaseType 16 | { 17 | public override string ParameterPrefix { get; } = ":"; 18 | 19 | 20 | public override void PreExecute(IDbCommand cmd) 21 | { 22 | cmd.GetType().GetTypeInfo().GetDeclaredProperty("BindByName")?.SetValue(cmd, true, null); 23 | cmd.GetType().GetTypeInfo().GetDeclaredProperty("InitialLONGFetchSize")?.SetValue(cmd, -1); //see http://docs.oracle.com/html/A96160_01/features.htm#1048395 24 | } 25 | 26 | public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) 27 | { 28 | if (parts.sqlSelectRemoved.StartsWith("*")) 29 | throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); 30 | 31 | // Same deal as SQL Server 32 | return Singleton.Instance.BuildPageQuery(skip, take, parts, ref args); 33 | } 34 | 35 | public override string EscapeSqlIdentifier(string str) 36 | { 37 | return string.Format("\"{0}\"", str.ToUpperInvariant()); 38 | } 39 | 40 | public override string GetAutoIncrementExpression(TableInfo ti) 41 | { 42 | if (!string.IsNullOrEmpty(ti.SequenceName)) 43 | return string.Format("{0}.nextval", ti.SequenceName); 44 | 45 | return null; 46 | } 47 | 48 | public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string PrimaryKeyName) 49 | { 50 | if (PrimaryKeyName != null) 51 | { 52 | cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(PrimaryKeyName)); 53 | var param = cmd.CreateParameter(); 54 | param.ParameterName = ":newid"; 55 | param.Value = DBNull.Value; 56 | param.Direction = ParameterDirection.ReturnValue; 57 | param.DbType = DbType.Int64; 58 | cmd.Parameters.Add(param); 59 | await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); 60 | return param.Value; 61 | } 62 | else 63 | { 64 | await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); 65 | return -1; 66 | } 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AsyncPoco/DatabaseTypes/PostgreSQLDatabaseType.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Data.Common; 7 | using System.Threading.Tasks; 8 | using AsyncPoco.Internal; 9 | 10 | namespace AsyncPoco.DatabaseTypes 11 | { 12 | class PostgreSQLDatabaseType : DatabaseType 13 | { 14 | public override object MapParameterValue(object value) 15 | { 16 | // Don't map bools to ints in PostgreSQL 17 | if (value.GetType() == typeof(bool)) 18 | return value; 19 | 20 | return base.MapParameterValue(value); 21 | } 22 | 23 | public override string EscapeSqlIdentifier(string str) 24 | { 25 | return string.Format("\"{0}\"", str); 26 | } 27 | 28 | public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string PrimaryKeyName) 29 | { 30 | if (PrimaryKeyName != null && !PrimaryKeyName.Contains(",")) 31 | { 32 | cmd.CommandText += string.Format("returning {0} as NewID", EscapeSqlIdentifier(PrimaryKeyName)); 33 | return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); 34 | } 35 | else 36 | { 37 | await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); 38 | return -1; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AsyncPoco/DatabaseTypes/SQLiteDatabaseType.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Data.Common; 7 | using System.Threading.Tasks; 8 | using AsyncPoco.Internal; 9 | 10 | 11 | namespace AsyncPoco.DatabaseTypes 12 | { 13 | class SQLiteDatabaseType : DatabaseType 14 | { 15 | public override object MapParameterValue(object value) 16 | { 17 | if (value.GetType() == typeof(uint)) 18 | return (long)((uint)value); 19 | 20 | return base.MapParameterValue(value); 21 | } 22 | 23 | public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string PrimaryKeyName) 24 | { 25 | if (PrimaryKeyName != null) 26 | { 27 | cmd.CommandText += ";\nSELECT last_insert_rowid();"; 28 | return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); 29 | } 30 | else 31 | { 32 | await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); 33 | return -1; 34 | } 35 | } 36 | 37 | public override string GetExistsSql() 38 | { 39 | return "SELECT EXISTS (SELECT 1 FROM {0} WHERE {1})"; 40 | } 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AsyncPoco/DatabaseTypes/SqlServerCEDatabaseType.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Data.Common; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using AsyncPoco.Internal; 10 | 11 | namespace AsyncPoco.DatabaseTypes 12 | { 13 | class SqlServerCEDatabaseType : DatabaseType 14 | { 15 | public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) 16 | { 17 | var sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", parts.sql, args.Length, args.Length + 1); 18 | args = args.Concat(new object[] { skip, take }).ToArray(); 19 | return sqlPage; 20 | } 21 | 22 | public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string PrimaryKeyName) 23 | { 24 | await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); 25 | return await db.ExecuteScalarAsync("SELECT @@@IDENTITY AS NewID;").ConfigureAwait(false); 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AsyncPoco/DatabaseTypes/SqlServerDatabaseType.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Data.Common; 7 | using System.Linq; 8 | using System.Text.RegularExpressions; 9 | using System.Threading.Tasks; 10 | using AsyncPoco.Internal; 11 | 12 | namespace AsyncPoco.DatabaseTypes 13 | { 14 | class SqlServerDatabaseType : DatabaseType 15 | { 16 | public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) 17 | { 18 | parts.sqlSelectRemoved = PagingHelper.rxOrderBy.Replace(parts.sqlSelectRemoved, "", 1); 19 | if (PagingHelper.rxDistinct.IsMatch(parts.sqlSelectRemoved)) 20 | { 21 | parts.sqlSelectRemoved = "peta_inner.* FROM (SELECT " + parts.sqlSelectRemoved + ") peta_inner"; 22 | } 23 | var sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", 24 | parts.sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : parts.sqlOrderBy, parts.sqlSelectRemoved, args.Length, args.Length + 1); 25 | args = args.Concat(new object[] { skip, skip + take }).ToArray(); 26 | 27 | return sqlPage; 28 | } 29 | 30 | public override Task ExecuteInsertAsync(Database db, DbCommand cmd, string PrimaryKeyName) 31 | { 32 | return db.ExecuteScalarHelperAsync(cmd); 33 | } 34 | 35 | public override string GetExistsSql() 36 | { 37 | return "IF EXISTS (SELECT 1 FROM [{0}] WHERE {1}) SELECT 1 ELSE SELECT 0"; 38 | } 39 | 40 | public override string GetInsertOutputClause(string primaryKeyName) 41 | { 42 | return String.Format(" OUTPUT INSERTED.[{0}]", primaryKeyName); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /AsyncPoco/IDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Runtime.CompilerServices; 6 | using System.Threading.Tasks; 7 | 8 | [assembly: InternalsVisibleTo("AsyncPoco.Tests")] 9 | 10 | namespace AsyncPoco 11 | { 12 | public interface IDatabase : IDisposable 13 | { 14 | /// 15 | /// When set to true the first opened connection is kept alive until this object is disposed 16 | /// 17 | bool KeepConnectionAlive { get; set; } 18 | 19 | /// 20 | /// Provides access to the currently open shared connection (or null if none) 21 | /// 22 | DbConnection Connection { get; } 23 | 24 | /// 25 | /// Retrieves the SQL of the last executed statement 26 | /// 27 | string LastSQL { get; } 28 | 29 | /// 30 | /// Retrieves the arguments to the last execute statement 31 | /// 32 | object[] LastArgs { get; } 33 | 34 | /// 35 | /// Returns a formatted string describing the last executed SQL statement and it's argument values 36 | /// 37 | string LastCommand { get; } 38 | 39 | /// 40 | /// When set to true, PetaPoco will automatically create the "SELECT columns" part of any query that looks like it needs it 41 | /// 42 | bool EnableAutoSelect { get; set; } 43 | 44 | /// 45 | /// When set to true, parameters can be named ?myparam and populated from properties of the passed in argument values. 46 | /// 47 | bool EnableNamedParams { get; set; } 48 | 49 | /// 50 | /// Sets the timeout value for all SQL statements. 51 | /// 52 | int CommandTimeout { get; set; } 53 | 54 | /// 55 | /// Sets the timeout value for the next (and only next) SQL statement 56 | /// 57 | int OneTimeCommandTimeout { get; set; } 58 | 59 | /// 60 | /// Open a connection that will be used for all subsequent queries. 61 | /// 62 | /// 63 | /// Calls to Open/CloseSharedConnection are reference counted and should be balanced 64 | /// 65 | Task OpenSharedConnectionAsync(); 66 | 67 | /// 68 | /// Releases the shared connection 69 | /// 70 | void CloseSharedConnection(); 71 | 72 | /// 73 | /// Starts or continues a transaction. 74 | /// 75 | /// An ITransaction reference that must be Completed or disposed 76 | /// 77 | /// This method makes management of calls to Begin/End/CompleteTransaction easier. 78 | /// 79 | /// The usage pattern for this should be: 80 | /// 81 | /// using (var tx = db.GetTransaction()) 82 | /// { 83 | /// // Do stuff 84 | /// db.Update(...); 85 | /// 86 | /// // Mark the transaction as complete 87 | /// tx.Complete(); 88 | /// } 89 | /// 90 | /// Transactions can be nested but they must all be completed otherwise the entire 91 | /// transaction is aborted. 92 | /// 93 | Task GetTransactionAsync(); 94 | 95 | /// 96 | /// Called when a transaction starts. Overridden by the T4 template generated database 97 | /// classes to ensure the same DB instance is used throughout the transaction. 98 | /// 99 | void OnBeginTransaction(); 100 | 101 | /// 102 | /// Called when a transaction ends. 103 | /// 104 | void OnEndTransaction(); 105 | 106 | /// 107 | /// Starts a transaction scope, see GetTransaction() for recommended usage 108 | /// 109 | Task BeginTransactionAsync(); 110 | 111 | /// 112 | /// Aborts the entire outer most transaction scope 113 | /// 114 | /// 115 | /// Called automatically by Transaction.Dispose() 116 | /// if the transaction wasn't completed. 117 | /// 118 | void AbortTransaction(); 119 | 120 | /// 121 | /// Marks the current transaction scope as complete. 122 | /// 123 | void CompleteTransaction(); 124 | 125 | DbCommand CreateCommand(DbConnection connection, string sql, params object[] args); 126 | 127 | /// 128 | /// Called if an exception occurs during processing of a DB operation. Override to provide custom logging/handling. 129 | /// 130 | /// The exception instance 131 | /// True to re-throw the exception, false to suppress it 132 | bool OnException(Exception x); 133 | 134 | /// 135 | /// Called when DB connection opened 136 | /// 137 | /// The newly opened DbConnection 138 | /// The same or a replacement DbConnection 139 | /// 140 | /// Override this method to provide custom logging of opening connection, or 141 | /// to provide a proxy DbConnection. 142 | /// 143 | DbConnection OnConnectionOpened(DbConnection conn); 144 | 145 | /// 146 | /// Called when DB connection closed 147 | /// 148 | /// The soon to be closed IDBConnection 149 | void OnConnectionClosing(DbConnection conn); 150 | 151 | /// 152 | /// Called just before an DB command is executed 153 | /// 154 | /// The command to be executed 155 | /// 156 | /// Override this method to provide custom logging of commands and/or 157 | /// modification of the IDbCommand before it's executed 158 | /// 159 | void OnExecutingCommand(IDbCommand cmd); 160 | 161 | /// 162 | /// Called on completion of command execution 163 | /// 164 | /// The IDbCommand that finished executing 165 | void OnExecutedCommand(IDbCommand cmd); 166 | 167 | /// 168 | /// Executes a non-query command 169 | /// 170 | /// The SQL statement to execute 171 | /// Arguments to any embedded parameters in the SQL 172 | /// The number of rows affected 173 | Task ExecuteAsync(string sql, params object[] args); 174 | 175 | /// 176 | /// Executes a non-query command 177 | /// 178 | /// An SQL builder object representing the query and it's arguments 179 | /// The number of rows affected 180 | Task ExecuteAsync(Sql sql); 181 | 182 | /// 183 | /// Executes a query and return the first column of the first row in the result set. 184 | /// 185 | /// The type that the result value should be cast to 186 | /// The SQL query to execute 187 | /// Arguments to any embedded parameters in the SQL 188 | /// The scalar value cast to T 189 | Task ExecuteScalarAsync(string sql, params object[] args); 190 | 191 | /// 192 | /// Executes a query and return the first column of the first row in the result set. 193 | /// 194 | /// The type that the result value should be cast to 195 | /// An SQL builder object representing the query and it's arguments 196 | /// The scalar value cast to T 197 | Task ExecuteScalarAsync(Sql sql); 198 | 199 | /// 200 | /// Runs a query and returns the result set as a typed list 201 | /// 202 | /// The Type representing a row in the result set 203 | /// The SQL query to execute 204 | /// Arguments to any embedded parameters in the SQL 205 | /// A List holding the results of the query 206 | Task> FetchAsync(string sql, params object[] args); 207 | 208 | /// 209 | /// Runs a query and returns the result set as a typed list 210 | /// 211 | /// The Type representing a row in the result set 212 | /// An SQL builder object representing the query and it's arguments 213 | /// A List holding the results of the query 214 | Task> FetchAsync(Sql sql); 215 | 216 | /// 217 | /// Retrieves a page of records and the total number of available records 218 | /// 219 | /// The Type representing a row in the result set 220 | /// The 1 based page number to retrieve 221 | /// The number of records per page 222 | /// The SQL to retrieve the total number of records 223 | /// Arguments to any embedded parameters in the sqlCount statement 224 | /// The SQL To retrieve a single page of results 225 | /// Arguments to any embedded parameters in the sqlPage statement 226 | /// A Page of results 227 | /// 228 | /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. 229 | /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. 230 | /// 231 | Task> PageAsync(long page, long itemsPerPage, string sqlCount, object[] countArgs, string sqlPage, object[] pageArgs); 232 | 233 | /// 234 | /// Retrieves a page of records and the total number of available records 235 | /// 236 | /// The Type representing a row in the result set 237 | /// The 1 based page number to retrieve 238 | /// The number of records per page 239 | /// The base SQL query 240 | /// Arguments to any embedded parameters in the SQL statement 241 | /// A Page of results 242 | /// 243 | /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the 244 | /// records for the specified page. It will also execute a second query to retrieve the 245 | /// total number of records in the result set. 246 | /// 247 | Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args); 248 | 249 | /// 250 | /// Retrieves a page of records and the total number of available records 251 | /// 252 | /// The Type representing a row in the result set 253 | /// The 1 based page number to retrieve 254 | /// The number of records per page 255 | /// An SQL builder object representing the base SQL query and it's arguments 256 | /// A Page of results 257 | /// 258 | /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the 259 | /// records for the specified page. It will also execute a second query to retrieve the 260 | /// total number of records in the result set. 261 | /// 262 | Task> PageAsync(long page, long itemsPerPage, Sql sql); 263 | 264 | /// 265 | /// Retrieves a page of records and the total number of available records 266 | /// 267 | /// The Type representing a row in the result set 268 | /// The 1 based page number to retrieve 269 | /// The number of records per page 270 | /// An SQL builder object representing the SQL to retrieve the total number of records 271 | /// An SQL builder object representing the SQL to retrieve a single page of results 272 | /// A Page of results 273 | /// 274 | /// This method allows separate SQL statements to be explicitly provided for the two parts of the page query. 275 | /// The page and itemsPerPage parameters are not used directly and are used simply to populate the returned Page object. 276 | /// 277 | Task> PageAsync(long page, long itemsPerPage, Sql sqlCount, Sql sqlPage); 278 | 279 | /// 280 | /// Retrieves a page of records (without the total count) 281 | /// 282 | /// The Type representing a row in the result set 283 | /// The 1 based page number to retrieve 284 | /// The number of records per page 285 | /// The base SQL query 286 | /// Arguments to any embedded parameters in the SQL statement 287 | /// A List of results 288 | /// 289 | /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the 290 | /// records for the specified page. 291 | /// 292 | Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); 293 | 294 | /// 295 | /// Retrieves a page of records (without the total count) 296 | /// 297 | /// The Type representing a row in the result set 298 | /// The 1 based page number to retrieve 299 | /// The number of records per page 300 | /// An SQL builder object representing the base SQL query and it's arguments 301 | /// A List of results 302 | /// 303 | /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the 304 | /// records for the specified page. 305 | /// 306 | Task> FetchAsync(long page, long itemsPerPage, Sql sql); 307 | 308 | /// 309 | /// Retrieves a range of records from result set 310 | /// 311 | /// The Type representing a row in the result set 312 | /// The number of rows at the start of the result set to skip over 313 | /// The number of rows to retrieve 314 | /// The base SQL query 315 | /// Arguments to any embedded parameters in the SQL statement 316 | /// A List of results 317 | /// 318 | /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the 319 | /// records for the specified range. 320 | /// 321 | Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); 322 | 323 | /// 324 | /// Retrieves a range of records from result set 325 | /// 326 | /// The Type representing a row in the result set 327 | /// The number of rows at the start of the result set to skip over 328 | /// The number of rows to retrieve 329 | /// An SQL builder object representing the base SQL query and it's arguments 330 | /// A List of results 331 | /// 332 | /// PetaPoco will automatically modify the supplied SELECT statement to only retrieve the 333 | /// records for the specified range. 334 | /// 335 | Task> SkipTakeAsync(long skip, long take, Sql sql); 336 | 337 | /// 338 | /// Runs an SQL query, asynchronously passing each result to a callback 339 | /// 340 | /// The Type representing a row in the result set 341 | /// The SQL query 342 | /// Callback to process each result 343 | /// 344 | /// For some DB providers, care should be taken to not start a new Query before finishing with 345 | /// and disposing the previous one. In cases where this is an issue, consider using Fetch which 346 | /// returns the results as a List. 347 | /// 348 | Task QueryAsync(string sql, Action action); 349 | 350 | /// 351 | /// Runs an SQL query, asynchronously passing each result to a callback 352 | /// 353 | /// The Type representing a row in the result set 354 | /// The SQL query 355 | /// Callback to process each result, return false to stop iterating 356 | /// 357 | /// For some DB providers, care should be taken to not start a new Query before finishing with 358 | /// and disposing the previous one. In cases where this is an issue, consider using Fetch which 359 | /// returns the results as a List. 360 | /// 361 | Task QueryAsync(string sql, Func func); 362 | 363 | /// 364 | /// Runs an SQL query, asynchronously passing each result to a callback 365 | /// 366 | /// The Type representing a row in the result set 367 | /// The SQL query 368 | /// Arguments to any embedded parameters in the SQL statement 369 | /// Callback to process each result 370 | /// 371 | /// For some DB providers, care should be taken to not start a new Query before finishing with 372 | /// and disposing the previous one. In cases where this is an issue, consider using Fetch which 373 | /// returns the results as a List. 374 | /// 375 | Task QueryAsync(string sql, object[] args, Action action); 376 | 377 | /// 378 | /// Runs an SQL query, asynchronously passing each result to a callback 379 | /// 380 | /// The Type representing a row in the result set 381 | /// The SQL query 382 | /// Arguments to any embedded parameters in the SQL statement 383 | /// Callback to process each result, return false to stop iterating 384 | /// 385 | /// For some DB providers, care should be taken to not start a new Query before finishing with 386 | /// and disposing the previous one. In cases where this is an issue, consider using Fetch which 387 | /// returns the results as a List. 388 | /// 389 | Task QueryAsync(string sql, object[] args, Func func); 390 | 391 | /// 392 | /// Runs an SQL query, asynchronously passing each result to a callback 393 | /// 394 | /// The Type representing a row in the result set 395 | /// An SQL builder object representing the base SQL query and it's arguments 396 | /// Callback to process each result 397 | /// 398 | /// For some DB providers, care should be taken to not start a new Query before finishing with 399 | /// and disposing the previous one. In cases where this is an issue, consider using Fetch which 400 | /// returns the results as a List. 401 | /// 402 | Task QueryAsync(Sql sql, Action action); 403 | 404 | /// 405 | /// Runs an SQL query, asynchronously passing each result to a callback 406 | /// 407 | /// The Type representing a row in the result set 408 | /// An SQL builder object representing the base SQL query and it's arguments 409 | /// Callback to process each result, return false to stop iterating 410 | /// 411 | /// For some DB providers, care should be taken to not start a new Query before finishing with 412 | /// and disposing the previous one. In cases where this is an issue, consider using Fetch which 413 | /// returns the results as a List. 414 | /// 415 | Task QueryAsync(Sql sql, Func func); 416 | 417 | /// 418 | /// Checks for the existance of a row matching the specified condition 419 | /// 420 | /// The Type representing the table being queried 421 | /// The SQL expression to be tested for (ie: the WHERE expression) 422 | /// Arguments to any embedded parameters in the SQL statement 423 | /// True if a record matching the condition is found. 424 | Task ExistsAsync(string sqlCondition, params object[] args); 425 | 426 | /// 427 | /// Checks for the existance of a row with the specified primary key value. 428 | /// 429 | /// The Type representing the table being queried 430 | /// The primary key value to look for 431 | /// True if a record with the specified primary key value exists. 432 | Task ExistsAsync(object primaryKey); 433 | 434 | /// 435 | /// Returns the record with the specified primary key value 436 | /// 437 | /// The Type representing a row in the result set 438 | /// The primary key value of the record to fetch 439 | /// The single record matching the specified primary key value 440 | /// 441 | /// Throws an exception if there are zero or more than one record with the specified primary key value. 442 | /// 443 | Task SingleAsync(object primaryKey); 444 | 445 | /// 446 | /// Returns the record with the specified primary key value, or the default value if not found 447 | /// 448 | /// The Type representing a row in the result set 449 | /// The primary key value of the record to fetch 450 | /// The single record matching the specified primary key value 451 | /// 452 | /// If there are no records with the specified primary key value, default(T) (typically null) is returned. 453 | /// 454 | Task SingleOrDefaultAsync(object primaryKey); 455 | 456 | /// 457 | /// Runs a query that should always return a single row. 458 | /// 459 | /// The Type representing a row in the result set 460 | /// The SQL query 461 | /// Arguments to any embedded parameters in the SQL statement 462 | /// The single record matching the specified primary key value 463 | /// 464 | /// Throws an exception if there are zero or more than one matching record 465 | /// 466 | Task SingleAsync(string sql, params object[] args); 467 | 468 | /// 469 | /// Runs a query that should always return either a single row, or no rows 470 | /// 471 | /// The Type representing a row in the result set 472 | /// The SQL query 473 | /// Arguments to any embedded parameters in the SQL statement 474 | /// The single record matching the specified primary key value, or default(T) if no matching rows 475 | Task SingleOrDefaultAsync(string sql, params object[] args); 476 | 477 | /// 478 | /// Runs a query that should always return at least one return 479 | /// 480 | /// The Type representing a row in the result set 481 | /// The SQL query 482 | /// Arguments to any embedded parameters in the SQL statement 483 | /// The first record in the result set 484 | Task FirstAsync(string sql, params object[] args); 485 | 486 | /// 487 | /// Runs a query and returns the first record, or the default value if no matching records 488 | /// 489 | /// The Type representing a row in the result set 490 | /// The SQL query 491 | /// Arguments to any embedded parameters in the SQL statement 492 | /// The first record in the result set, or default(T) if no matching rows 493 | Task FirstOrDefaultAsync(string sql, params object[] args); 494 | 495 | /// 496 | /// Runs a query that should always return a single row. 497 | /// 498 | /// The Type representing a row in the result set 499 | /// An SQL builder object representing the query and it's arguments 500 | /// The single record matching the specified primary key value 501 | /// 502 | /// Throws an exception if there are zero or more than one matching record 503 | /// 504 | Task SingleAsync(Sql sql); 505 | 506 | /// 507 | /// Runs a query that should always return either a single row, or no rows 508 | /// 509 | /// The Type representing a row in the result set 510 | /// An SQL builder object representing the query and it's arguments 511 | /// The single record matching the specified primary key value, or default(T) if no matching rows 512 | Task SingleOrDefaultAsync(Sql sql); 513 | 514 | /// 515 | /// Runs a query that should always return at least one return 516 | /// 517 | /// The Type representing a row in the result set 518 | /// An SQL builder object representing the query and it's arguments 519 | /// The first record in the result set 520 | Task FirstAsync(Sql sql); 521 | 522 | /// 523 | /// Runs a query and returns the first record, or the default value if no matching records 524 | /// 525 | /// The Type representing a row in the result set 526 | /// An SQL builder object representing the query and it's arguments 527 | /// The first record in the result set, or default(T) if no matching rows 528 | Task FirstOrDefaultAsync(Sql sql); 529 | 530 | /// 531 | /// Performs an SQL Insert 532 | /// 533 | /// The name of the table to insert into 534 | /// The name of the primary key column of the table 535 | /// The POCO object that specifies the column values to be inserted 536 | /// The auto allocated primary key of the new record 537 | Task InsertAsync(string tableName, string primaryKeyName, object poco); 538 | 539 | /// 540 | /// Performs an SQL Insert 541 | /// 542 | /// The name of the table to insert into 543 | /// The name of the primary key column of the table 544 | /// True if the primary key is automatically allocated by the DB 545 | /// The POCO object that specifies the column values to be inserted 546 | /// The auto allocated primary key of the new record, or null for non-auto-increment tables 547 | /// Inserts a poco into a table. If the poco has a property with the same name 548 | /// as the primary key the id of the new record is assigned to it. Either way, 549 | /// the new id is returned. 550 | Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, object poco); 551 | 552 | /// 553 | /// Performs an SQL Insert 554 | /// 555 | /// The POCO object that specifies the column values to be inserted 556 | /// The auto allocated primary key of the new record, or null for non-auto-increment tables 557 | /// The name of the table, it's primary key and whether it's an auto-allocated primary key are retrieved 558 | /// from the POCO's attributes 559 | Task InsertAsync(object poco); 560 | 561 | /// 562 | /// Performs an SQL update 563 | /// 564 | /// The name of the table to update 565 | /// The name of the primary key column of the table 566 | /// The POCO object that specifies the column values to be updated 567 | /// The primary key of the record to be updated 568 | /// The number of affected records 569 | Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); 570 | 571 | /// 572 | /// Performs an SQL update 573 | /// 574 | /// The name of the table to update 575 | /// The name of the primary key column of the table 576 | /// The POCO object that specifies the column values to be updated 577 | /// The primary key of the record to be updated 578 | /// The column names of the columns to be updated, or null for all 579 | /// The number of affected rows 580 | Task UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); 581 | 582 | /// 583 | /// Performs an SQL update 584 | /// 585 | /// The name of the table to update 586 | /// The name of the primary key column of the table 587 | /// The POCO object that specifies the column values to be updated 588 | /// The number of affected rows 589 | Task UpdateAsync(string tableName, string primaryKeyName, object poco); 590 | 591 | /// 592 | /// Performs an SQL update 593 | /// 594 | /// The name of the table to update 595 | /// The name of the primary key column of the table 596 | /// The POCO object that specifies the column values to be updated 597 | /// The column names of the columns to be updated, or null for all 598 | /// The number of affected rows 599 | Task UpdateAsync(string tableName, string primaryKeyName, object poco, IEnumerable columns); 600 | 601 | /// 602 | /// Performs an SQL update 603 | /// 604 | /// The POCO object that specifies the column values to be updated 605 | /// The column names of the columns to be updated, or null for all 606 | /// The number of affected rows 607 | Task UpdateAsync(object poco, IEnumerable columns); 608 | 609 | /// 610 | /// Performs an SQL update 611 | /// 612 | /// The POCO object that specifies the column values to be updated 613 | /// The number of affected rows 614 | Task UpdateAsync(object poco); 615 | 616 | /// 617 | /// Performs an SQL update 618 | /// 619 | /// The POCO object that specifies the column values to be updated 620 | /// The primary key of the record to be updated 621 | /// The number of affected rows 622 | Task UpdateAsync(object poco, object primaryKeyValue); 623 | 624 | /// 625 | /// Performs an SQL update 626 | /// 627 | /// The POCO object that specifies the column values to be updated 628 | /// The primary key of the record to be updated 629 | /// The column names of the columns to be updated, or null for all 630 | /// The number of affected rows 631 | Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable columns); 632 | 633 | /// 634 | /// Performs an SQL update 635 | /// 636 | /// The POCO class who's attributes specify the name of the table to update 637 | /// The SQL update and condition clause (ie: everything after "UPDATE tablename" 638 | /// Arguments to any embedded parameters in the SQL 639 | /// The number of affected rows 640 | Task UpdateAsync(string sql, params object[] args); 641 | 642 | /// 643 | /// Performs an SQL update 644 | /// 645 | /// The POCO class who's attributes specify the name of the table to update 646 | /// An SQL builder object representing the SQL update and condition clause (ie: everything after "UPDATE tablename" 647 | /// The number of affected rows 648 | Task UpdateAsync(Sql sql); 649 | 650 | /// 651 | /// Performs and SQL Delete 652 | /// 653 | /// The name of the table to delete from 654 | /// The name of the primary key column 655 | /// The POCO object whose primary key value will be used to delete the row 656 | /// The number of rows affected 657 | Task DeleteAsync(string tableName, string primaryKeyName, object poco); 658 | 659 | /// 660 | /// Performs and SQL Delete 661 | /// 662 | /// The name of the table to delete from 663 | /// The name of the primary key column 664 | /// The POCO object whose primary key value will be used to delete the row (or null to use the supplied primary key value) 665 | /// The value of the primary key identifing the record to be deleted (or null, or get this value from the POCO instance) 666 | /// The number of rows affected 667 | Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue); 668 | 669 | /// 670 | /// Performs an SQL Delete 671 | /// 672 | /// The POCO object specifying the table name and primary key value of the row to be deleted 673 | /// The number of rows affected 674 | Task DeleteAsync(object poco); 675 | 676 | /// 677 | /// Performs an SQL Delete 678 | /// 679 | /// The POCO class whose attributes identify the table and primary key to be used in the delete 680 | /// The value of the primary key of the row to delete 681 | /// 682 | Task DeleteAsync(object pocoOrPrimaryKey); 683 | 684 | /// 685 | /// Performs an SQL Delete 686 | /// 687 | /// The POCO class who's attributes specify the name of the table to delete from 688 | /// The SQL condition clause identifying the row to delete (ie: everything after "DELETE FROM tablename" 689 | /// Arguments to any embedded parameters in the SQL 690 | /// The number of affected rows 691 | Task DeleteAsync(string sql, params object[] args); 692 | 693 | /// 694 | /// Performs an SQL Delete 695 | /// 696 | /// The POCO class who's attributes specify the name of the table to delete from 697 | /// An SQL builder object representing the SQL condition clause identifying the row to delete (ie: everything after "UPDATE tablename" 698 | /// The number of affected rows 699 | Task DeleteAsync(Sql sql); 700 | 701 | /// 702 | /// Check if a poco represents a new row 703 | /// 704 | /// The name of the primary key column 705 | /// The object instance whose "newness" is to be tested 706 | /// True if the POCO represents a record already in the database 707 | /// This method simply tests if the POCO's primary key column property has been set to something non-zero. 708 | bool IsNew(string primaryKeyName, object poco); 709 | 710 | /// 711 | /// Check if a poco represents a new row 712 | /// 713 | /// The object instance whose "newness" is to be tested 714 | /// True if the POCO represents a record already in the database 715 | /// This method simply tests if the POCO's primary key column property has been set to something non-zero. 716 | bool IsNew(object poco); 717 | 718 | /// 719 | /// Saves a POCO by either performing either an SQL Insert or SQL Update 720 | /// 721 | /// The name of the table to be updated 722 | /// The name of the primary key column 723 | /// The POCO object to be saved 724 | Task SaveAsync(string tableName, string primaryKeyName, object poco); 725 | 726 | /// 727 | /// Saves a POCO by either performing either an SQL Insert or SQL Update 728 | /// 729 | /// The POCO object to be saved 730 | Task SaveAsync(object poco); 731 | 732 | /// 733 | /// Perform a multi-poco fetch 734 | /// 735 | /// The first POCO type 736 | /// The second POCO type 737 | /// The returned list POCO type 738 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 739 | /// The SQL query to be executed 740 | /// Arguments to any embedded parameters in the SQL 741 | /// A collection of POCO's as a List 742 | Task> FetchAsync(Func cb, string sql, params object[] args); 743 | 744 | /// 745 | /// Perform a multi-poco fetch 746 | /// 747 | /// The first POCO type 748 | /// The second POCO type 749 | /// The third POCO type 750 | /// The returned list POCO type 751 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 752 | /// The SQL query to be executed 753 | /// Arguments to any embedded parameters in the SQL 754 | /// A collection of POCO's as a List 755 | Task> FetchAsync(Func cb, string sql, params object[] args); 756 | 757 | /// 758 | /// Perform a multi-poco fetch 759 | /// 760 | /// The first POCO type 761 | /// The second POCO type 762 | /// The third POCO type 763 | /// The fourth POCO type 764 | /// The returned list POCO type 765 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 766 | /// The SQL query to be executed 767 | /// Arguments to any embedded parameters in the SQL 768 | /// A collection of POCO's as a List 769 | Task> FetchAsync(Func cb, string sql, params object[] args); 770 | 771 | /// 772 | /// Perform a multi-poco query 773 | /// 774 | /// The first POCO type 775 | /// The second POCO type 776 | /// The type of objects passed to the action callback 777 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 778 | /// The SQL query to be executed 779 | /// Arguments to any embedded parameters in the SQL 780 | /// Callback to process each result 781 | Task QueryAsync(Func cb, string sql, object[] args, Action action); 782 | 783 | /// 784 | /// Perform a multi-poco query 785 | /// 786 | /// The first POCO type 787 | /// The second POCO type 788 | /// The third POCO type 789 | /// The type of objects passed to the action callback 790 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 791 | /// The SQL query to be executed 792 | /// Arguments to any embedded parameters in the SQL 793 | /// Callback to process each result 794 | Task QueryAsync(Func cb, string sql, object[] args, Action action); 795 | 796 | /// 797 | /// Perform a multi-poco query 798 | /// 799 | /// The first POCO type 800 | /// The second POCO type 801 | /// The third POCO type 802 | /// The fourth POCO type 803 | /// The type of objects passed to the action callback 804 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 805 | /// The SQL query to be executed 806 | /// Arguments to any embedded parameters in the SQL 807 | /// Callback to process each result 808 | Task QueryAsync(Func cb, string sql, object[] args, Action action); 809 | 810 | /// 811 | /// Perform a multi-poco fetch 812 | /// 813 | /// The first POCO type 814 | /// The second POCO type 815 | /// The returned list POCO type 816 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 817 | /// An SQL builder object representing the query and it's arguments 818 | /// A collection of POCO's as a List 819 | Task> FetchAsync(Func cb, Sql sql); 820 | 821 | /// 822 | /// Perform a multi-poco fetch 823 | /// 824 | /// The first POCO type 825 | /// The second POCO type 826 | /// The third POCO type 827 | /// The returned list POCO type 828 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 829 | /// An SQL builder object representing the query and it's arguments 830 | /// A collection of POCO's as a List 831 | Task> FetchAsync(Func cb, Sql sql); 832 | 833 | /// 834 | /// Perform a multi-poco fetch 835 | /// 836 | /// The first POCO type 837 | /// The second POCO type 838 | /// The third POCO type 839 | /// The fourth POCO type 840 | /// The returned list POCO type 841 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 842 | /// An SQL builder object representing the query and it's arguments 843 | /// A collection of POCO's as a List 844 | Task> FetchAsync(Func cb, Sql sql); 845 | 846 | /// 847 | /// Perform a multi-poco query 848 | /// 849 | /// The first POCO type 850 | /// The second POCO type 851 | /// The type of objects passed to the action callback 852 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 853 | /// An SQL builder object representing the query and it's arguments 854 | /// Callback to process each result 855 | Task QueryAsync(Func cb, Sql sql, Action action); 856 | 857 | /// 858 | /// Perform a multi-poco query 859 | /// 860 | /// The first POCO type 861 | /// The second POCO type 862 | /// The third POCO type 863 | /// The type of objects passed to the action callback 864 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 865 | /// An SQL builder object representing the query and it's arguments 866 | /// Callback to process each result 867 | Task QueryAsync(Func cb, Sql sql, Action action); 868 | 869 | /// 870 | /// Perform a multi-poco query 871 | /// 872 | /// The first POCO type 873 | /// The second POCO type 874 | /// The third POCO type 875 | /// The fourth POCO type 876 | /// The type of objects passed to the action callback 877 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 878 | /// An SQL builder object representing the query and it's arguments 879 | /// Callback to process each result 880 | Task QueryAsync(Func cb, Sql sql, Action action); 881 | 882 | /// 883 | /// Perform a multi-poco fetch 884 | /// 885 | /// The first POCO type 886 | /// The second POCO type 887 | /// The SQL query to be executed 888 | /// Arguments to any embedded parameters in the SQL 889 | /// A collection of POCO's as a List 890 | Task> FetchAsync(string sql, params object[] args); 891 | 892 | /// 893 | /// Perform a multi-poco fetch 894 | /// 895 | /// The first POCO type 896 | /// The second POCO type 897 | /// The third POCO type 898 | /// The SQL query to be executed 899 | /// Arguments to any embedded parameters in the SQL 900 | /// A collection of POCO's as a List 901 | Task> FetchAsync(string sql, params object[] args); 902 | 903 | /// 904 | /// Perform a multi-poco fetch 905 | /// 906 | /// The first POCO type 907 | /// The second POCO type 908 | /// The third POCO type 909 | /// The fourth POCO type 910 | /// The SQL query to be executed 911 | /// Arguments to any embedded parameters in the SQL 912 | /// A collection of POCO's as a List 913 | Task> FetchAsync(string sql, params object[] args); 914 | 915 | /// 916 | /// Perform a multi-poco query 917 | /// 918 | /// The first POCO type 919 | /// The second POCO type 920 | /// The SQL query to be executed 921 | /// Arguments to any embedded parameters in the SQL 922 | /// Callback to process each result 923 | Task QueryAsync(string sql, object[] args, Action action); 924 | 925 | /// 926 | /// Perform a multi-poco query 927 | /// 928 | /// The first POCO type 929 | /// The second POCO type 930 | /// The third POCO type 931 | /// The SQL query to be executed 932 | /// Arguments to any embedded parameters in the SQL 933 | /// Callback to process each result 934 | Task QueryAsync(string sql, object[] args, Action action); 935 | 936 | /// 937 | /// Perform a multi-poco query 938 | /// 939 | /// The first POCO type 940 | /// The second POCO type 941 | /// The third POCO type 942 | /// The fourth POCO type 943 | /// The SQL query to be executed 944 | /// Arguments to any embedded parameters in the SQL 945 | /// Callback to process each result 946 | Task QueryAsync(string sql, object[] args, Action action); 947 | 948 | /// 949 | /// Perform a multi-poco fetch 950 | /// 951 | /// The first POCO type 952 | /// The second POCO type 953 | /// An SQL builder object representing the query and it's arguments 954 | /// A collection of POCO's as a List 955 | Task> FetchAsync(Sql sql); 956 | 957 | /// 958 | /// Perform a multi-poco fetch 959 | /// 960 | /// The first POCO type 961 | /// The second POCO type 962 | /// The third POCO type 963 | /// An SQL builder object representing the query and it's arguments 964 | /// A collection of POCO's as a List 965 | Task> FetchAsync(Sql sql); 966 | 967 | /// 968 | /// Perform a multi-poco fetch 969 | /// 970 | /// The first POCO type 971 | /// The second POCO type 972 | /// The third POCO type 973 | /// The fourth POCO type 974 | /// An SQL builder object representing the query and it's arguments 975 | /// A collection of POCO's as a List 976 | Task> FetchAsync(Sql sql); 977 | 978 | /// 979 | /// Perform a multi-poco query 980 | /// 981 | /// The first POCO type 982 | /// The second POCO type 983 | /// An SQL builder object representing the query and it's arguments 984 | /// Callback to process each result 985 | Task QueryAsync(Sql sql, Action action); 986 | 987 | /// 988 | /// Perform a multi-poco query 989 | /// 990 | /// The first POCO type 991 | /// The second POCO type 992 | /// The third POCO type 993 | /// An SQL builder object representing the query and it's arguments 994 | /// Callback to process each result 995 | Task QueryAsync(Sql sql, Action action); 996 | 997 | /// 998 | /// Perform a multi-poco query 999 | /// 1000 | /// The first POCO type 1001 | /// The second POCO type 1002 | /// The third POCO type 1003 | /// The fourth POCO type 1004 | /// An SQL builder object representing the query and it's arguments 1005 | /// Callback to process each result 1006 | Task QueryAsync(Sql sql, Action action); 1007 | 1008 | /// 1009 | /// Perform a multi-poco fetch 1010 | /// 1011 | /// The type of objects to pass to the action 1012 | /// An array of Types representing the POCO types of the returned result set. 1013 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 1014 | /// The SQL query to be executed 1015 | /// Arguments to any embedded parameters in the SQL 1016 | /// A collection of POCO's as a List 1017 | Task> FetchAsync(Type[] types, object cb, string sql, params object[] args); 1018 | 1019 | /// 1020 | /// Performs a multi-poco query 1021 | /// 1022 | /// The type of objects to pass to the action 1023 | /// An array of Types representing the POCO types of the returned result set. 1024 | /// A callback function to connect the POCO instances, or null to automatically guess the relationships 1025 | /// The SQL query to be executed 1026 | /// Arguments to any embedded parameters in the SQL 1027 | /// Callback to process each result 1028 | Task QueryAsync(Type[] types, object cb, string sql, object[] args, Action action); 1029 | 1030 | /// 1031 | /// Formats the contents of a DB command for display 1032 | /// 1033 | /// 1034 | /// 1035 | string FormatCommand(IDbCommand cmd); 1036 | 1037 | /// 1038 | /// Formats an SQL query and it's arguments for display 1039 | /// 1040 | /// 1041 | /// 1042 | /// 1043 | string FormatCommand(string sql, object[] args); 1044 | } 1045 | } -------------------------------------------------------------------------------- /AsyncPoco/Utilities/ArrayKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace AsyncPoco.Internal 7 | { 8 | class ArrayKey 9 | { 10 | public ArrayKey(T[] keys) 11 | { 12 | // Store the keys 13 | _keys = keys; 14 | 15 | // Calculate the hashcode 16 | _hashCode = 17; 17 | foreach (var k in keys) 18 | { 19 | _hashCode = _hashCode * 23 + (k==null ? 0 : k.GetHashCode()); 20 | } 21 | } 22 | 23 | T[] _keys; 24 | int _hashCode; 25 | 26 | bool Equals(ArrayKey other) 27 | { 28 | if (other == null) 29 | return false; 30 | 31 | if (other._hashCode != _hashCode) 32 | return false; 33 | 34 | if (other._keys.Length != _keys.Length) 35 | return false; 36 | 37 | for (int i = 0; i < _keys.Length; i++) 38 | { 39 | if (!object.Equals(_keys[i], other._keys[i])) 40 | return false; 41 | } 42 | 43 | return true; 44 | } 45 | 46 | public override bool Equals(object obj) 47 | { 48 | return Equals(obj as ArrayKey); 49 | } 50 | 51 | public override int GetHashCode() 52 | { 53 | return _hashCode; 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AsyncPoco/Utilities/AutoSelectHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace AsyncPoco.Internal 8 | { 9 | static class AutoSelectHelper 10 | { 11 | public static string AddSelectClause(DatabaseType DatabaseType, string sql) 12 | { 13 | if (sql.StartsWith(";")) 14 | return sql.Substring(1); 15 | 16 | if (!rxSelect.IsMatch(sql)) 17 | { 18 | var pd = PocoData.ForType(typeof(T)); 19 | var tableName = DatabaseType.EscapeTableName(pd.TableInfo.TableName); 20 | string cols = pd.Columns.Count != 0 ? string.Join(", ", (from c in pd.QueryColumns select tableName + "." + DatabaseType.EscapeSqlIdentifier(c)).ToArray()) : "NULL"; 21 | if (!rxFrom.IsMatch(sql)) 22 | sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql); 23 | else 24 | sql = string.Format("SELECT {0} {1}", cols, sql); 25 | } 26 | return sql; 27 | } 28 | 29 | static Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); 30 | static Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AsyncPoco/Utilities/Cache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace AsyncPoco.Internal 8 | { 9 | class Cache 10 | { 11 | Dictionary _map = new Dictionary(); 12 | ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); 13 | 14 | public int Count 15 | { 16 | get 17 | { 18 | return _map.Count; 19 | } 20 | } 21 | 22 | public TValue Get(TKey key, Func factory) 23 | { 24 | // Check cache 25 | _lock.EnterReadLock(); 26 | TValue val; 27 | try 28 | { 29 | if (_map.TryGetValue(key, out val)) 30 | return val; 31 | } 32 | finally 33 | { 34 | _lock.ExitReadLock(); 35 | } 36 | 37 | 38 | // Cache it 39 | _lock.EnterWriteLock(); 40 | try 41 | { 42 | // Check again 43 | if (_map.TryGetValue(key, out val)) 44 | return val; 45 | 46 | // Create it 47 | val = factory(); 48 | 49 | // Store it 50 | _map.Add(key, val); 51 | 52 | // Done 53 | return val; 54 | } 55 | finally 56 | { 57 | _lock.ExitWriteLock(); 58 | } 59 | } 60 | 61 | public void Flush() 62 | { 63 | // Cache it 64 | _lock.EnterWriteLock(); 65 | try 66 | { 67 | _map.Clear(); 68 | } 69 | finally 70 | { 71 | _lock.ExitWriteLock(); 72 | } 73 | 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AsyncPoco/Utilities/EnumMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace AsyncPoco.Internal 8 | { 9 | internal static class EnumMapper 10 | { 11 | public static IEqualityComparer FieldComparer { get; set; } = StringComparer.OrdinalIgnoreCase; 12 | 13 | public static object EnumFromString(Type enumType, string value) 14 | { 15 | if (!enumType.GetTypeInfo().IsEnum) { 16 | enumType = Nullable.GetUnderlyingType(enumType); 17 | } 18 | 19 | Dictionary map = _types.Get(enumType, () => 20 | { 21 | var values = Enum.GetValues(enumType); 22 | 23 | var newmap = new Dictionary(values.Length, FieldComparer); 24 | 25 | foreach (var v in values) 26 | { 27 | newmap.Add(v.ToString(), v); 28 | } 29 | 30 | return newmap; 31 | }); 32 | 33 | 34 | return (value == null) ? null : map[value]; 35 | } 36 | 37 | public static object EnumFromNullableInt(Type dstType, int? src) 38 | { 39 | return src.HasValue ? Enum.ToObject(Nullable.GetUnderlyingType(dstType), src) : null; 40 | } 41 | 42 | static Cache> _types = new Cache>(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AsyncPoco/Utilities/PagingHelper.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text.RegularExpressions; 8 | using System.Text; 9 | 10 | namespace AsyncPoco.Internal 11 | { 12 | internal static class PagingHelper 13 | { 14 | public struct SQLParts 15 | { 16 | public string sql; 17 | public string sqlCount; 18 | public string sqlSelectRemoved; 19 | public string sqlOrderBy; 20 | } 21 | 22 | public static bool SplitSQL(string sql, out SQLParts parts) 23 | { 24 | parts.sql = sql; 25 | parts.sqlSelectRemoved = null; 26 | parts.sqlCount = null; 27 | parts.sqlOrderBy = null; 28 | 29 | // Extract the columns from "SELECT FROM" 30 | var m = rxColumns.Match(sql); 31 | if (!m.Success) 32 | return false; 33 | 34 | // Save column list and replace with COUNT(*) 35 | Group g = m.Groups[1]; 36 | parts.sqlSelectRemoved = sql.Substring(g.Index); 37 | 38 | if (rxDistinct.IsMatch(parts.sqlSelectRemoved)) 39 | parts.sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); 40 | else 41 | parts.sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); 42 | 43 | 44 | // Look for the last "ORDER BY " clause not part of a ROW_NUMBER expression 45 | m = rxOrderBy.Match(parts.sqlCount); 46 | if (!m.Success) 47 | { 48 | parts.sqlOrderBy = null; 49 | } 50 | else 51 | { 52 | g = m.Groups[0]; 53 | parts.sqlOrderBy = g.ToString(); 54 | parts.sqlCount = parts.sqlCount.Substring(0, g.Index) + parts.sqlCount.Substring(g.Index + g.Length); 55 | } 56 | 57 | return true; 58 | } 59 | 60 | public static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); 62 | public static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AsyncPoco/Utilities/ParametersHelper.cs: -------------------------------------------------------------------------------- 1 | // AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 2 | // PetaPoco - A Tiny ORMish thing for your POCO's. 3 | // Copyright © 2011-2012 Topten Software. All Rights Reserved. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using System.Text.RegularExpressions; 9 | using System.Text; 10 | 11 | namespace AsyncPoco.Internal 12 | { 13 | internal static class ParametersHelper 14 | { 15 | // Helper to handle named parameters from object properties 16 | public static string ProcessParams(string sql, object[] args_src, List args_dest) 17 | { 18 | return rxParams.Replace(sql, m => 19 | { 20 | string param = m.Value.Substring(1); 21 | 22 | object arg_val; 23 | 24 | int paramIndex; 25 | if (int.TryParse(param, out paramIndex)) 26 | { 27 | // Numbered parameter 28 | if (paramIndex < 0 || paramIndex >= args_src.Length) 29 | throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, sql)); 30 | arg_val = args_src[paramIndex]; 31 | } 32 | else 33 | { 34 | // Look for a property on one of the arguments with this name 35 | bool found = false; 36 | arg_val = null; 37 | foreach (var o in args_src) 38 | { 39 | var pi = o.GetType().GetTypeInfo().GetDeclaredProperty(param); 40 | if (pi != null) 41 | { 42 | arg_val = pi.GetValue(o, null); 43 | found = true; 44 | break; 45 | } 46 | } 47 | 48 | if (!found) 49 | throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, sql)); 50 | } 51 | 52 | // Expand collections to parameter lists 53 | if ((arg_val as System.Collections.IEnumerable) != null && 54 | (arg_val as string) == null && 55 | (arg_val as byte[]) == null) 56 | { 57 | var sb = new StringBuilder(); 58 | foreach (var i in arg_val as System.Collections.IEnumerable) 59 | { 60 | sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count.ToString()); 61 | args_dest.Add(i); 62 | } 63 | return sb.ToString(); 64 | } 65 | else 66 | { 67 | args_dest.Add(arg_val); 68 | return "@" + (args_dest.Count - 1).ToString(); 69 | } 70 | } 71 | ); 72 | } 73 | 74 | static Regex rxParams = new Regex(@"(? where T : new() 9 | { 10 | public static T Instance = new T(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | AsyncPoco - Copyright (C) 2018 (tmenier@gmail.com) 2 | AsyncPoco is a fork of PetaPoco and is bound by the same licensing terms. 3 | 4 | PetaPoco - Copyright (C) 2011-2011 Topten Software (contact@toptensoftware.com) 5 | All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this product except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AsyncPoco 2 | 3 | ***2.0 is here! For details and help transitioning from 1.x, check out the [upgrade guide](https://github.com/tmenier/AsyncPoco/wiki/2.0-Upgrade-Guide).*** 4 | 5 | AsyncPoco is a fork of the popular PetaPoco micro-ORM for .NET, with a fully asynchronous API and broad cross-platform support, including .NET Core. If you're familiar with PetaPoco and the [TAP pattern](http://msdn.microsoft.com/en-us/library/hh873175.aspx) (i.e. `async`/`await`), the transition to AsyncPoco should be quite intuitive. 6 | 7 | ```C# 8 | var db = new AsyncPoco.Database("connectionStringName"); 9 | 10 | var count = await db.ExecuteScalarAsync("SELECT Count(*) FROM articles"); 11 | var a = await db.SingleOrDefaultAsync
("SELECT * FROM articles WHERE article_id = @0", 123); 12 | var result = await db.PageAsync
(1, 20, // <-- page number and items per page 13 | "SELECT * FROM articles WHERE category = @0 ORDER BY date_posted DESC", "coolstuff"); 14 | 15 | await db.ExecuteAsync("DELETE FROM articles WHERE draft<>0"); 16 | await db.DeleteAsync
("WHERE article_id = @0", 123); 17 | await db.UpdateAsync
("SET title = @0 WHERE article_id = @1", "New Title", 123); 18 | await db.SaveAsync(a); 19 | ``` 20 | 21 | One imporant note is that **the constructor in the example above is not supported in .NET Core**. In a config file, a connection string generally includes a `providerName`, which resolves to a globally registered ADO.NET provider. Unfortunately, this functionality is absent in .NET Core, so AsyncPoco requires that you pass the provider a bit more directly. This is still pretty painless; either of these will work: 22 | 23 | ```c# 24 | var db = Database.Create("connectionString"); 25 | var db = Database.Create(() => new OracleConnection("connectionString")); 26 | ``` 27 | 28 | One case where the transition to AsyncPoco might be less straightforward is the `Query` method. In PetaPoco, `Query` (and its various overloads) returns `IEnumerable`, and its implementation `yield return`s POCOs as it streams results from the underlying DataReader. But AsyncPoco's `QueryAsync` methods do not return `Task>`. The reason is that if you `await` a method with that signature, you will not have results to work with until the `Task` completes, meaning all results are pulled into memory, at which point you may as well `Fetch` a `List`. Ideally, you want to be able to process the results asynchronously *as they become available*. So instead of returning a result that can be enumerated, `QueryAsync` accepts a callback that is invoked for each POCO in the result set as it becomes available. 29 | 30 | ```C# 31 | await db.QueryAsync
("SELECT * FROM articles", a => 32 | { 33 | Console.WriteLine("{0} - {1}", a.article_id, a.title); 34 | }); 35 | ``` 36 | 37 | What if you want to stop processing results before you reach the end of the DataReader's stream? There is a set of `QueryAsync` overloads that take a `Func` callback; simply return `false` from the callback to hault the iteration immediately and close/dispose the `DataReader`. 38 | 39 | ```C# 40 | await db.QueryAsync
("SELECT * FROM articles", a => 41 | { 42 | if (IsWhatIWant(a)) 43 | { 44 | Console.WriteLine("Found it! {0} - {1}", a.article_id, a.title); 45 | return false; // stop iterating and close/dispose the DataReader 46 | } 47 | else 48 | { 49 | return true; // continue iterating 50 | } 51 | }); 52 | ``` 53 | 54 | ## What databases are supported? 55 | 56 | AsyncPoco supports the following database platforms: 57 | 58 | - SQL Server 59 | - Oracle 60 | - MySQL 61 | - PostgreSQL 62 | - SQLite 63 | - SQL Server CE 64 | 65 | ## What flavors of .NET are supported? 66 | 67 | AsyncPoco targets full .NET Framework as well as .NET Standard 1.3 and 2.0, meaning it will run on the following platforms: 68 | 69 | - .NET Framework 4.5 and above 70 | - .NET Core 1.0 and 2.0 71 | - Mono 72 | - Xamarin.iOS 73 | - Xamarin.Mac 74 | - Xamarin.Android 75 | - UWP (Windows 10) 76 | 77 | ## Is it faster than PetaPoco? 78 | 79 | No. But that's not the point of asynchronous code. The point is to free up threads while waiting on I/O-bound work to complete, making desktop and mobile apps more responsive and web applications more scalable. 80 | 81 | ## Why *shouldn't* I switch from PetaPoco? 82 | 83 | Once you start converting synchronous code to async, it's said to spread like a zombie virus, meaning that if you're dealing with a large codebase, be prepared to make a substantial number of changes. If don't have the time or resources needed for this commitment, AsyncPoco is probably not a good fit. Going only partially async is an [invitation for deadlocks](http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). A good rule of thumb is if you've used `.Wait()` or `.Result` anywhere in your code (other than perhaps the `Main` method of a console app), you've done something wrong. You need to either use async all the way up and down your call stack, or not at all. 84 | 85 | ## Where do I get it? 86 | 87 | AsyncPoco is available via [NuGet](https://www.nuget.org/packages/AsyncPoco/): 88 | 89 | `PM> Install-Package AsyncPoco` 90 | 91 | ## How do I get help? 92 | 93 | - Ask specific programming questions on [Stack Overflow](http://stackoverflow.com/questions/ask?tags=asyncpoco+c%23+orm+micro-orm+async-await). I'll answer personally (unless someone beats me to it). 94 | - For announcements and (light) discussions, follow [@AsyncPoco](https://twitter.com/AsyncPoco) on Twitter. 95 | - To report bugs or suggest improvements, no matter how opinionated, [create an issue](https://github.com/tmenier/AsyncPoco/issues/new). 96 | 97 | ## How can I contribute? 98 | 99 | I'll gladly accept pull requests that address issues and implement cool features, although I generally prefer that you [create an issue](https://github.com/tmenier/AsyncPoco/issues/new) first so we can discuss the specifics. I'd also be grateful for your help spreading the word via [Twitter](https://twitter.com/intent/tweet?text=Check%20out%20AsyncPoco!&tw_p=tweetbutton&url=https%3A%2F%2Fgithub.com%2Ftmenier%2FAsyncPoco), blog posts, etc. 100 | 101 | ## Credit where credit is due 102 | 103 | Well over 90% of this code is the brainchild of Brad Robinson ([@toptensoftware](https://twitter.com/toptensoftware)); I'm merely riding the coattails of [PetaPoco](http://www.toptensoftware.com/petapoco)'s success. Brad in turn credits Rob Conery ([@robconery](https://twitter.com/robconery)) for original inspiration (ie: [Massive](https://github.com/robconery/massive)), Rob Sullivan ([@DataChomp](https://twitter.com/DataChomp)) for hard core DBA advice, and Adam Schroder ([@schotime](https://twitter.com/schotime)) for lots of suggestions, improvements and Oracle support. 104 | --------------------------------------------------------------------------------