Skip to content

Commit

Permalink
Adds documentation for Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
pleb committed Jul 31, 2016
1 parent 7dcd1da commit a7dcfe3
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 1 deletion.
4 changes: 3 additions & 1 deletion PetaPoco.Tests.Integration/Documentation/Inserts.cs
Expand Up @@ -6,6 +6,7 @@


using System; using System;
using System.Linq; using System.Linq;
using PetaPoco.Core;
using PetaPoco.Core.Inflection; using PetaPoco.Core.Inflection;
using PetaPoco.Tests.Integration.Databases; using PetaPoco.Tests.Integration.Databases;
using PetaPoco.Tests.Integration.Databases.MSSQL; using PetaPoco.Tests.Integration.Databases.MSSQL;
Expand All @@ -20,6 +21,7 @@ public class Inserts : BaseDatabase
public Inserts() public Inserts()
: base(new MssqlDBTestProvider()) : base(new MssqlDBTestProvider())
{ {
PocoData.FlushCaches();
} }


[Fact] [Fact]
Expand Down Expand Up @@ -137,7 +139,7 @@ public void InsertUnconventionalPoco()
)"); )");


// This POCO is unconventional because, when using the default conventional mapper, PetaPoco won't understand how this poco maps to the database. // This POCO is unconventional because, when using the default conventional mapper, PetaPoco won't understand how this poco maps to the database.
// To understand the power how the conventional mapper, one could configure to work in this situation. // To understand the power of unconventional mapping, a developer could configure it to work in this situation.
var poco = new UnconventionalPoco { Text = "PetaPoco" }; var poco = new UnconventionalPoco { Text = "PetaPoco" };


// Insert the poco // Insert the poco
Expand Down
305 changes: 305 additions & 0 deletions PetaPoco.Tests.Integration/Documentation/Updates.cs
@@ -0,0 +1,305 @@
// <copyright company="PetaPoco - CollaboratingPlatypus">
// Apache License, Version 2.0 https://github.com/CollaboratingPlatypus/PetaPoco/blob/master/LICENSE.txt
// </copyright>
// <author>PetaPoco - CollaboratingPlatypus</author>
// <date>2015/12/13</date>

using System;
using System.Linq;
using PetaPoco.Core;
using PetaPoco.Core.Inflection;
using PetaPoco.Tests.Integration.Databases;
using PetaPoco.Tests.Integration.Databases.MSSQL;
using Shouldly;
using Xunit;

