Wednesday, August 29, 2012

Multiple inheritance with C#

It's not possible to do multiple inheritance in C#

public partial class A : FirstConcreteClass, SecondConcreteClass
{
}

public class FirstConcreteClass
{
 public void Hello()
 {
  Console.WriteLine ("Hello");
 }
}


public class SecondConcreteClass
{
 public void World()
 {
  Console.WriteLine ("World");
 }

 public string Yo()
 {
  return "POCO";
 }
}


But what you can possibly do is to isolate the SecondConcreteClass' APIs to an interface and implement it separately:


public class SecondConcreteClass : ISecondConcreteClass
{
 public void World()
 {
  Console.WriteLine ("World");
 }

 public string Yo()
 {
  return "POCO";
 }
}

public interface ISecondConcreteClass
{
 void World();
 string Yo();
}

Then change class A to this implementation, note the partial keyword and the implementation of ISecondConcreteClass:

public partial class A : FirstConcreteClass, ISecondConcreteClass
{
 public A()
 {
    this.SC = new SecondConcreteClass();
 }
}


The second piece of the puzzle. Put the stub class for ISecondConcreteClass implementation on a partial class. The stub class merely pass-the-buck the actions to the instance of ISecondConcreteClass.

public partial class A 
{
 ISecondConcreteClass SC { get; set; }
 
 public void World() { SC.World(); }
 public string Yo() { return SC.Yo(); }
}


To use:

var a = new A();
a.Hello();

a.World();
Console.WriteLine (a.Yo ());


Output:
Hello
World
POCO


The gist is, if you have a class(say B) that already extends another class and yet it need to extend another concrete class, just make that class B implement the other concrete's interface instead, and instantiate an object for the interface


public partial class B : WhateverClassThatMaybe, ISecondConcreteClass
{
 public B()
 {
    // instantiate SecondConcreteClass
    this.SC = new SecondConcreteClass();
 }
}


To shift the burden of manually coding the partial stub classes from the developer to the computer, use T4 (Text Template Transformation Toolkit) to automate the generation of those partial stub classes, that's how useful the computer is ツ


<#@ template language="C#v3.5" #>
  
  
<# var a = new[] { "A", "B" }; #>

namespace TestMultipleInheritance
{

<# foreach(string s in a) { #>

 public partial class <#=s#> 
 {
  ISecondConcreteClass SC { get; set; }
  
  public void World() { SC.World(); }
  public string Yo() { return SC.Yo(); }
 }


<# } #>


}


An aside, I'm using Mono .NET Framework version 4.0 and can use C# 4 features(e.g. dynamic), though for some reasons unknown to me, Mono's templating language is C# 3.5


The topic of OOP and inheritance is not complete without the capability to polymorph the object's functionality. So how can we achieve polymorphism with a simulated concrete class? In a normal class or abstract class, you have a virtual or abstract method that you can override. With multiple inheritance simulation using interface implementation, just implement the interface the usual way, so don't include the class(class C in following example) that polymorphs its concrete base class(simulated concrete base class ISecondConcreteClass) in T4:


public partial class C 
{
 ISecondConcreteClass SC { get; set; }
 
 public void World() // think of this as an overriding function? Well it is
 { 
  SC.World();  // think of SC.World() as base.World();

  System.Console.WriteLine ("Long Live!");
 }
 public string Yo() { return SC.Yo(); }
}

Or better yet, copy-paste existing class stub to new partial class file, then delete string "C" from T4's iterated string array, T4 will automatically re-generate A and B class, and shall not generate C class. Then customize your overrides in that partial C class file.


If you want the World method to be overridable further down the inheritance chain, just add the virtual keyword to the method signature:


public partial class C 
{
 ISecondConcreteClass SC { get; set; }
 
 // virtual will make the World overridable down the inheritance chain
 public virtual void World() 
 { 
  SC.World();  // think of SC.World() as base.World();

  System.Console.WriteLine ("Long Live!");
 }
 public string Yo() { return SC.Yo(); }
}



That's multiple inheritance for us C# folks!


Happy Computing! ツ

Saturday, August 25, 2012

Eager-loading deep object graph from repository object

If your repository component don't have an abstraction layer for eager-loading deep object graph, your ORM will spill to your controller. Caveat: just detect the provider, so your MVC app is still unit-testing-friendly


public class SomeController : Controller
{
    const string EFIndicator = "System.Data.Entity.Internal.Linq.DbQueryProvider";

    IQueryable<Question> _q = null;

    public SomeController (IQueryable<Question> q)
    {
        _q = q;
    }
 
    public ViewResult ShowEverything(int id)
    {

        Question q = null;
        var q1 = _q.Where(x => x.QuestionId == id);


        if (_q.Provider.ToString() == EFIndicator)
        {
            var backToEf = (System.Data.Entity.Infrastructure.DbQuery<Question>)q1;

            q = backToEf
            .Include("AskedBy")
            .Include("QuestionModifiedBy")

            .Include("QuestionComments")

            .Include("Answers")
                .Include("Answers.AnswerComments")

            .Single();
        }

        return View(q);

    }

}


And you have to anticipate different eager-loading mechanism based on the IQueryable ORM provider too, e.g. NHibernate:


public ViewResult ShowEverything(int id)
{


    Question q = null;
    var q1 = _q.Where(x => x.QuestionId == id);



    if (_q.Provider.ToString() == EFIndicator)
    {

        var backToEf = (System.Data.Entity.Infrastructure.DbQuery<question>)q1;

        q = backToEf
        .Include("AskedBy")
        .Include("QuestionModifiedBy")

        .Include("QuestionComments")

        .Include("Answers")
            .Include("Answers.AnswerComments")

        .Single();

    }
    else if (_q.Provider is NHibernate.Linq.DefaultQueryProvider)
    {
        q1
        .FetchMany(x => x.QuestionComments)
        .ToFuture();


        q1
        .Fetch(x => x.AskedBy)
        .Fetch(x => x.QuestionModifiedBy)
        .FetchMany(x => x.Answers)
        .ToFuture();


        //sess.Query<answer>()
        //.Where(x => x.Question.QuestionId == id)
        //.FetchMany(x => x.AnswerComments)
        //.ToFuture();

        q = q1.ToFuture().Single();
    }
    else
    {
        // Repository pattern is unit-testing friendly code ツ
        q = q1.ToSingle();
    }


    return View(q);
   
}




You'll notice in the code above that we cannot eager-load NHibernate's AnswerComments. So you have to pass the Answer repository too. An argument can be made that NHibernate can leak repository abstractions. Following is the repository pattern-using code that supports NHibernate eager-loading mechanism. Though if you have a good dependency injection component, you don't have to worry about changes, laugh in the face of changes when you use empowering tools.


public class SomeController : Controller
{
     const string EFIndicator = "System.Data.Entity.Internal.Linq.DbQueryProvider";


     IQueryable<Question> _q = null;
     IQueryable<Answer> _a = null;

     public SomeController (IQueryable<Question> q, IQueryable<Answer> a)
     {
      _q = q;
      _a = a;
     }
     
    public ViewResult ShowEverything(int id)
    {

        Question q = null;
        var q1 = _q.Where(x => x.QuestionId == id);



        if (_q.Provider.ToString() == EFIndicator)
        {

            var backToEf = (System.Data.Entity.Infrastructure.DbQuery<Question>)q1;

            q = backToEf
                .Include("AskedBy")
                .Include("QuestionModifiedBy")

                .Include("QuestionComments")

                .Include("Answers")
                .Include("Answers.AnswerComments")


            .Single();

        }
        else if (_q.Provider is NHibernate.Linq.DefaultQueryProvider)
        {
            q1
            .FetchMany(x => x.QuestionComments)
            .ToFuture();


            q1
            .Fetch(x => x.AskedBy)
            .Fetch(x => x.QuestionModifiedBy)
            .FetchMany(x => x.Answers)
            .ToFuture();


            _a
            .Where(x => x.Question.QuestionId == id)
            .FetchMany(x => x.AnswerComments)
            .ToFuture();

            q = q1.ToFuture().Single();
        }
        else {} // Very unit-testing-friendly :-)

        q.QuestionText = q.QuestionText + "?";

        return View(q);

        }
    }


Sample unit test:
// Arrange
int id = 1;
string qt = "Answer to life and everything";

var questions = new List<Question> { new Question{ QuestionId = id, QuestionText = qt } }.AsQueryable();
var answers = new List<Answers> { new Answer{ Question = questions[0], AnswerText = "4" } }.AsQueryable();

// Act
Question q = (Question) new SomeController( questions, answers).ShowEverything(id);

// Assert
Assert.AreEqual(qt + "?", q.QuestionText);


Sample integration tests:
using (var db = new EfMapping())
{
        // Arrange
        int id = 1;

        IQueryable<Question> questions = db.Set<Question>();
        IQueryable<Answer> answers = db.Set<Answer>();

        Question qX = db.Set<Question>().Find(id);


        // Act
        Question q = (Question) new SomeController(questions, answers).ShowEverything(id);


        // Assert
        Assert.AreEqual(qX.QuestionText + "?", q.QuestionText);
}

/* for NHibernate:

using (var sess = NhMapping.GetSessionFactory().OpenSession())
{
        // Arrange
        int id = 1;

        IQueryable<Question> questions = sess.Query<Question>();
        IQueryable<Answer> answers = sess.Query<Answer>();

        Question qX = sess.Query<Question>().Load(id);


        // Act
        Question q = (Question) new SomeController(questions, answers).ShowEverything(id);


        // Assert
        Assert.AreEqual(qX.QuestionText + "?", q.QuestionText);
*/


Solving NHibernate ThenFetchMany duplicate rows

What's the problem with NHibernate's ThenFetchMany? Simple answer: It Doesn't Work™

Contrast with Entity Framework's sophisticated(complex?) union approach, NHibernate merely join tables blindly, resulting to unintended duplicate objects, aka cartesian product

So while this work out-of-the-box in Entity Framework. It's debatable though if one would want how Entity Framework is able to seamlessly achieve that feat

static void TestEF()
{
    using (var db = new EfMapping())
    {
        var q = db.Set<Question>()

        .Include("AskedBy")
        .Include("QuestionModifiedBy")

        .Include("QuestionComments")

        .Include("Answers")
            .Include("Answers.AnswerComments")

        .Where(x => x.QuestionId == 1)
        .Single();

        Console.WriteLine("{0}", q.QuestionText);

        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonId);
        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonName);
        Console.ReadKey();
        Console.WriteLine("Question Comments Count should be 2: {0}", q.QuestionComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answers Count should be 3: {0}", q.Answers.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #1 Comments Count should be 5: {0}", q.Answers[0].AnswerComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #2 Comments Count should be 4: {0}", q.Answers[1].AnswerComments.Count());

        Console.ReadLine();

    }
}


