Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Desktop Developer

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Simple.Data and complex types: many to oneMore Simple.Data with VB.Net: adding fields and tables »
    comments

    So yesterday Chrissie and I did posts on Simple.Data and PetaPoco. Today he followed up with more complex examples, including keys and multiple table queries.

    PetaPoco is built specifically with primary keys as a first class citizen, so it will be interesting to see how it compares.

    Adding a column

    Like Simple.Data, adding a column to our database table is no problem at all. First lets add the column to our database, then we'll look at how that affects both our existing code and an updated POCO with a matching field.

    Like before, I'll use the more concise syntax of PetaPoco to add the column instead of the standard ADO logic:

    1. private void CreateTable() {
    2.     using (var db = new Database("DataSource=\"test.sdf\"; Password=\"chrissiespassword\"", "System.Data.SqlServerCe.4.0")) {
    3.         db.Execute("CREATE TABLE Person (Id int IDENTITY(1,1) PRIMARY KEY, LastName nvarchar (40) NOT NULL, FirstName nvarchar (40));");
    4.     }
    5. }

    After adding this additional column, we can still use the original POCO as PetaPoco will map the columns that are present in the POCO without complaining about leftovers. In the case where we allowed PetaPoco to build the query for us, we get more concise SQL that only queries for the columns with matching properties in that smaller POCO class:

    1. // SQL = SELECT [Person].[LastName], [Person].[FirstName] FROM [Person] WHERE lastname=@0
    2. private void SelectDecoratedRecords() {
    3.     using (var db = GetDatabase()) {
    4.         var results = db.Query<DecoratedPerson>("WHERE lastname=@0", "lastname1");
    5.     }
    6. }

    Now let's add the new column to both our raw POCO and the decorated POCO:

    1. public class Person {
    2.         public int Id { get; set; }
    3.         public string LastName { get; set; }
    4.         public string FirstName { get; set; }
    5.  
    6.         public override string ToString() {
    7.             return String.Format("{0}: {1}, {2}", Id, LastName, FirstName);
    8.         }
    9.     }
    10.  
    11.     [TableName("Person")]
    12.     [PrimaryKey("Id",autoIncrement=true)]
    13.     public class DecoratedPerson : Person { }

    Without changing of the logic, the function from above will populate the extra column in our non-decorated POCO and the SQL generated by the short query version above will now include the Id column:

    1. //SQL: SELECT * FROM Person WHERE lastname=@0
    2. private void SelectRecords() {
    3.     using (var db = GetDatabase()) {
    4.         var results = db.Query<Person>("SELECT * FROM Person WHERE lastname=@0", "lastname1");
    5.     }
    6. }
    7.  
    8. //SQL: SELECT [Person].[Id], [Person].[LastName], [Person].[FirstName] FROM [Person] WHERE lastname=@0
    9. private void SelectDecoratedRecords() {
    10.     using (var db = GetDatabase()) {
    11.         var results = db.Query<DecoratedPerson>("WHERE lastname=@0", "lastname1");
    12.     }
    13. }

    Yesterdays third insert option, using an undecorated object and counting on reflection to match up columns to properties, will fail now because it will attempt to insert a value into that ID field, but using it in a select would still work. The first option, where we supplied the name of the table, will have to be updated to also supply the name of the ID and a boolean to indicate that it is autoincrementing. The second option, inserting the decorated object, requires no changes at all.

    Adding a Table

    Following Chrissie's lead, lets add an address table and an undeclared foreign key relationship from the Person table (I have to tease him about something).

    1. private void CreateTables() {
    2.     using (var db = new Database("DataSource=\"test.sdf\"; Password=\"chrissiespassword\"", "System.Data.SqlServerCe.4.0")) {
    3.         db.Execute("CREATE TABLE Person (Id int IDENTITY(1,1) PRIMARY KEY, LastName nvarchar (40) NOT NULL, FirstName nvarchar (40), AddressId int NOT NULL);");
    4.         db.Execute("CREATE TABLE Address (Id int IDENTITY(1,1) PRIMARY KEY, Street nvarchar (40) NOT NULL, HouseNumber nvarchar (10));");
    5.     }
    6. }

    And I will add an additional Person POCO to reflect the new column, as well as an Address POCO to reflect the new table. The simpler query logic is addictive, so I've decorated both POCOs (I'll explain the ResultColumn later):

    1. [TableName("Person")]
    2. [PrimaryKey("Id", autoIncrement = true)]
    3. public class Person {
    4.     public int Id { get; set; }
    5.     public string LastName { get; set; }
    6.     public string FirstName { get; set; }
    7.     public int AddressId { get; set; }
    8.     [ResultColumn] public Address Address { get; set; }
    9.  
    10.     public override string ToString() {
    11.         return String.Format("{0}: {1}, {2}", Id, LastName, FirstName);
    12.     }
    13. }
    14.  
    15. [TableName("Address")]
    16. [PrimaryKey("Id", autoIncrement = true)]
    17. public class Address {
    18.     public int Id { get; set; }
    19.     public string Street { get; set; }
    20.     public string HouseNumber { get; set; }
    21. }

    Following Chrissie's lead, I'll query for the related records separately first:

    1. public void QuerySeperately() {
    2.     // already called CreateDatabase()
    3.     // already called CreateTables()
    4.     using (var db = GetDatabase()) {
    5.         db.Insert(new Address() { Street = "street1", HouseNumber = "1" });
    6.         db.Insert(new Person() { LastName = "lastname1", FirstName = "firstname1", AddressId = 1 });
    7.         db.Insert(new Person() { LastName = "lastname2", FirstName = "firstname2", AddressId = 1 });
    8.  
    9.         var results = db.Query<Person>("WHERE LastName=@0", "lastname1");
    10.         foreach (var person in results) {
    11.             Console.WriteLine("Person: {0} {1} {2}", person.Id, person.LastName, person.FirstName);
    12.             var address = db.Single<Address>("Where Id=@0", person.AddressId);
    13.             Console.WriteLine("Address: {0} {1}", address.Street, address.HouseNumber);
    14.         }
    15.  
    16.         int count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person WHERE LastName=@0", "lastname1");
    17.         Console.WriteLine("Count: " + count.ToString());
    18.     }
    19. }

    As he pointed out, this method doesn't perform well. We can replace this with a single query using the Multi-POCO support.

    1. public void QueryMultiStyle() {
    2.     // already called CreateDatabase()
    3.     // already called CreateTables()
    4.     using (var db = GetDatabase()) {
    5.         db.Insert(new Address() { Street = "street1", HouseNumber = "1" });
    6.         db.Insert(new Person() { LastName = "lastname1", FirstName = "firstname1", AddressId = 1 });
    7.         db.Insert(new Person() { LastName = "lastname1", FirstName = "firstname2", AddressId = 1 });
    8.  
    9.         var results = db.Query<Person, Address>(@"
    10.                               SELECT Person.*, Address.*
    11.                               FROM Person
    12.                                 INNER JOIN Address ON Person.AddressId = Address.Id
    13.                               WHERE Person.lastname=@0", "lastname1");
    14.         foreach (var person in results) {
    15.             Console.WriteLine("Person: {0} {1}", person.LastName, person.FirstName);
    16.             Console.WriteLine("Address: {0} {1}", person.Address.Street, person.Address.HouseNumber);
    17.         }
    18.     }
    19. }

    PetaPoco has the ability to map the results of JOINs to several objects, but it's kind of tricky. The simplest method is to return the fields in the same order as the generic object list. What PetaPoco then does is attempt to process each column in the result from left to right, moving to the next object in line when it reaches a column that doesn't exist in the first or has already been populated. So in this case, because both of the tables and POCOs have an "Id", when the result set reaches the second id it makes the logical conclusion that it is time to start mapping the Address object. PetaPoco uses type detection in the Person object to locate a property to assign the Address instance to.

    There is also more extensive capabilities available to use lambdas to manage the multi-POCO mapping on our own, and if we wanted to we could easily define a single POCO object that had all the necessary fields for both tables. Logic for One-to-many joins is more complex and I haven't had time to dig fully into the intricacies yet.

    The last trick was the ResultColumn attribute I used above. By default PetaPoco assumes that all of the properties in our POCO are going to be inserted into the database. ResultColumn properties are ignored for inserts and updates, but can still be selected into. In this case I'm using it to have PetaPoco ignore the column, but the real purpose would be to allow me to return an additional calculated column, aggregate, or other value that wouldn't have meaning in an INSERT or UPDATE.

    Note: There is an Ignore attribute that would have worked just as well and been a better fit, but then I wouldn't have had a chance to talk about the ResultColumn :)

    Conclusion

    So there we go. We can add auto-incrementing IDs very easily, PetaPoco is smart enough to map partial objects, and there is some really neat stuff available for multi-POCO joins. I've continued to update the github repository, so feel free to grab a copy of the code and play around with yourself.

    About the Author

    User bio imageEli delivers software and technology solutions for a living. His roles have included lone developer, accidental DBA, team lead, and even unintentional Solaris consultant once. With experience in adhoc, Lean, and Agile environments across NSF grants, SaaS products, and in-house IT groups, he is just as willing to chat about the principles of Lean or Continuous Delivery as he is to dive into Azure, SQL Server, or the last ATDD project he created.
    Social SitingsTwitterLinkedInHomePagedeliciousLTD RSS Feed
    InstapaperVote on HN

    5 comments

    Comment from: Christiaan Baes (chrissie1) [Member]
    Christiaan Baes (chrissie1) "play around with yourself"

    Dude, really ?
    05/02/12 @ 05:55
    Comment from: Eli Weinstock-Herman (tarwn) [Member]
    Eli Weinstock-Herman (tarwn) You have to read it in the right context, in this case the context where you are writing your posts while on a pleasant vacation while I wrote that line about 5 minutes before bedtime last night at the end of a long line of busy workdays :)

    Out of curiosity, how does Simple.Data do with stored procs?
    05/02/12 @ 06:13
    Comment from: Christiaan Baes (chrissie1) [Member]
    Christiaan Baes (chrissie1) Simple.data works with Stored procedures. I'll do that next ;-).
    05/02/12 @ 06:33
    Comment from: Mark Rendle [Visitor] Email · http://github.com/markrendle/simple.data
    Mark Rendle Of course, once you start decorating your classes and properties with attributes defined in your ORM, they're not POCOs any more. :P

    I think Christiaan should do a post on Simple.Data's With methods:

    db.Persons.All().WithAddress();
    05/03/12 @ 04:13
    Comment from: Christiaan Baes (chrissie1) [Member]
    Christiaan Baes (chrissie1) Mark, it was next on my list.
    05/03/12 @ 15:03

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)