namespace PetaPoco.Tests.Integration.Documentation
{
[Collection("MssqlTests")]
public class Updates : BaseDatabase
{
public Updates()
: base(new MssqlDBTestProvider())
{
PocoData.FlushCaches();
}

[Fact]
public void Update()
{
// Create and insert the person
var person = new Person { Id = Guid.NewGuid(), Name = "PetaPoco", Dob = new DateTime(2011, 1, 1), Age = (DateTime.Now.Year - 2011), Height = 242 };
var id = DB.Insert(person);

// Update a few properties of the person
person.Age = 70;
person.Name = "The PetaPoco";

// Tell PetaPoco to update the DB
DB.Update(person);

// Get a clone/copy from the DB
var clone = DB.Single<Person>(id);

// See, the person has been updated
clone.Id.ShouldBe(person.Id);
clone.Dob.ShouldBe(person.Dob);
clone.Height.ShouldBe(person.Height);
clone.Age.ShouldBe(person.Age);
clone.Name.ShouldBe(person.Name);
}

[Fact]
public void UpdatePartial()
{
// Create and insert the person
var person = new Person { Id = Guid.NewGuid(), Name = "PetaPoco", Dob = new DateTime(2011, 1, 1), Age = (DateTime.Now.Year - 2011), Height = 242 };
var id = DB.Insert(person);

// Update a few properties of the person
person.Age = 70;
person.Name = "The PetaPoco";

// Get the poco data
var pocoData = PocoData.ForType(person.GetType(), DB.DefaultMapper);

// Tell PetaPoco to update only ther person's name
// The update statement produced is `UPDATE [People] SET [FullName] = @0 WHERE [Id] = @1`
DB.Update(person, new [] { pocoData.GetColumnName(nameof(Person.Name)) });

// Get a clone/copy from the DB
var clone = DB.Single<Person>(id);

// See, the person has been updated, but only the name
clone.Id.ShouldBe(person.Id);
clone.Dob.ShouldBe(person.Dob);
clone.Height.ShouldBe(person.Height);

clone.Age.ShouldNotBe(70);
clone.Name.ShouldBe("The PetaPoco");
}

[Fact]
public void UpdateToDifferentTable()
{
// Create the and insert the person
var person = new Person { Id = Guid.NewGuid(), Name = "PetaPoco", Dob = new DateTime(2011, 1, 1), Age = (DateTime.Now.Year - 2011), Height = 242 };
var id = DB.Insert("SpecificPeople", "Id", person);

// Update a few properties of the person
person.Age = 70;
person.Name = "The PetaPoco";

// Tell PetaPoco to update the DB table SpecificPeople
// The update statement produced is `UPDATE [SpecificPeople] SET [FullName] = @0, [Age] = @1, [Height] = @2, [Dob] = @3 WHERE [Id] = @4`
DB.Update("SpecificPeople", "Id", person);

// We need to get the clone/copy from the correct table
// Note: we can't use auto select builder here because PetaPoco would create columns such as People.Id
var clone = DB.Query<Person>("SELECT * FROM [SpecificPeople] sp WHERE sp.[Id] = @0", id).Single();

// See, the person has been updated
clone.Id.ShouldBe(person.Id);
clone.Dob.ShouldBe(person.Dob);
clone.Height.ShouldBe(person.Height);
clone.Age.ShouldBe(person.Age);
clone.Name.ShouldBe(person.Name);
}

[Fact]
public void UpdateConventionalPoco()
{
// Clear out any notes and reset the ID sequence counter
DB.Execute("TRUNCATE TABLE [Note]");

// Insert some notes using all APIs
var note1 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) };
var note2 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) };
var note3 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) };
var note4 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) };
var note5 = new Note { Text = "PetaPoco's note", CreatedOn = new DateTime(1948, 1, 11, 4, 2, 4, DateTimeKind.Utc) };

// Each of the API usuages here are effectively the same, as PetaPoco is providing the correct unknown values.
// This is because the poco has been mapped by convention and therefore PetaPoco understands how to do this.
DB.Insert(note1);
DB.Insert(note2);
DB.Insert(note3);
DB.Insert(note4);
DB.Insert(note5);

//Update the notes
note1.Text += " some more text";
note2.Text += " some more text";
note3.Text += " some more text";
note4.Text += " some more text";
note5.Text += " some more text";

// Get the poco data
var pocoData = PocoData.ForType(typeof(Note), DB.DefaultMapper);

// Update all notes using all APIs
DB.Update(note1);
DB.Update(note2, note2.Id);
DB.Update(note3, note3.Id, pocoData.UpdateColumns);
var sql1 = $"SET {DB.Provider.EscapeSqlIdentifier(pocoData.GetColumnName(nameof(Note.Text)))} = @1 " +
$"WHERE {DB.Provider.EscapeSqlIdentifier(pocoData.TableInfo.PrimaryKey)} = @0";
DB.Update<Note>(sql1, note4.Id, note4.Text);
var sql2 = new Sql($"SET {DB.Provider.EscapeSqlIdentifier(pocoData.GetColumnName(nameof(Note.Text)))} = @1 " +
$"WHERE {DB.Provider.EscapeSqlIdentifier(pocoData.TableInfo.PrimaryKey)} = @0", note5.Id, note5.Text);
DB.Update<Note>(sql2);