This doesn't work in NHibernate:

static void TestNhProb2()
{
    using (var sess = NhMapping.GetSessionFactory().OpenSession())
    {

        var q1 = sess.Query<Question>()
        .Fetch(x => x.AskedBy)
        .Fetch(x => x.QuestionModifiedBy)
        .FetchMany(x => x.Answers)
            .ThenFetchMany(x => x.AnswerComments) // Doesn't work as advertised. This causes duplicate/cartesianed rows
        .FetchMany(x => x.QuestionComments)
        .Where(x => x.QuestionId == 1);


        var q = q1.Single();

        Console.WriteLine("{0}", q.QuestionText);

        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonId);
        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonName);
        Console.ReadKey();
        Console.WriteLine("Question Comments Count should be 2: {0}", q.QuestionComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answers Count should be 3: {0}", q.Answers.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #1 Comments Count should be 5: {0}", q.Answers[0].AnswerComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #2 Comments Count should be 4: {0}", q.Answers[1].AnswerComments.Count());

    }
}



Though you can fix NHibernate eager loading(Fetch,FetchMany) problem with ToFuture, its ThenFetchMany still causes duplicate rows, hence resulting to duplicate objects. In the following code, though QuestionComments will have correct count, the count for Answers and AnswerComments will all be wrong.


static void TestNhProb()
{
    using (var sess = NhMapping.GetSessionFactory().OpenSession())
    {

        var q1 = sess.Query<Question>().Where(x => x.QuestionId == 1);


        q1
        .FetchMany(x => x.QuestionComments)
        .ToFuture();


        q1
        .Fetch(x => x.AskedBy)
        .Fetch(x => x.QuestionModifiedBy)
        .FetchMany(x => x.Answers)
            .ThenFetchMany(x => x.AnswerComments) // Doesn't work as advertised. This causes duplicate/cartesianed rows
        .ToFuture();




        var q = q1.ToFuture().Single();

        Console.WriteLine("{0}", q.QuestionText);

        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonId);
        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonName);
        Console.ReadKey();
        Console.WriteLine("Question Comments Count should be 2: {0}", q.QuestionComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answers Count should be 3: {0}", q.Answers.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #1 Comments Count should be 5: {0}", q.Answers[0].AnswerComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #2 Comments Count should be 4: {0}", q.Answers[1].AnswerComments.Count());

    }
}




To really fix that, you have to do an extra hoops, you need to separate the eager fetching of AnswerComments from the Answer by filtering them from the Answer of the Question.


static void TestNhOk()
{
    using (var sess = NhMapping.GetSessionFactory().OpenSession())
    {
        int id = 1;

        var q1 = sess.Query<Question>().Where(x => x.QuestionId == id);


        q1
        .FetchMany(x => x.QuestionComments)
        .ToFuture();


        q1
        .Fetch(x => x.AskedBy)
        .Fetch(x => x.QuestionModifiedBy)
        .FetchMany(x => x.Answers)                    
        .ToFuture();


        sess.Query<Answer>()                    
        .Where(x => x.Question.QuestionId == id)
        .FetchMany(x => x.AnswerComments)
        .ToFuture();





        var q = q1.ToFuture().Single();

        Console.WriteLine("{0}", q.QuestionText);

        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonId);
        Console.ReadKey();
        Console.WriteLine("{0}", q.AskedBy.PersonName);
        Console.ReadKey();
        Console.WriteLine("Question Comments Count should be 2: {0}", q.QuestionComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answers Count should be 3: {0}", q.Answers.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #1 Comments Count should be 5: {0}", q.Answers[0].AnswerComments.Count());
        Console.ReadKey();
        Console.WriteLine("Answer #2 Comments Count should be 4: {0}", q.Answers[1].AnswerComments.Count());

        
        Console.ReadLine();
    }
}





Models:
using System.Collections.Generic;

namespace NHibernateFetchProblems.Models
{
    public class Question
    {
        public virtual int QuestionId { get; set; }

        public virtual string QuestionText { get; set; }

        public virtual Person AskedBy { get; set; }
        public virtual Person QuestionModifiedBy { get; set; }

        public virtual IList<QuestionComment> QuestionComments { get; set; }
        public virtual IList<Answer> Answers { get; set; }
    }


    public class QuestionComment
    {
        public virtual Question Question { get; set; }

        public virtual int QuestionCommentId { get; set; }
        
        public virtual string QuestionCommentText { get; set; }

        public virtual Person QuestionCommentBy { get; set; }
    }


    public class Answer
    {
        public virtual Question Question { get; set; }

        public virtual int AnswerId { get; set; }

        public virtual string AnswerText { get; set; }
        
        public virtual Person AnsweredBy { get; set; }
        public virtual Person AnswerModifiedBy { get; set; }

        public virtual IList<AnswerComment> AnswerComments { get; set; }
    }


    public class AnswerComment
    {
        public virtual Answer Answer { get; set; }

        public virtual int AnswerCommentId { get; set; }

        public virtual string  AnswerCommentText { get; set; }

        public virtual Person AnswerCommentBy { get; set; }
    }


    public class Person
    {
        public virtual int PersonId { get; set; }
        public virtual string PersonName { get; set; }
    }
}


NHibernate mapping:
using System;
using System.Collections.Generic;
using System.Linq;

using NHibernate;

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;

using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;

using NHibernateFetchProblems.Models;

namespace NHibernateFetchProblems.DbMapping
{
    public static class NhMapping
    {
        private static ISessionFactory _isf = null;
        public static ISessionFactory GetSessionFactory()
        {
            if (_isf != null) return _isf;

            var cfg = new StoreConfiguration();

            var sessionFactory = Fluently.Configure()
              .Database(MsSqlConfiguration.MsSql2008.ShowSql().ConnectionString(                
                  "Server=localhost; Database=NhFetch; Trusted_Connection=true; MultipleActiveResultSets=true"
                  ))
              .Mappings(m =>
                m.AutoMappings
                  .Add(AutoMap.AssemblyOf<Person>(cfg)
                  .Conventions.Add<ReferenceConvention>()
                  .Override<Question>(x => x.HasMany(y => y.QuestionComments).KeyColumn("Question_QuestionId").Cascade.AllDeleteOrphan().Inverse())
                  .Override<Question>(x => x.HasMany(y => y.Answers).KeyColumn("Question_QuestionId").Cascade.AllDeleteOrphan().Inverse())
                  .Override<Answer>(x => x.HasMany(y => y.AnswerComments).KeyColumn("Answer_AnswerId").Cascade.AllDeleteOrphan().Inverse())
                  )
                )
              .BuildSessionFactory();


            _isf = sessionFactory;

            return _isf;
        }
    }


    public class StoreConfiguration : DefaultAutomappingConfiguration
    {
        readonly IList<Type> _objectsToMap = new List<Type>()
        {
            // whitelisted objects to map
            typeof(Person), typeof(Question), typeof(QuestionComment), typeof(Answer), typeof(AnswerComment)
        };
        public override bool IsId(FluentNHibernate.Member member)
        {
            // return base.IsId(member);
            return member.Name == member.DeclaringType.Name + "Id";
        }
        public override bool ShouldMap(Type type) { return _objectsToMap.Any(x => x == type); }


    }

    public class ReferenceConvention : IReferenceConvention
    {
        public void Apply(IManyToOneInstance instance)
        {
            instance.Column(
                instance.Name + "_" + instance.Property.PropertyType.Name + "Id");
        }
    }

}


Entity Framework mapping:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;


using NHibernateFetchProblems.Models;

namespace NHibernateFetchProblems.DbMapping
{
    public class EfMapping : DbContext
    {
        public EfMapping() 
        {
            this.Configuration.ProxyCreationEnabled = true;


        }

        public DbSet<Person> Persons { get; set; }
        public DbSet<Question> Questions { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

            
        }
    }
}


DDL:
use NhFetch;
drop table AnswerComment;
drop table Answer;
drop table QuestionComment;
drop table Question;
drop table Person;

create table Person
(
PersonId int identity(1,1) primary key,
PersonName nvarchar(100) not null
);

create table Question
(
QuestionId int identity(1,1) primary key,
QuestionText nvarchar(100) not null,
AskedBy_PersonId int not null references Person(PersonId),
QuestionModifiedBy_PersonId int null references Person(PersonId)
);

create table QuestionComment
(
Question_QuestionId int not null references Question(QuestionId),
QuestionCommentId int identity(1,1) primary key,
QuestionCommentText nvarchar(100) not null,
QuestionCommentBy_PersonId int not null references Person(PersonId)
);

create table Answer
(
Question_QuestionId int not null references Question(QuestionId),
AnswerId int identity(1,1) primary key,
AnswerText nvarchar(100) not null,
AnsweredBy_PersonId int not null references Person(PersonId),
AnswerModifiedBy_PersonId int null references Person(PersonId)
);

create table AnswerComment
(
Answer_AnswerId int not null references Answer(AnswerId),
AnswerCommentId int identity(1,1) primary key,
AnswerCommentText nvarchar(100) not null,
AnswerCommentBy_PersonId int not null references Person(PersonId)
);


insert into Person(PersonName) values('John');
declare @john int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Paul');
declare @paul int = SCOPE_IDENTITY();

insert into Person(PersonName) values('George');
declare @george int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Ringo');
declare @ringo int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Brian');
declare @brian int = SCOPE_IDENTITY();



insert into Person(PersonName) values('Ely');
declare @ely int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Raymund');
declare @raymund int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Buddy');
declare @buddy int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Marcus');
declare @marcus int = SCOPE_IDENTITY();




insert into Question(QuestionText,AskedBy_PersonId) values('What''s the answer to life and everything?',@john);
declare @question1 int = SCOPE_IDENTITY();

insert into QuestionComment(Question_QuestionId,QuestionCommentText,QuestionCommentBy_PersonId) values(@question1,'what is that?',@paul);
insert into QuestionComment(Question_QuestionId,QuestionCommentText,QuestionCommentBy_PersonId) values(@question1,'nice question',@george);

insert into Answer(Question_QuestionId,AnswerText,AnsweredBy_PersonId) values(@question1,'42',@ringo);
declare @answer1q1 int = SCOPE_IDENTITY();
insert into Answer(Question_QuestionId,AnswerText,AnsweredBy_PersonId) values(@question1,'9',@brian);
declare @answer2q1 int = SCOPE_IDENTITY();
insert into Answer(Question_QuestionId,AnswerText,AnsweredBy_PersonId) values(@question1,'76',@brian);
declare @answer3q1 int = SCOPE_IDENTITY();


insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1q1, 'I think so', @ely);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1q1, 'I''m sure', @raymund);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1q1, 'But not quite', @raymund);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1q1, '42 = 7 * 6. Answer to life is 76!', @buddy);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1q1, '@buddy makes sense! you have same thoughts as @brian', @marcus);


insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer2q1, 'Really 9?', @ely);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer2q1, 'Maybe 10?', @raymund);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer2q1, 'Maybe 12?', @buddy);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer2q1, 'Make it baker''s dozen, make it 13', @buddy);


insert into Question(QuestionText,AskedBy_PersonId) values('Is the sky blue?',@john);
declare @question2 int = SCOPE_IDENTITY();

insert into QuestionComment(Question_QuestionId,QuestionCommentText,QuestionCommentBy_PersonId) values(@question2,'maybe white',@paul);


insert into Answer(Question_QuestionId,AnswerText,AnsweredBy_PersonId) values(@question2,'green',@ringo);
declare @answer1q2 int = SCOPE_IDENTITY();




select * from Question;
select * from QuestionComment;
select * from Answer;
select * from AnswerComment;


Demo code: https://github.com/MichaelBuen/Demo_NHibernate_ThenFetchMany_Problem_And_WorkAround

Thursday, August 23, 2012

Deep copying an object graph with NHibernate is very easy and simple

You were given a task to clone/copy an existing record, down to its collections and sub-collections, and so forth.


It will involve a great deal of code if you will do it manually with SQL, or even from a run-off-the-mill ORM. But with NHibernate, this is a simple undertaking.


With NHibernate, just resetting the object's Id to 0 can make NHibernate able to persist the object (and its collections and sub-collections) to a new row in the database. And parent object's collections and sub-collections are able to reference the cloned parent's assigned key(e.g. SCOPE_IDENTITY). These are done automatically by NHibernate for you.


It's very simple:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NHibernateDeepCopyDemo.Models;
using NHibernateDeepCopyDemo.DbMapping;

using NHibernate.Linq;

namespace NHibernateDeepCopyDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var d = new DemoNh();

            

            var q = d.MakeCopy(1);                
                
            if (q.QuestionId == 1) throw new Exception("Failed");

            if (q.QuestionComments[0].QuestionCommentId == 1) throw new Exception("Failed");
            if (q.QuestionComments[1].QuestionCommentId == 2) throw new Exception("Failed");

            if (q.Answers[0].AnswerId == 1) throw new Exception("Failed");
            if (q.Answers[1].AnswerId == 2) throw new Exception("Failed");

            if (q.Answers[0].AnswerComments[0].AnswerCommentId == 1) throw new Exception("Failed");
            if (q.Answers[0].AnswerComments[1].AnswerCommentId == 2) throw new Exception("Failed");

            if (q.Answers[1].AnswerComments[0].AnswerCommentId == 3) throw new Exception("Failed");
            if (q.Answers[1].AnswerComments[1].AnswerCommentId == 4) throw new Exception("Failed");

            Console.WriteLine("\nOK!");

            Console.ReadLine();

        }
    }

    public class DemoNh
    {
        public Question MakeCopy(int id)
        {
            using (var sess = NhMapping.GetSessionFactory().OpenSession())
            {
                var q = sess.Query<Question>().Single(x => x.QuestionId == id);               


                foreach (var item in q.QuestionComments)
                {
                    sess.Evict(item);
                    item.QuestionCommentId = 0;                    
                }

                foreach (var ansItem in q.Answers)
                {                            
                    foreach (var ansCommentItem in ansItem.AnswerComments)
                    {
                        sess.Evict(ansCommentItem);
                        ansCommentItem.AnswerCommentId = 0;
                    }

                    sess.Evict(ansItem);
                    ansItem.AnswerId = 0;
                }

                sess.Evict(q);
                q.QuestionId = 0;
                

                var nq = sess.Merge(q);
                sess.Flush();
                return nq;
            }

        }
    }
}



These are the models(think of Stackoverflow):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NHibernateDeepCopyDemo.Models
{
    public class Question
    {
        public virtual int QuestionId { get; set; }

        public virtual string QuestionText { get; set; }

        public virtual Person AskedBy { get; set; }
        public virtual Person QuestionModifiedBy { get; set; }

        public virtual IList<QuestionComment> QuestionComments { get; set; }
        public virtual IList<Answer> Answers { get; set; }
    }


    public class QuestionComment
    {
        public virtual Question Question { get; set; }

        public virtual int QuestionCommentId { get; set; }

        public virtual string QuestionCommentText { get; set; }

        public virtual Person QuestionCommentBy { get; set; }
    }


    public class Answer
    {
        public virtual Question Question { get; set; }

        public virtual int AnswerId { get; set; }

        public virtual string AnswerText { get; set; }

        public virtual Person AnsweredBy { get; set; }
        public virtual Person AnswerModifiedBy { get; set; }

        public virtual IList<AnswerComment> AnswerComments { get; set; }
    }


    public class AnswerComment
    {
        public virtual Answer Answer { get; set; }

        public virtual int AnswerCommentId { get; set; }

        public virtual string AnswerCommentText { get; set; }

        public virtual Person AnswerCommentBy { get; set; }
    }


    public class Person
    {
        public virtual int PersonId { get; set; }
        public virtual string PersonName { get; set; }
    }
}