// Just to be sure
DB.ExecuteScalar<int>("SELECT COUNT(*) FROM [Note] WHERE CAST(Text AS NVARCHAR(MAX)) = @0", "PetaPoco's note some more text").ShouldBe(5);
}

[Fact]
public void UpdateUnconventionalPoco()
{
// Create the UnconventionalPocos table
DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'TBL_UnconventionalPocos')
DROP TABLE dbo.[TBL_UnconventionalPocos]
CREATE TABLE dbo.[TBL_UnconventionalPocos] (
[PrimaryKey] INT IDENTITY(1,1) PRIMARY KEY,
[Text] NTEXT NOT NULL
)");

// This POCO is unconventional because, when using the default conventional mapper, PetaPoco won't understand how this poco maps to the database.
// To understand the power of unconventional mapping, a developer could configure it to work in this situation.
var poco = new UnconventionalPoco { Text = "PetaPoco" };

// Insert the poco
var id = DB.Insert("TBL_UnconventionalPocos", "PrimaryKey", true, poco);

// Update the poco
poco.Text += " some more text";
DB.Update("TBL_UnconventionalPocos", "PrimaryKey", poco);

// Get a clone/copy from the DB
var clone = DB.Query<UnconventionalPoco>("SELECT * FROM [TBL_UnconventionalPocos] WHERE [PrimaryKey] = @0", id).Single();

// Just to be sure
poco.PrimaryKey.ShouldBe(clone.PrimaryKey);
poco.Text.ShouldBe(clone.Text);
}

[Fact]
public void UpdateConventionalUnconventionalPoco()
{
// Create the UnconventionalPocos table
DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'TBL_UnconventionalPocos')
DROP TABLE dbo.[TBL_UnconventionalPocos]
CREATE TABLE dbo.[TBL_UnconventionalPocos] (
[PrimaryKey] INT IDENTITY(1,1) PRIMARY KEY,
[Text] NTEXT NOT NULL
)");

// Reconfigure the convention mapper
// Note: I can't think of a valid reason, other than for a purpose such as this, where you would configure the convention mapper in this way.
((ConventionMapper) DB.DefaultMapper).MapPrimaryKey = (ti, t) =>
{
var prop = t.GetProperties().FirstOrDefault(p => p.Name == "PrimaryKey");
if (prop == null)
return false;
ti.PrimaryKey = prop.Name;
ti.AutoIncrement = ((ConventionMapper)DB.DefaultMapper).IsPrimaryKeyAutoIncrement(prop.PropertyType);
return true;
};
((ConventionMapper) DB.DefaultMapper).InflectTableName = (i, tn) => "TBL_" + tn + "s";

// Create the POCO
var poco = new UnconventionalPoco { Text = "PetaPoco" };

// Tell PetaPoco to insert it
var id = DB.Insert(poco);

// Get a clone/copy from the DB
var clone = DB.SingleOrDefault<UnconventionalPoco>(id);

// See, they're are the same
clone.ShouldBe(poco);

// Update the original poco
poco.Text += " some more text";

// Update the poco
DB.Update(poco);

// Get the clone from teh database again
clone = DB.SingleOrDefault<UnconventionalPoco>(id);

// Confirm the text was updated
clone.Text.ShouldBe("PetaPoco some more text");
}

[Fact]
public void UpdateAnonymousPocoWithConventionalNaming()
{
// Create the table for our unknown but conventional POCO
DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'XFiles')
DROP TABLE dbo.[XFiles]
CREATE TABLE dbo.[XFiles] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[FileName] VARCHAR(255) NOT NULL
)");

// Anonymous type are friend of PetaPoco
var xfile = new { FileName = "Agent Mulder.sec" };

// Tell PetaPoco to insert it
var id = DB.Insert("XFiles", "Id", true, xfile);

// Update the poco
xfile = new { FileName = "Agent Mulder.sec" };

// Update the database
DB.Update("XFiles", "Id", xfile);

// Get a clone/copy from the DB
// Note: Check out the name parameters - cool eh?
var clone = DB.Query<dynamic>("SELECT * FROM [XFiles] WHERE [Id] = @Id", new { Id = id }).Single();

// See, they're are the same
id.ShouldBe((int)clone.Id);
xfile.FileName.ShouldBe((string)clone.FileName);
}

[Fact]
public void InsertDynamicUnknownPocoWithConventionalNaming()
{
// Create the table for our unknown but conventional POCO
DB.Execute(@"IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_SCHEMA = 'dbo' AND t.TABLE_NAME = 'XFiles')
DROP TABLE dbo.[XFiles]
CREATE TABLE dbo.[XFiles] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[FileName] VARCHAR(255) NOT NULL
)");

// Dynamics type are friend of PetaPoco
dynamic xfile = new System.Dynamic.ExpandoObject();
xfile.FileName = "Agent Mulder.sec";

// Tell PetaPoco to insert it
var id = DB.Insert("XFiles", "Id", true, (object) xfile);

// Update the poco
xfile.FileName = "Agent Mulder.sec" ;

// Update the database
DB.Update("XFiles", "Id", (object) xfile);

// Get a clone/copy from the DB
// Note: Check out the name parameters - cool eh?
var clone = DB.Query<dynamic>("SELECT * FROM [XFiles] WHERE [Id] = @Id", new { Id = id }).Single();

// See, they're are the same
id.ShouldBe((int)clone.Id);
((string)xfile.FileName).ShouldBe((string)clone.FileName);
}
}
}
Expand Up @@ -179,6 +179,7 @@
<Compile Include="Databases\Sqlite\SqliteExecuteTests.cs" /> <Compile Include="Databases\Sqlite\SqliteExecuteTests.cs" />
<Compile Include="Databases\Sqlite\SqliteInsertTests.cs" /> <Compile Include="Databases\Sqlite\SqliteInsertTests.cs" />
<Compile Include="Databases\Sqlite\SqliteUpdateTests.cs" /> <Compile Include="Databases\Sqlite\SqliteUpdateTests.cs" />
<Compile Include="Documentation\Updates.cs" />
<Compile Include="Documentation\Inserts.cs" /> <Compile Include="Documentation\Inserts.cs" />
<Compile Include="Models\Note.cs" /> <Compile Include="Models\Note.cs" />
<Compile Include="Models\NoteNullablePrimary.cs" /> <Compile Include="Models\NoteNullablePrimary.cs" />
Expand Down
12 changes: 12 additions & 0 deletions PetaPoco/Core/PocoData.cs
Expand Up @@ -27,6 +27,13 @@ public class PocoData
private Cache<Tuple<string, string, int, int>, Delegate> PocoFactories = new Cache<Tuple<string, string, int, int>, Delegate>(); private Cache<Tuple<string, string, int, int>, Delegate> PocoFactories = new Cache<Tuple<string, string, int, int>, Delegate>();
public Type Type; public Type Type;
public string[] QueryColumns { get; private set; } public string[] QueryColumns { get; private set; }

public string[] UpdateColumns
{
// No need to cache as it's not used by PetaPoco internally
get { return (from c in Columns where !c.Value.ResultColumn && c.Value.ColumnName != TableInfo.PrimaryKey select c.Key).ToArray(); }
}

public TableInfo TableInfo { get; private set; } public TableInfo TableInfo { get; private set; }
public Dictionary<string, PocoColumn> Columns { get; private set; } public Dictionary<string, PocoColumn> Columns { get; private set; }


Expand Down Expand Up @@ -390,5 +397,10 @@ internal static void FlushCaches()
{ {
_pocoDatas.Flush(); _pocoDatas.Flush();
} }

public string GetColumnName(string propertyName)
{
return Columns.Values.First(c => c.PropertyInfo.Name.Equals(propertyName)).ColumnName;
}
} }
} }

0 comments on commit a7dcfe3

Please sign in to comment.