Mapping:

using System;
using System.Collections.Generic;
using System.Linq;

using NHibernate;

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;
using FluentNHibernate.Conventions.Helpers;

using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;

using NHibernateDeepCopyDemo.Models;

namespace NHibernateDeepCopyDemo.DbMapping
{
    public static class NhMapping
    {
        private static ISessionFactory _isf = null;
        public static ISessionFactory GetSessionFactory()
        {
            if (_isf != null) return _isf;

            var cfg = new StoreConfiguration();

            var sessionFactory = Fluently.Configure()
              .Database(MsSqlConfiguration.MsSql2008.ShowSql().ConnectionString(
                  "Server=localhost; Database=NhFetch; Trusted_Connection=true;"
                  ))
              .Mappings(m =>
                m.AutoMappings
                  .Add(AutoMap.AssemblyOf<Person>(cfg)
                  .Conventions.Add<ReferenceConvention>()
                  .Override<Question>(x => x.HasMany(y => y.QuestionComments).KeyColumn("Question_QuestionId").Cascade.AllDeleteOrphan().Inverse())
                  .Override<Question>(x => x.HasMany(y => y.Answers).KeyColumn("Question_QuestionId").Cascade.AllDeleteOrphan().Inverse())
                  .Override<Answer>(x => x.HasMany(y => y.AnswerComments).KeyColumn("Answer_AnswerId").Cascade.AllDeleteOrphan().Inverse())
                  )
                )
              .BuildSessionFactory();


            _isf = sessionFactory;

            return _isf;
        }
    }


    public class StoreConfiguration : DefaultAutomappingConfiguration
    {
        readonly IList<Type> _objectsToMap = new List<Type>()
        {
            // whitelisted objects to map
            typeof(Person), typeof(Question), typeof(QuestionComment), typeof(Answer), typeof(AnswerComment)
        };
        public override bool IsId(FluentNHibernate.Member member)
        {
            // return base.IsId(member);
            return member.Name == member.DeclaringType.Name + "Id";
        }
        public override bool ShouldMap(Type type) { return _objectsToMap.Any(x => x == type); }


    }

    public class ReferenceConvention : IReferenceConvention
    {
        public void Apply(IManyToOneInstance instance)
        {
            instance.Column(
                instance.Name + "_" + instance.Property.PropertyType.Name + "Id");
        }
    }

}


Supporting database:

drop table AnswerComment;
drop table Answer;
drop table QuestionComment;
drop table Question;
drop table Person;

create table Person
(
PersonId int identity(1,1) primary key,
PersonName nvarchar(100) not null
);

create table Question
(
QuestionId int identity(1,1) primary key,
QuestionText nvarchar(100) not null,
AskedBy_PersonId int not null references Person(PersonId),
QuestionModifiedBy_PersonId int null references Person(PersonId)
);

create table QuestionComment
(
Question_QuestionId int not null references Question(QuestionId),
QuestionCommentId int identity(1,1) primary key,
QuestionCommentText nvarchar(100) not null,
QuestionCommentBy_PersonId int not null references Person(PersonId)
);

create table Answer
(
Question_QuestionId int not null references Question(QuestionId),
AnswerId int identity(1,1) primary key,
AnswerText nvarchar(100) not null,
AnsweredBy_PersonId int not null references Person(PersonId),
AnswerModifiedBy_PersonId int null references Person(PersonId)
);

create table AnswerComment
(
Answer_AnswerId int not null references Answer(AnswerId),
AnswerCommentId int identity(1,1) primary key,
AnswerCommentText nvarchar(100) not null,
AnswerCommentBy_PersonId int not null references Person(PersonId)
);


insert into Person(PersonName) values('John');
declare @john int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Paul');
declare @paul int = SCOPE_IDENTITY();

insert into Person(PersonName) values('George');
declare @george int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Ringo');
declare @ringo int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Brian');
declare @brian int = SCOPE_IDENTITY();



insert into Person(PersonName) values('Ely');
declare @ely int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Raymund');
declare @raymund int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Buddy');
declare @buddy int = SCOPE_IDENTITY();

insert into Person(PersonName) values('Marcus');
declare @marcus int = SCOPE_IDENTITY();




insert into Question(QuestionText,AskedBy_PersonId) values('What''s the answer to life and everything?',@john);
declare @question int = SCOPE_IDENTITY();

insert into QuestionComment(Question_QuestionId,QuestionCommentText,QuestionCommentBy_PersonId) values(@question,'what is that?',@paul);
insert into QuestionComment(Question_QuestionId,QuestionCommentText,QuestionCommentBy_PersonId) values(@question,'nice question',@george);

insert into Answer(Question_QuestionId,AnswerText,AnsweredBy_PersonId) values(@question,'42',@ringo);
declare @answer1 int = SCOPE_IDENTITY();
insert into Answer(Question_QuestionId,AnswerText,AnsweredBy_PersonId) values(@question,'9',@brian);
declare @answer2 int = SCOPE_IDENTITY();

insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1, 'I think so', @ely);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer1, 'I''m sure', @raymund);


insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer2, 'Really 9?', @ely);
insert into AnswerComment(Answer_AnswerId,AnswerCommentText,AnswerCommentBy_PersonId) values(@answer2, 'Maybe 10?', @raymund);



select * from Question;
select * from QuestionComment;
select * from Answer;
select * from AnswerComment;


As far as I know, doing this on other ORMs is very tedious.


Happy Computing! ツ

Tuesday, August 21, 2012

ORM navigation differences between Entity Framework and NHibernate

Given this model:

public class Person
{
 public virtual int PersonId { get; set; }
 public virtual string PersonName { get; set; }
}


public class Question
{
 public virtual int QuestionId { get; set; }
 public virtual string QuestionText { get; set; }
 public virtual Person AskedBy { get; set; }
}


And this schema:

create table Person
(
PersonId int identity(1,1) primary key,
PersonName nvarchar(100) not null
);

create table Question
(
QuestionId int identity(1,1) primary key,
QuestionText nvarchar(100) not null,
AskedBy_PersonId int not null references Person(PersonId)
);



While this incur two database roundtrip on Entity Framework:

Console.WriteLine("{0}", q.QuestionText);
Console.WriteLine("{0}", q.AskedBy.PersonId);


NHibernate doesn't, it incurs one database roundtrip only

Wednesday, August 1, 2012

Use components that uses yield return

When to use yield return?

You were given a task to shop for a component you cannot make by yourself, say fibonacci number generators.

You found two vendors. Both of their components produces correct output, both of them can generate up to fifty fibonaccis, and both of them are priced the same.

All things being equal, you are left with one qualifier, a good qualifier, you want to choose the component with a better quality. But for some reasons, these two vendors don't want to give you their source codes, hence you cannot see how they implement things. They just want to give you the DLL only.

But those vendors still have some kindness left in their hearts, they at least give you a clue how their components work.

Vendor A told you they uses eager loading(i.e. uses IList)
Vendor B told you they uses lazy loading(i.e. uses yield return)

In case like this, you need to purchase vendor B's component.

Vendor A's approach uses too much memory, they put elements to an allocated memory. Vendor B generates elements on-the-fly.


Desirability of vendor B's approach is more apparent if for example you just want the 8th fibonacci.

Using vendor A's component, your code still need to wait a long time in order to get the 8th element. Why it is so? Even you just want the 8th element, your code still need for vendor A's component to generate the whole 50 elements, after doing so, then that's the only time you can pick the 8th element.


Using vendor B's component, if you just want to get the 8th element, it will stop generating on the 8th element. Your code don't need vendor B's component to generate all the 50 elements in order for your code to get the 8th element. Vendor B's component has some smart on it.


To contrast the difference between IList and yield return, check the output of this code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ChooseLesserOfTheTwoEvils
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLoopOnEvilA();
            Console.WriteLine("------");
            TestLoopOnEvilB();
            Console.WriteLine("------");
 
            Console.ReadKey();
             
            TestPick8thOnEvilA();
            Console.WriteLine("------");
            TestPick8thOnEvilB();
            Console.WriteLine("------");
 
            Console.ReadKey();
        }
 
        static void TestPick8thOnEvilA()
        {
            Console.WriteLine("TestPick8thOnEvilA");
            long eighth = EvilCompanyA.MathProvider.Fibonacci().Skip(7).Take(1).Single();
            Console.WriteLine("\nEvil A's 8th fibonacci is {0}", eighth);
        }
 
        static void TestPick8thOnEvilB()
        {
            Console.WriteLine("TestPick8thOnEvilB");
            long eighth = EvilCompanyB.MathProvider.Fibonacci().Skip(7).Take(1).Single();
            Console.WriteLine("\nEvil B's 8th fibonacci is {0}", eighth);
        }
 
        static void TestLoopOnEvilA()
        {
            Console.WriteLine("Test Loop On Evil A");
            IEnumerable<long> bucket = EvilCompanyA.MathProvider.Fibonacci();
            Console.WriteLine("\nTest start");
 
            foreach (var item in bucket)
            {
                Console.WriteLine("Evil A's Fib: {0}", item);
            }
            Console.ReadLine();
        }
 
         
        static void TestLoopOnEvilB()
        {
            Console.WriteLine("Test Loop On Evil B");
            IEnumerable<long> bucket = EvilCompanyB.MathProvider.Fibonacci();
            Console.WriteLine("\nTest start");
 
            foreach (var item in bucket)
            {               
                Console.WriteLine("Evil B's Fib: {0}", item);
            }
            Console.ReadLine();
        }
    }
 
     
}
 
 
namespace EvilCompanyA
{
    public static class MathProvider
    {
        public static IEnumerable<long> Fibonacci()
        {           
            IList<long> il = new List<long>();
 
            long a = 0, b = 1;
 
            for (int i = 1; i <= 50; ++i)
            {
                Console.Write("eager{0} ",i);
                il.Add(a);
                long n = a;
                a += b;
                b = n;
            }
                   
 
             
            return il;
        }
    }
}
 
namespace EvilCompanyB
{
    public static class MathProvider
    {
        public static IEnumerable<long> Fibonacci()
        {
            long a = 0, b = 1;
 
            for (int i = 1; i <= 50; ++i)
            {
                Console.Write("lazy{0} ",i);
                yield return a;
                long n = a;
                a += b;
                b = n;
            }
        }
    }
}

Output:

Test Loop On Evil A
eager1 eager2 eager3 eager4 eager5 eager6 eager7 eager8 eager9 eager10 eager11 eager12 eager13 eager14 eager15 eager16 eager17 eager18 eager19 eager20 eager21 eager22 eager23 eager24 eager25 eager26 eager27 eager28 eager29 eager30 eager31 eager32 eager33 eager34 eager35 eager36 eager37 eager38 eager39 eager40 eager41 eager42 eager43 eager44 eager45 eager46 eager47 eager48 eager49 eager50 
Test start
Evil A's Fib: 0
Evil A's Fib: 1
Evil A's Fib: 1
Evil A's Fib: 2
Evil A's Fib: 3
Evil A's Fib: 5
Evil A's Fib: 8
Evil A's Fib: 13
Evil A's Fib: 21
Evil A's Fib: 34
Evil A's Fib: 55
Evil A's Fib: 89
Evil A's Fib: 144
Evil A's Fib: 233
Evil A's Fib: 377
Evil A's Fib: 610
Evil A's Fib: 987
Evil A's Fib: 1597
Evil A's Fib: 2584
Evil A's Fib: 4181
Evil A's Fib: 6765
Evil A's Fib: 10946
Evil A's Fib: 17711
Evil A's Fib: 28657
Evil A's Fib: 46368
Evil A's Fib: 75025
Evil A's Fib: 121393
Evil A's Fib: 196418
Evil A's Fib: 317811
Evil A's Fib: 514229
Evil A's Fib: 832040
Evil A's Fib: 1346269
Evil A's Fib: 2178309
Evil A's Fib: 3524578
Evil A's Fib: 5702887
Evil A's Fib: 9227465
Evil A's Fib: 14930352
Evil A's Fib: 24157817
Evil A's Fib: 39088169
Evil A's Fib: 63245986
Evil A's Fib: 102334155
Evil A's Fib: 165580141
Evil A's Fib: 267914296
Evil A's Fib: 433494437
Evil A's Fib: 701408733
Evil A's Fib: 1134903170
Evil A's Fib: 1836311903
Evil A's Fib: 2971215073
Evil A's Fib: 4807526976
Evil A's Fib: 7778742049
------
Test Loop On Evil B

Test start
lazy1 Evil B's Fib: 0
lazy2 Evil B's Fib: 1
lazy3 Evil B's Fib: 1
lazy4 Evil B's Fib: 2
lazy5 Evil B's Fib: 3
lazy6 Evil B's Fib: 5
lazy7 Evil B's Fib: 8
lazy8 Evil B's Fib: 13
lazy9 Evil B's Fib: 21
lazy10 Evil B's Fib: 34
lazy11 Evil B's Fib: 55
lazy12 Evil B's Fib: 89
lazy13 Evil B's Fib: 144
lazy14 Evil B's Fib: 233
lazy15 Evil B's Fib: 377
lazy16 Evil B's Fib: 610
lazy17 Evil B's Fib: 987
lazy18 Evil B's Fib: 1597
lazy19 Evil B's Fib: 2584
lazy20 Evil B's Fib: 4181
lazy21 Evil B's Fib: 6765
lazy22 Evil B's Fib: 10946
lazy23 Evil B's Fib: 17711
lazy24 Evil B's Fib: 28657
lazy25 Evil B's Fib: 46368
lazy26 Evil B's Fib: 75025
lazy27 Evil B's Fib: 121393
lazy28 Evil B's Fib: 196418
lazy29 Evil B's Fib: 317811
lazy30 Evil B's Fib: 514229
lazy31 Evil B's Fib: 832040
lazy32 Evil B's Fib: 1346269
lazy33 Evil B's Fib: 2178309
lazy34 Evil B's Fib: 3524578
lazy35 Evil B's Fib: 5702887
lazy36 Evil B's Fib: 9227465
lazy37 Evil B's Fib: 14930352
lazy38 Evil B's Fib: 24157817
lazy39 Evil B's Fib: 39088169
lazy40 Evil B's Fib: 63245986
lazy41 Evil B's Fib: 102334155
lazy42 Evil B's Fib: 165580141
lazy43 Evil B's Fib: 267914296
lazy44 Evil B's Fib: 433494437
lazy45 Evil B's Fib: 701408733
lazy46 Evil B's Fib: 1134903170
lazy47 Evil B's Fib: 1836311903
lazy48 Evil B's Fib: 2971215073
lazy49 Evil B's Fib: 4807526976
lazy50 Evil B's Fib: 7778742049
------
TestPick8thOnEvilA
eager1 eager2 eager3 eager4 eager5 eager6 eager7 eager8 eager9 eager10 eager11 eager12 eager13 eager14 eager15 eager16 eager17 eager18 eager19 eager20 eager21 eager22 eager23 eager24 eager25 eager26 eager27 eager28 eager29 eager30 eager31 eager32 eager33 eager34 eager35 eager36 eager37 eager38 eager39 eager40 eager41 eager42 eager43 eager44 eager45 eager46 eager47 eager48 eager49 eager50 
Evil A's 8th fibonacci is 13
------
TestPick8thOnEvilB
lazy1 lazy2 lazy3 lazy4 lazy5 lazy6 lazy7 lazy8 
Evil B's 8th fibonacci is 13
------



Live Code: http://ideone.com/QxU7Sn