Monday, August 29, 2011

Saving the whole object graph across WCF with Entity Framework via ToTheEfnhX

First of all, we will introduce a repository interface to implement the merge functionality that is glaringly missing from Entity Framework. I decided to name this repository ToTheEfnhX.


What ToTheEfnhX is not:

  • It's not a Unit of Work component
  • It doesn't compete with S#arp, S#arp is tied to NHibernate



What ToTheEfnhX is:

  • It's a repository interface
  • It's a repository interface that supports both Entity Framework and NHibernate




On this topic, we will discuss how to save to whole object graph using Entity Framework via ToTheEfnhX component. The API is patterned after NHibernate session.Merge, this function can save and update the whole object graph to database.

The general pattern of persisting object to database:


public void SaveQuestion(Question question, out int id, out byte[] version)
{
 var repo = QuestionRepository;
 repo.Merge(question, question.RowVersion);
 id = question.QuestionId;
 version = question.RowVersion;
}


IRepository<Question> QuestionRepository
{
   get
   {
    // Entity Framework:
    var x = new EfDbMapper();                                
    return new EfRepository<question>(x);

    // NHibernate:
    // return new NhRepository<Question> (NhDbMapper.GetSession(ConfigurationManager.ConnectionStrings["EfDbMapper"].ConnectionString));
 }
}


Basically that's it! It's similar to NHibernate session.Merge

For a complete step-by-step persisting of object graph across the wires(e.g. WCF), follow the following steps, or you can just download the source code on both projects(see the bottom of the article)



Create a Winforms Project

Add a Class Library Project

Copy these entities on Class1.cs, overwrite all of its content:

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

namespace TheEntities
{
    [DataContract(IsReference=true)]    
    public class Question
    {
        [DataMember] public virtual int QuestionId { get; set; }
        [DataMember] public virtual string Text { get; set; }
        [DataMember] public virtual string Poster { get; set; }

        [DataMember] public virtual IList<QuestionComment> Comments { get; set; }

        [DataMember] public virtual IList<Answer> Answers{ get; set; }



        [DataMember] public virtual byte[] RowVersion { get; set; }


    }

    [DataContract]
    public class QuestionComment
    {
        [DataMember] public virtual Question Question { get; set; }        

        [DataMember] public virtual int QuestionCommentId { get; set; }
        [DataMember] public virtual string Text { get; set; }
        [DataMember] public virtual string Poster { get; set; }


    }


    [DataContract(IsReference = true)]
    public class Answer
    {
        [DataMember] public virtual Question Question { get; set; }

        [DataMember] public virtual int AnswerId { get; set; }
        [DataMember] public virtual string Text { get; set; }
        [DataMember] public virtual string Poster { get; set; }

        [DataMember] public virtual IList<AnswerComment> Comments { get; set; }
        
    }

    [DataContract]
    public class AnswerComment
    {
        [DataMember] public virtual Answer Answer { get; set; }

        [DataMember] public virtual int AnswerCommentId { get; set; }
        [DataMember] public virtual string Text { get; set; }
        [DataMember] public virtual string Poster { get; set; }
    }

}



Add the System.Runtime.Serialization to  TheEntities' References:


Add WCF Service Application project to your solution. Name it TheService



Delete Service1.svc and IService1.svc. Then add new WCF Service, name it Forumer.svc



Add TheEntities to TheService's References



Activate NuGet (Tools > Library Package Manager > Package Manager Console). Then add Entity Framework and Fluent NHibernate to TheService.

PM> install-package EntityFramework
You are downloading EntityFramework from Microsoft, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkId=224682. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'EntityFramework 4.1.10715.0'.
Successfully added 'EntityFramework 4.1.10715.0' to TheService.

PM> install-package FluentNHibernate
'NHibernate (≥ 3.2.0.4000)' not installed. Attempting to retrieve dependency from source...
Done.
'Iesi.Collections (≥ 3.2.0.4000)' not installed. Attempting to retrieve dependency from source...
Done.
Successfully installed 'Iesi.Collections 3.2.0.4000'.
Successfully installed 'NHibernate 3.2.0.4000'.
You are downloading FluentNHibernate from James Gregory and contributors, the license agreement to which is available at http://github.com/jagregory/fluent-nhibernate/raw/master/LICENSE.txt. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'FluentNHibernate 1.3.0.717'.
Successfully added 'Iesi.Collections 3.2.0.4000' to TheService.
Successfully added 'NHibernate 3.2.0.4000' to TheService.
Successfully added 'FluentNHibernate 1.3.0.717' to TheService.


Then copy this in IForumer.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using TheEntities;

namespace TheService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
    [ServiceContract]
    public interface IForumer
    {

        [OperationContract]
        string GetData(int value);



        [OperationContract]
        Question OpenQuestion(int id);

        [OperationContract]
        void SaveQuestion(Question question, out int id, out byte[] version);

        // TODO: Add your service operations here

        [OperationContract]
        void DeleteQuestion(int questionId, byte[] version);
    }
}


Copy this to your Forumer.svc.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Configuration;

using NHibernate;
using NHibernate.Linq;
using System.Data.Entity;

using Ienablemuch.ToTheEfnhX;
using Ienablemuch.ToTheEfnhX.EntityFramework;
using Ienablemuch.ToTheEfnhX.NHibernate;



using TheEntities;



namespace TheService
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    public class Forumer : IForumer
    {

        
        public Forumer()
        {
        }

        IRepository<Question> QuestionRepository
        {
            get
            {
                // choose between these two

                // Entity Framework:
                /*var x = new EfDbMapper();                                
                return new EfRepository<Question>(x);*/

                // NHibernate:
                return new NhRepository<Question>(NhDbMapper.GetSession(ConfigurationManager.ConnectionStrings["EfDbMapper"].ConnectionString));
            }
        }
        
        public Question OpenQuestion(int id)
        {
            var repo = QuestionRepository;

            var query = repo.All.Where(y => y.QuestionId == id);


            if (QuestionRepository.GetType() == typeof(EfRepository<Question>))
            {                
                query = query
                        .Include("Answers")
                            .Include("Answers.Comments")
                        .Include("Comments");

                return query.Single();
            }
            else if (QuestionRepository.GetType() == typeof(NhRepository<Question>))
            {               
                 
                /* 
                // Nested FetchMany produces duplicate data. See solution below(solution obtained from stackoverflow).
                query = query
                        .FetchMany(x => x.Answers)
                            .ThenFetchMany(x => x.Comments)
                        .FetchMany(x => x.Comments);
                */



                // Good thing there's Stackoverflow, here's one way to solve it:
                // http://stackoverflow.com/questions/7028705/is-nhibernate-fetchmany-on-more-than-two-tables-broken

                repo.All.Where(y => y.QuestionId == id)
                        .FetchMany(x => x.Answers)
                            .ThenFetchMany(x => x.Comments)
                        .ToFuture();

                query = repo.All.Where(y => y.QuestionId == id)
                        .FetchMany(x => x.Comments);

                
                 
            }
            else
                throw new Exception("Something unsupported. Contact the developer");

            return query.Single();

        }


        public string GetData(int value)
        {
            
            // z.Database.Create();

            var x = QuestionRepository;

            return x.Get(1).Text;

            return string.Format("You entered: {0}", value);
        }



        public void SaveQuestion(Question question, out int id, out byte[] version)
        {
            var repo = QuestionRepository;
            repo.Merge(question, question.RowVersion);
            id = question.QuestionId;
            version = question.RowVersion;
        }



        public void DeleteQuestion(int questionId, byte[] version)
        {
            var repo = QuestionRepository;
            repo.DeleteCascade(questionId, version);
        }
    }
}



Add Entity Framework database mapper to TheService. Name it EfDbMapper.







Then copy this mapping:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using TheEntities;

namespace TheService
{
    public class EfDbMapper : DbContext
    {


        public EfDbMapper() 
        {
            this.Configuration.ProxyCreationEnabled = false;
        }
        

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

            modelBuilder.Entity<Question>().Property(x => x.RowVersion).IsRowVersion();

            modelBuilder.Entity<Question>().HasMany(x => x.Comments).WithRequired(x => x.Question).Map(x => x.MapKey("Question_QuestionId"));
            
            modelBuilder.Entity<Question>().HasMany(x => x.Answers).WithRequired(x => x.Question).Map(x => x.MapKey("Question_QuestionId"));

            modelBuilder.Entity<Answer>().HasMany(x => x.Comments).WithRequired(x => x.Answer).Map(x => x.MapKey("Answer_AnswerId"));

            

        }
    }

}

Add NHibernate database mapper to TheService. Name it NhDbMapper:




Then copy this mapping:

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

using NHibernate;

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

using TheEntities;

namespace TheService
{
    internal static class NhDbMapper
    {
        public static ISession GetSession(string connectionString)
        {
            return GetSessionFactory(connectionString).OpenSession();
        }


        static ISessionFactory _sf = null;
        private static ISessionFactory GetSessionFactory(string connectionString)
        {
            if (_sf != null) return _sf;





            var fc = Fluently.Configure()
                    .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
                    .Mappings
                    (m =>


                            m.AutoMappings.Add
                            (
                                AutoMap.AssemblyOf<Question>(new CustomConfiguration())

                                // .Conventions.Add(ForeignKey.EndsWith("Id"))
                               .Conventions.Add<CustomForeignKeyConvention>()

                               .Conventions.Add<HasManyConvention>()
                               .Conventions.Add<RowversionConvention>()

                               .Override<Question>(x =>
                                    {
                                       

                                        x.Id(z => z.QuestionId).GeneratedBy.Identity();

                                        x.HasMany(z => z.Comments).KeyColumn("Question_QuestionId");
                                        x.HasMany(z => z.Answers).KeyColumn("Question_QuestionId");

                                    })
                                .Override<Answer>(x =>
                                   {
                                       x.References(z => z.Question).Column("Question_QuestionId");

                                       x.HasMany(z => z.Comments).KeyColumn("Answer_AnswerId");
                                   })

                                .Override<QuestionComment>(x =>
                                    {
                                        x.References(z => z.Question).Column("Question_QuestionId");
                                    })
                                .Override<AnswerComment>(x =>
                                    {
                                        x.References(z => z.Answer).Column("Answer_AnswerId");
                                    })

                            )


           );



            _sf = fc.BuildSessionFactory();
            return _sf;
        }


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

            public override bool IsVersion(FluentNHibernate.Member member) { return member.Name == "RowVersion"; }
        }




        public class CustomForeignKeyConvention : ForeignKeyConvention
        {
            protected override string GetKeyName(FluentNHibernate.Member property, Type type)
            {
                if (property == null)
                    return type.Name + "Id";


                // make foreign key compatible with Entity Framework
                return type.Name + "_" + property.Name + "Id";
            }
        }


        class HasManyConvention : IHasManyConvention
        {

            public void Apply(IOneToManyCollectionInstance instance)
            {
                instance.Inverse();
                instance.Cascade.AllDeleteOrphan();
            }


        }

        class RowversionConvention : IVersionConvention
        {
            public void Apply(IVersionInstance instance) { instance.Generated.Always(); }
        }

    }//ModelsMapper


}


Add Ienablemuch.ToTheEfnhX.dll and SystemLinqDynamic.dll to TheService's References:



Add Ienablemuch.ToTheEfnhX.EntityFramework.dll and Ienablemuch.ToTheEfnhX.NHibernate.dll to  TheService's References





Compile TheService (WCF Service project).


On your front-end(Winforms app), add Service Reference to TheService, name it UseService



Then configure your Service Reference:




Add BindingSource to your form, then name it to bdsQuestion, on your bdsQuestion Properties, click DataSource, then point it to UseService.Question:





Your bdsQuestion's DataSource should look like this:



Add another binding source to your form, name it bdsQuestionComment, point its DataSource to bdsQuestion, and its DataMember to Comments.

It should look like this:



Add another binding source to your form, name it bdsAnswer, point its DataSource to bdsQuestion, and its DataMember to Answers.

It should look like this:



Add another binding source to your form, name it bdsAnswerComment, point its DataSource to bdsAnswer, and its DataMember to Comments.

It should look like this:



Add these controls, and set the controls' property's BindingSource accordingly




Question's Text

Question's Poster

Question's Comments

Question's Answers' Text

Question's Answers' Poster

Question's Answers' Comments

Answer's BindingNavigator


Copy the following to your Form1.cs, and bind the events to their corresponding controls(I think this is where VB.NET trumps C#, VB.NET has declarative way of binding events to objects(via Handles keyword), VB.NET event has more one-stop-shop feel into it, anyway I still love C# :-)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Threading;

using ObjectGraphToTheEfnhX.UseService;


namespace ObjectGraphToTheEfnhX
{



    public partial class Form1 : Form
    {

        ForumerClient _svc = new ForumerClient();
        public Form1()
        {
            InitializeComponent();

            
            
        }


        private void uxNew_Click(object sender, EventArgs e)
        {
            bdsQuestion.DataSource = new Question();

            IndicateAnswerInputsAvailability();
        }

        private void uxSave_Click(object sender, EventArgs e)
        {
            bdsQuestion.EndEdit();
            bdsAnswerComment.EndEdit();
            bdsAnswer.EndEdit();

            ThreadPool.QueueUserWorkItem(o =>
            {
                Question question = (Question)bdsQuestion.Current;
                int id;
                byte[] rowVersion;
                id = _svc.SaveQuestion(out rowVersion, question);

                this.Invoke((MethodInvoker)delegate
                {
                    Question q = (Question)bdsQuestion.Current;
                    q.QuestionId = id;
                    q.RowVersion = rowVersion;
                });

                MessageBox.Show("Saved.");
            });
        }



        private void uxOpen_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(o =>
            {

                int n;
                bool isOk = int.TryParse(uxQuestionNavigator.Text, out n);
                if (isOk)
                {
                    Question q = _svc.OpenQuestion(n);

                    this.Invoke((MethodInvoker)delegate
                    {
                        bdsQuestion.DataSource = q;
                        IndicateAnswerInputsAvailability();
                    });
                }
            });




        }


        private void bdsQuestion_CurrentChanged(object sender, EventArgs e)
        {
            Console.WriteLine("bdsQuestion");

            var c = (Question)bdsQuestion.Current;

            if (c.Answers == null) c.Answers = new List<Answer>();
            if (c.Comments == null) c.Comments = new List<QuestionComment>();


        }

        void IndicateAnswerInputsAvailability()
        {
            bool readOnly = bdsAnswer.Current == null;


            uxAnswerText.ReadOnly = readOnly;
            uxAnswerPoster.ReadOnly = readOnly;

            grdAnswerComment.ReadOnly = readOnly;
            grdAnswerComment.DefaultCellStyle.BackColor = readOnly ? Color.LightGray : Color.White;
        }

        private void bdsAnswer_CurrentChanged(object sender, EventArgs e)
        {
            Console.WriteLine("bdsAnswer_CurrentChanged");




            var a = (Answer)bdsAnswer.Current;
            if (a == null)
            {
                IndicateAnswerInputsAvailability();
                return;
            }

            IndicateAnswerInputsAvailability();

            if (a.Question == null)
            {
                a.Question = (Question)bdsQuestion.Current; // link to parent

                // http://www.ienablemuch.com/2011/08/dont-initialize-collection-on-your.html
                a.Comments = new List<AnswerComment>();
            }
        }


        private void bdsQuestionComment_CurrentChanged(object sender, EventArgs e)
        {
            var c = (QuestionComment)bdsQuestionComment.Current;
            if (c == null) return;

            if (c.Question == null)
            {
                c.Question = (Question)bdsQuestion.Current; // link to parent                
            }

        }

        private void bdsAnswerComment_CurrentChanged(object sender, EventArgs e)
        {
            var ac = (AnswerComment)bdsAnswerComment.Current;
            if (ac == null) return;

            if (ac.Answer == null)
            {
                ac.Answer = (Answer)bdsAnswer.Current; // link to parent
            }



        }

        private void uxDelete_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(o =>
            {
                Question question = (Question)bdsQuestion.Current;

                _svc.DeleteQuestion(question.QuestionId, question.RowVersion);

                this.Invoke((MethodInvoker)delegate
                {
                    uxNew.PerformClick();
                });

                MessageBox.Show("Deleted.");
            });
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            _svc = new UseService.ForumerClient();

            uxNew.PerformClick();
        }





    }

    public static class Console
    {
        static int i = 0;
        public static void WriteLine(string s)
        {
            global::System.Console.WriteLine(s + " " + ++i);
        }
    }
}



Run this on SQL Management Studio:
drop database ObjectGraph;
create database ObjectGraph;
use ObjectGraph;

/*
drop table AnswerComment;
drop table Answer;
drop table QuestionComment;
drop table Question;
*/


-- Primary key and foreign key fields naming convention is patterned after Entity Framework's DB creation style,
-- except for the constraint name of primary key and foreign key

-- make the link to parent obvious, field(s) that comes before primary key are foreign key(which typically is immutable too)
-- other foreign key that comes after primary key are mutable

create table Question
(
QuestionId int identity(1,1) not null,
Text nvarchar(max),
Poster nvarchar(max),
RowVersion rowversion,

constraint pk_Question primary key(QuestionId)
);

create table QuestionComment
(
Question_QuestionId int not null, 

QuestionCommentId int identity(1,1) not null,
Text nvarchar(max),
Poster nvarchar(max),

constraint pk_QuestionComment primary key(QuestionCommentId),
constraint fk_QuestionComment__Question foreign key(Question_QuestionId) references Question(QuestionId)
);


create table Answer
(
Question_QuestionId int not null,

AnswerId int identity(1,1) not null,
Text nvarchar(max),
Poster nvarchar(max),

constraint pk_Answer primary key(AnswerId),
constraint fk_Answer__Question foreign key(Question_QuestionId) references Question(QuestionId)
);


create table AnswerComment
(
Answer_AnswerId int not null,

AnswerCommentId int identity(1,1) not null,
Text nvarchar(max),
Poster nvarchar(max),

constraint pk_AnswerComment primary key(AnswerCommentId),
constraint fk_AnswerComment__Answer foreign key(Answer_AnswerId) references Answer(AnswerId)
);


Then put this in Web.Config or your WCF Service Project

<connectionStrings>
    <add name="EfDbMapper"
         providerName="System.Data.SqlClient"
         connectionString="Data Source=localhost; Initial Catalog=ObjectGraph; User id=sa; Password=P@$$w0rd"/>
  </connectionStrings>


Sample output:





As you can see, it can save the whole object graph.

Now let's try to delete the first answer(its corresponding comments will be deleted too), and let's try to modify the second's answer's text, and add another comment to second's answer. This is where ToTheEfnhX dutifully do its job, this Merge functionality is glaringly missing in Entity Framework. All changes (delete,update,insert) to the entities can be merged back to database.



This is the output:




Run the app. Happy Computing!


The sample project code:
http://code.google.com/p/to-the-efnh-x-sample-project/downloads/list

ToTheEfnhX code:
http://code.google.com/p/to-the-efnh-x/downloads/list


Updated ToTheEfnhX code: https://github.com/MichaelBuen/ToTheEfnhX

Sunday, August 21, 2011

Entity Framework troubles on WCF

If you have this recurring error..

The underlying connection was closed: The connection was closed unexpectedly.

.., and has accompanying error message similar to this (debug WCF effectively):
There was an error while trying to serialize parameter http://tempuri.org/:OpenQuestionResult. The InnerException message was 'Type 'System.Data.Entity.DynamicProxies.Question_67CF6019AA09770B428CB66B12E25E67E057C1CE1AF042237C78DD56D4B495D9' with data contract name 'Question_67CF6019AA09770B428CB66B12E25E67E057C1CE1AF042237C78DD56D4B495D9:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.

even you turned off ProxyCreation in your OnModelCreating of your Entity Framework mapping:

public class EfDbMapper : DbContext
{
   
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<Question>().Property(x => x.RowVersion).IsRowVersion();

        modelBuilder.Entity<Question>().HasMany(x => x.Comments).WithRequired(x => x.Question).Map(x => x.MapKey("Question_QuestionId"));
        
        modelBuilder.Entity<Question>().HasMany(x => x.Answers).WithRequired(x => x.Question).Map(x => x.MapKey("Question_QuestionId"));

        modelBuilder.Entity<Answer>().HasMany(x => x.Comments).WithRequired(x => x.Answer).Map(x => x.MapKey("Answer_AnswerId"));

        this.Configuration.ProxyCreationEnabled = false;

    }
}


.., but the error still occurs, that mean ProxyCreationEnabled was reset to true. I noticed when I plug the NHibernate repository, there's no WCF error happening, by then we can infer that the error doesn't lie in WCF. It has something to do with the ORM (in this case, Entity Framework) woes.


I noticed, it's better to turn off proxy in constructor. There's no more dreaded underlying connection was closed after I moved the disabling of ProxyCreation to Entity Framework constructor


public class EfDbMapper : DbContext
{
    public EfDbMapper() 
    {
        this.Configuration.ProxyCreationEnabled = false;        
    }
    

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<Question>().Property(x => x.RowVersion).IsRowVersion();

        modelBuilder.Entity<Question>().HasMany(x => x.Comments).WithRequired(x => x.Question).Map(x => x.MapKey("Question_QuestionId"));
        
        modelBuilder.Entity<Question>().HasMany(x => x.Answers).WithRequired(x => x.Question).Map(x => x.MapKey("Question_QuestionId"));

        modelBuilder.Entity<Answer>().HasMany(x => x.Comments).WithRequired(x => x.Answer).Map(x => x.MapKey("Answer_AnswerId"));


    }
}

NHibernate Session's Transaction cannot detect other transactions' commit status

[TestMethod]
public void Nh_SessionTransaction_cannot_detect_transaction_commitness()
{
    // Arrange
    NHibernate.ISession sess = NhModelsMapper.GetSession(connectionString);
    Assert.IsFalse(sess.Transaction.IsActive);
    
    using(var tx = sess.BeginTransaction())
    {
        Assert.IsTrue(sess.Transaction.IsActive);
        Assert.IsTrue(tx.IsActive);

        tx.Commit();

        Assert.IsFalse(sess.Transaction.WasCommitted);
        Assert.IsTrue(tx.WasCommitted);

        Assert.IsFalse(sess.Transaction.IsActive);
        Assert.IsFalse(tx.IsActive);

    }

}

Further unit tests:
[TestMethod]
public void Nh_SessionTransaction_can_detect_its_own_transaction_commitness_only()
{
    // Arrange
    NHibernate.ISession sess = NhModelsMapper.GetSession(connectionString);
    Assert.IsFalse(sess.Transaction.IsActive);

    sess.Transaction.Begin();
    Assert.IsTrue(sess.Transaction.IsActive);
    
    Assert.IsFalse(sess.Transaction.WasCommitted);
    
}

NHibernate non-support for nested transactions, and how to tackle it

We cannot do nested transaction in NHibernate, but we can detect if there's an active transaction and adjust our code's transaction accordingly

[TestMethod]
public void Nh_Can_Detect_Transaction_From_Session_Begin_Transaction() 
{
	// Arrange
	NHibernate.ISession sess = NhModelsMapper.GetSession(connectionString);
	Assert.IsNotNull(sess.Transaction);
	Assert.IsFalse(sess.Transaction.IsActive);


	// Act
	var tx = sess.BeginTransaction();
	
	
	// Assert
	Assert.IsTrue(sess.Transaction.IsActive);
	Assert.AreEqual(sess.Transaction.IsActive, tx.IsActive);

	Assert.IsNotNull(tx);
	Assert.IsNotNull(sess.Transaction);
	Assert.AreSame(sess.Transaction, tx);

	tx.Commit();
	Assert.IsFalse(sess.Transaction.IsActive);
	Assert.AreEqual(sess.Transaction.IsActive, tx.IsActive);

}

Monday, August 15, 2011

Ensuring the the entities object graph returned by Entity Framework is not of proxy type

I got a nasty error in my repository interface that I debugged for about three days. I noticed that the cloning procedure sometimes get proxy objects when the unit tests are run together (the cloning procedure failed), though when run individually each of them passed the unit test.

It turns out the cause of the problem is when the repository interface get objects from GetCascade repository interface, sometimes it return object proxies. Though the DbContext query in GetCascade returns the real object type when the unit test is run individually(I have this.Configuration.ProxyCreationEnabled = false; on OnModelCreating), some of the objects that are returned when the unit tests are run together are of the proxy type. And it turns out that ProxyCreationEnabled are being reset back to true when the unit tests are run together. How this quirk happens is beyond me, so the interim solution is to set again the ProxyCreationEnabled to false to ensure the objects returned by EF's Linq are not of proxy type.

public TEnt GetCascade(object pkValue)
{
    // don't want proxy objects here :-)
    
    bool oldValue = _ctx.Configuration.ProxyCreationEnabled;
    _ctx.Configuration.ProxyCreationEnabled = false;
     

    Type entType = typeof(TEnt);
    var query = _ctx.Set<TEnt>().AsNoTracking().Where(string.Format("{0} = @0", PrimaryKeyName), pkValue);
    if (!_ctx.Configuration.ProxyCreationEnabled)
    {
        /* e.g.
        var query = new EfRepository<Question>().All.Where(x => x.QuestionId == id);
        query = query.Include("Answers");
        query = query.Include("Answers.Comments");
        query = query.Include("Comments");*/

        foreach (string path in GetCollectionPaths(entType))
            query = query.Include(path);

    }


    var r = query.Single();


    _ctx.Configuration.ProxyCreationEnabled = oldValue;
    

    return r;
}

IEnumerable<string> GetCollectionPaths(Type root)
{

    foreach (PropertyInfo pi in root.GetProperties().Where(x => x.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(x.PropertyType)))
    {

        yield return pi.Name;

        // ICollection<listElemType> p; IList derives from ICollection, just use the base interface
        Type listElemType =
            pi.PropertyType.GetInterfaces().Where(x =>
            x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)).Single().GetGenericArguments()[0];


        foreach (string subPath in GetCollectionPaths(listElemType))
        {
            yield return pi.Name + "." + subPath;
        }

    }
}



UPDATE: June 3, 2012

Permanent solution: http://www.ienablemuch.com/2011/08/entity-framework-troubles-on-wcf.html



Sunday, August 14, 2011

Deep object cloning (at least for ORM uses) sans serialization

public static class ObjectCloner
{

    public static object Clone(this object root)
    {
        Type rootType = root.GetType();
        object clone = Activator.CreateInstance(rootType);
 
        foreach (PropertyInfo pi in rootType.GetProperties())
        {
            bool isCollection = pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType);
            if (!isCollection)
            {
                object transientVal = rootType.InvokeMember(pi.Name, BindingFlags.GetProperty, null, root, new object[] { });
                rootType.InvokeMember(pi.Name, BindingFlags.SetProperty, null, clone, new object[] { transientVal });
            }
            else
            {                    
                var colOrig = (IList)rootType.InvokeMember(pi.Name, BindingFlags.GetProperty, null, root, new object[] { });
                object clonedList = Activator.CreateInstance(colOrig.GetType());
                rootType.InvokeMember(pi.Name, BindingFlags.SetProperty, null, clone, new object[] { clonedList });


                CloneCollection(root, (IList)colOrig, clone, (IList)clonedList);

            }
        }
        return clone;
    }

    private static void CloneCollection(object origParent, IList origList, object cloneParent, IList cloneList)
    {            
        foreach (object item in origList)
        {
            object cloneItem = item.Clone();

            foreach(PropertyInfo pi in cloneItem.GetType().GetProperties().Where(x => x.PropertyType == origParent.GetType()))
            {
                object val = cloneItem.GetType().InvokeMember(pi.Name, BindingFlags.GetProperty, null, cloneItem, new object[] { });

                if (object.ReferenceEquals(val,origParent))
                {
                    // point it to its new parent
                    cloneItem.GetType().InvokeMember(pi.Name, BindingFlags.SetProperty, null, cloneItem, new object[] { cloneParent });
                }
            }

            cloneList.Add(cloneItem);
        }

    }

}


To use:
[TestMethod]
public void Can_do_deep_clone()
{
    Question orig = PopulateQuestion();

    Question clone = (Question)orig.Clone();



    Assert.AreNotSame(orig, clone);
    Assert.AreNotSame(orig.Answers, clone.Answers);
    
    Assert.AreSame(orig, orig.Answers[0].Question);
    Assert.AreSame(clone, clone.Answers[0].Question);
    
    Assert.AreNotSame(orig.Answers[0], clone.Answers[0]);
    Assert.AreNotSame(orig.Answers[0].Question, clone.Answers[0].Question);
    Assert.AreNotSame(orig.Answers[1].Question, clone.Answers[1].Question);
                
    Assert.AreNotSame(orig.Answers[1].Comments, clone.Answers[1].Comments);
    Assert.AreNotSame(orig.Answers[1].Comments[0], clone.Answers[1].Comments[0]);

    Assert.AreSame(orig.Answers[1], orig.Answers[1].Comments[0].Answer);
    Assert.AreSame(clone.Answers[1], clone.Answers[1].Comments[0].Answer);

    Assert.AreEqual(orig.Text, clone.Text);
    Assert.AreEqual(orig.Answers.Count, clone.Answers.Count);
    Assert.AreEqual(orig.Answers[0].Text, clone.Answers[0].Text);
    Assert.AreEqual(orig.Answers[1].Comments[0].Text, clone.Answers[1].Comments[0].Text);


}

private static Question PopulateQuestion()
{
    var importantQuestion = new Question { Text = "The answer to life", Poster = "Boy", Answers = new List<Answer>(), Comments = new List<QuestionComment>() };
    var answerA = new Answer { Question = importantQuestion, Text = "42", Poster = "John", Comments = new List<AnswerComment>() };
    var answerB = new Answer { Question = importantQuestion, Text = "143", Poster = "Paul", Comments = new List<AnswerComment>() };
    var answerC = new Answer { Question = importantQuestion, Text = "888", Poster = "Elton", Comments = new List<AnswerComment>() };
    importantQuestion.Answers.Add(answerA);
    importantQuestion.Answers.Add(answerB);
    importantQuestion.Answers.Add(answerC);

    var commentToImportantQuestion = new QuestionComment { Question = importantQuestion, Text = "Is There?", Poster = "George" };
    importantQuestion.Comments.Add(commentToImportantQuestion);
    var commentToAnswerB = new AnswerComment { Answer = answerB, Text = "Isn't the answer is 7 times 6?", Poster = "Ringo" };
    answerB.Comments.Add(commentToAnswerB);
    return importantQuestion;
}


Object structure sample:
public class Question
{
    public virtual int QuestionId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }

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

   
    public virtual byte[] RowVersion { get; set; }
}


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

    public virtual int QuestionCommentId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }
}



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

    public virtual int AnswerId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }

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



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

    public virtual int AnswerCommentId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }
}

Saturday, August 13, 2011

NHibernate LINQ left join not supported yet

Given this entities:

public class Question
{
    public virtual int QuestionId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }

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

   
    public virtual byte[] RowVersion { get; set; }
}


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

    public virtual int QuestionCommentId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }
}



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

    public virtual int AnswerId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }

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



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

    public virtual int AnswerCommentId { get; set; }
    public virtual string Text { get; set; }
    public virtual string Poster { get; set; }
}



This is the INNER JOIN using LINQ:

var www = from x in q.All
          from y in x.Answers
          group y by new { x.QuestionId } into grp
          select new { grp.Key.QuestionId, Count = grp.Count() };


Output by Entity Framework:
SELECT 
[GroupBy1].[K1] AS [Question_QuestionId], 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
 [Extent1].[Question_QuestionId] AS [K1], 
 COUNT(1) AS [A1]
 FROM [dbo].[Answer] AS [Extent1]
 GROUP BY [Extent1].[Question_QuestionId]
)  AS [GroupBy1]

Output by NHibernate:
SELECT question0_.QuestionId AS col_0_0_, CAST(COUNT(*) AS INT) AS col_1_0_ 
FROM [Question] question0_ INNER JOIN [Answer] answers1_ ON question0_.QuestionId=answers1_.Question_QuestionId 
GROUP BY question0_.QuestionId   



This is the LEFT JOIN using LINQ:

var www = from x in q.All
          from y in x.Answers.DefaultIfEmpty()
          group y by new { x.QuestionId } into grp
          select new { grp.Key.QuestionId, AnswersCount = grp.Count() };

This is Entity Framework output:
SELECT 
[GroupBy1].[K1] AS [QuestionId], 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
 [Extent1].[QuestionId] AS [K1], 
 COUNT(1) AS [A1]
 FROM  [dbo].[Question] AS [Extent1]
 LEFT OUTER JOIN [dbo].[Answer] AS [Extent2] ON [Extent1].[QuestionId] = [Extent2].[Question_QuestionId]
 GROUP BY [Extent1].[QuestionId]
)  AS [GroupBy1]

NHibernate is currently lacking LEFT JOIN
System.NotSupportedException: The DefaultIfEmptyResultOperator result operator is not current supported

But as any programmers worth their salt, they can quickly discern that the query is not a proper LEFT JOIN. A Question's AnswersCount will still have a value of 1 even there's no matching answers on a given question


It should be constructed this way:

var www = from x in q.All
                      from y in x.Answers.DefaultIfEmpty()
                      group y by new { x.QuestionId } into grp
                      select new { grp.Key.QuestionId, Count = grp.Sum(x => x.Question.QuestionId != null ? 1 : 0 ) }; 

// similar to this: http://stackoverflow.com/questions/3789850/is-clean-sql-achievable-on-linq-to-sql

Entity Framework's Linq query generator is atrocious compared to other ORMs (even when compared to Linq-to-Sql):

SELECT 
[GroupBy1].[K1] AS [QuestionId], 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
 [Join1].[K1] AS [K1], 
 SUM([Join1].[A1]) AS [A1]
 FROM ( SELECT 
  [Extent1].[QuestionId] AS [K1], 
  CASE WHEN ([Extent2].[Question_QuestionId] IS NOT NULL) THEN 1 ELSE 0 END AS [A1]
  FROM  [dbo].[Question] AS [Extent1]
  LEFT OUTER JOIN [dbo].[Answer] AS [Extent2] ON [Extent1].[QuestionId] = [Extent2].[Question_QuestionId]
 )  AS [Join1]
 GROUP BY [K1]
)  AS [GroupBy1]


Contrast that with this Linq-to-Sql sample generated query (same scenario as above, i.e. using two tables too). This is very lean

SELECT SUM(
    (CASE
        WHEN [t1].[CustomerID] IS NOT NULL THEN @p0
        ELSE @p1
     END)) AS [N], [t0].[CustomerID] AS [Key]
FROM [Customers] AS [t0]
LEFT OUTER JOIN [Orders] AS [t1] ON [t0].[CustomerID] = [t1].[CustomerID]
GROUP BY [t0].[CustomerID]

// source: http://stackoverflow.com/questions/3789850/is-clean-sql-achievable-on-linq-to-sql

For NHibernate, you have to use QueryOver for LEFT JOIN functionality: http://www.ienablemuch.com/2012/12/nhibernates-non-stringly-typed-left-join.html

ProxyCreationEnabled = false; can not load the collections

When using

this.Configuration.ProxyCreationEnabled = false;

You will not be able to load all child collections of an entity

var query = questionRep.All.Where(x => x.QuestionId == questionId);
retrievedQuestion = query.Single();
Assert.AreEqual(expectedCount, retrievedQuestion.Answers.Count);


The retrievedQuestion.Answers is left null when you set ProxyCreationEnabled to false. That assertion will throw an exception.


If you want then to populate the collections of an entity yet you don't want proxy creations, you need to eager load the collections manually.

var query = questionRep.All.Where(x => x.QuestionId == questionId);

query = query.Include("Answers");
query = query.Include("Comments");
query = query.Include("Answers.Comments");

retrievedQuestion = query.Single();

Assert.AreEqual(expectedCount, retrievedQuestion.Answers.Count);


retrievedQuestion.Answers will not be null anymore, Assert.AreEqual can now do its job.

Make catching errors an enjoyable experience

When using Visual Studio, to put a try catch block on your code, highlight the concern code, press Ctrl+K+S, then type: try

Friday, August 12, 2011

NHibernate and Entity Framework error details

Sometimes, NHibernate produces clearer error message than Entity Framework.

Entity Framework, level of error detail is sounding like an alibi:

Test method TestProject.TestTheIRepository.Ef_CanUpdate threw exception: 
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. ---> System.Data.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Invalid column name 'Product_ProductId'.

While NHibernate is very spot-on from where the error came from:

Test method TestProject.TestTheIRepository.Nh_CanUpdate threw exception: 
NHibernate.Exceptions.GenericADOException: could not insert: [TestProject.SampleModel.ProductPrice][SQL: INSERT INTO [ProductPrice] (EffectiveDate, Price, Product_ProductId) VALUES (?, ?, ?); select SCOPE_IDENTITY()] ---> System.Data.SqlClient.SqlException: Invalid column name 'Product_ProductId'.

Check your Entity Framework mapping or your database, e.g. see if the mapped column on your Entity Framework class' property is existing on your database table.

How to prevent your extension methods from polluting the base libary types?

I got constants and some useful Helper(extension methods) that makes sense on my component(s) domain only.

On common assembly, I have these common constants and helpers

Assembly A:


namespace Ienablemuch.ToTheEfnhX
{
    public static class RepositoryConstants
    {
        // rationales: 
        // http://msdn.microsoft.com/en-us/library/ms182256(v=vs.80).aspx
        // http://10rem.net/articles/net-naming-conventions-and-programming-standards---best-practices
        public readonly static string IdSuffix = "Id";


        public readonly static string RowversionName = "RowVersion";

    }


    public static class Helper
    {
        public static object GetDefault(this Type type)
        {
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }


        public static void DetectIdType(this Type ent, string primaryKeyName, object id)
        {
            if (ent.GetProperty(primaryKeyName).PropertyType != id.GetType())
                throw new IdParameterIsInvalidTypeException("Id is invalid type: " + id.GetType() + ", didn't matched the repository's primary key. Most IDs are of primitive type. Contact the dev to fix this problem");
        }
    }
}


namespace Ienablemuch.ToTheEfnhX
{
    public interface IRepository<TEnt> where TEnt : class
    {
        IQueryable<TEnt> All { get; }
        
        void Save(TEnt ent, byte[] version);
        void Merge(TEnt ent, byte[] version);
        TEnt Get(object id);
        void Delete(object id, byte[] version);
        void DeleteCascade(object id, byte[] version);
        void Evict(object id);

        TEnt LoadStub(object id);

        string PrimaryKeyName { get; set; }
        string VersionName { get; set; }
    }
}

Here are the implementors of that repository interface:

Assembly B:
namespace Ienablemuch.ToTheEfnhX.EntityFramework
{
    public class EfRepository<TEnt> : IRepository<TEnt> where TEnt : class
    {
        DbContext _ctx = null;
        public EfRepository(DbContext ctx)
        {
            _ctx = ctx;
            // Convention-over-configuration :-)
            PrimaryKeyName = typeof(TEnt).Name + RepositoryConstants.IdSuffix;
            VersionName = "RowVersion";
        }
        
        public string PrimaryKeyName { get; set; }
        public string VersionName { get; set; }
        
        public void Delete(object id, byte[] version)
        {
            try
            {
                typeof(TEnt).DetectIdType(PrimaryKeyName, id);
                Evict(id);
                _ctx.Set<TEnt>().Remove(LoadDeleteStub(id, version));
                _ctx.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                throw new DbChangesConcurrencyException();
            }
        }
    }    
}    


Assembly C:
namespace Ienablemuch.ToTheEfnhX.NHibernate
{
    
    public class NhRepository<TEnt> : IRepository<TEnt> where TEnt : class
    {

        ISession _session = null;

       
        public NhRepository(ISession sess)
        {
            _session = sess;
            PrimaryKeyName = typeof(TEnt).Name + RepositoryConstants.IdSuffix;
            VersionName = RepositoryConstants.RowversionName;


            
        }
        
        public string PrimaryKeyName { get; set; }
        public string VersionName { get; set; }
        
        
        public void Delete(object id, byte[] version)
        {
            try
            {
                typeof(TEnt).DetectIdType(PrimaryKeyName, id);
                using (var tx = _session.BeginTransaction())
                {
                    object objStub = LoadDeleteStub(id, version);
                    _session.Delete(objStub);
                    tx.Commit();
                }
            }
            catch (StaleObjectStateException)
            {
                throw new DbChangesConcurrencyException();
            }
        }
    }
}



The Type's GetDefault and DetectIdType are potential polluters on base library types, especially the DetectId type, it's only useful on the component's domain.

At first attempt, I'm thinking of making these constants and helpers be made as internals..

namespace Ienablemuch.ToTheEfnhX
{

    internal static class RepositoryConstants
    {
        internal readonly static string IdSuffix = "Id";
        internal readonly static string RowversionName = "RowVersion";
    }


    internal static class Helper
    {
        internal static object GetDefault(this Type type)
        {
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }


        internal static void DetectIdType(this Type ent, string primaryKeyName, object id)
        {
            if (ent.GetProperty(primaryKeyName).PropertyType != id.GetType())
                throw new IdParameterIsInvalidTypeException("Id is invalid type: " + id.GetType() + ", didn't matched the repository's primary key. Most IDs are of primitive type. Contact the dev to fix this problem");
        }
    }
}



..,then make Ienablemuch.ToTheEfnhX.NHibernate and Ienablemuch.ToTheEfnhX.EntityFramework as friend assemblies of repository interface(on Assembly A), putting these on assembly A:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Ienablemuch.ToTheEfnhX.NHibernate")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Ienablemuch.ToTheEfnhX.EntityFramework")]

Prior to that, I attempted if it's possible to include wildcard on assembly name, tee hee :D

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Ienablemuch.ToTheEfnhX.*")]

Alas no joy, that didn't work.


However, as any discerning component makers will notice, using friend assemblies will produce tight coupling between interface and its implementors, as you have to specify what specific assembly can be a friend of your assembly. What will happen if there's another ORM vendor who wanted to implement your repository interface? if you used friend assemblies that would entail on your part to recompile repository interface to enable new vendor(s) on implementing your repository interface, which is a complicated routine to do so, entails coordination too. So we must not use friend assemblies, don't use InternalsVisibleTo. The only case that InternalsVisibleTo makes sense is when you don't want anyone to inherit functionalities from your components.



On a bit related note, though it's a bit of disappointment that wildcard on InternalsVisibleTo is not allowed, I think it's a good decision on Microsoft part, politically-wise you don't want to implement someone's interface and at the same time you must rigidly follow their assembly's name just for you to able to use their constants and extension methods sauce, isn't it?



So, the easiest way is just make things public, so those constants and helpers will be available to any vendors who wanted to implement your components. But as most of us wanted to avoid, we don't want components that pollutes the base library types with extension methods that are very domain-specific or has very localized uses only.



However, making things public is the first step to the solution, the last step is to put those RepositoryConstants and Helpers to their own namespace. That way, those constants won't be accidentally exposed to the end-user devs, and those extension methods won't be accidentally polluting the base library types. Alleviating your worries and/or frustrations on non-conforming citizen components.



namespace Ienablemuch.ToTheEfnhX.ForImplementorsOnly
{

	public static class RepositoryConstants
	{
		public readonly static string IdSuffix = "Id";
		public readonly static string RowversionName = "RowVersion";
	}


	public static class Helper
	{
		public static object GetDefault(this Type type)
		{
			if (type.IsValueType)
			{
				return Activator.CreateInstance(type);
			}
			return null;
		}


		public static void DetectIdType(this Type ent, string primaryKeyName, object id)
		{
			if (ent.GetProperty(primaryKeyName).PropertyType != id.GetType())
				throw new IdParameterIsInvalidTypeException("Id is invalid type: " + id.GetType() + ", didn't matched the repository's primary key. Most IDs are of primitive type. Contact the dev to fix this problem");
		}
	}
}

Then on an implementor side, they can use the constants and helpers by using a particular namespace.

using Ienablemuch.ToTheEfnhX.ForImplementorsOnly;

namespace Ienablemuch.ToTheEfnhX.EntityFramework
{
    public class EfRepository<TEnt> : IRepository<TEnt> where TEnt : class
    {
        DbContext _ctx = null;
        public EfRepository(DbContext ctx)
        {
            _ctx = ctx;
            // Convention-over-configuration :-)
            PrimaryKeyName = typeof(TEnt).Name + RepositoryConstants.IdSuffix;
            VersionName = "RowVersion";
        }
        
        public string PrimaryKeyName { get; set; }
        public string VersionName { get; set; }
        
        public void Delete(object id, byte[] version)
        {
            try
            {
                typeof(TEnt).DetectIdType(PrimaryKeyName, id);
                Evict(id);
                _ctx.Set<TEnt>().Remove(LoadDeleteStub(id, version));
                _ctx.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                throw new DbChangesConcurrencyException();
            }
        }
    }    
}    


Then on end-user devs, the following will be the typical code they will write. Notice that on end-user devs side, typically, they will not have a need to use the namespace Ienablemuch.ToTheEfnhX.ForImplementorsOnly, the constants will not be accidentally leaked to end-user devs, and extension methods will not pollute the base library types then. Though we cannot enforce end-user devs from using the namespace ForImplementorsOnly, at least it would take a conscious effort on their part if they wish to do so.

using Ienablemuch.ToTheEfnhX;
// Notice the end-user dev didn't include the ForImplementorsOnly. Constants
// and extension methods that makes sense to component implementors(e.g.  Ienablemuch.ToTheEfnhX.NHibernate) only,
// won't be exposed to end-user devs.
using Ienablemuch.ToTheEfnhX.NHibernate;
using Ienablemuch.ToTheEfnhX.EntityFramework;
using Ienablemuch.ToTheEfnhX.Memory;

namespace TestProject
{
    [TestClass()]
    public class TestTheIRepository
	{
		[TestMethod]
		public void Memory_CanSave()
		{
			IRepository<Product> db = new MemoryRepository<Product>();
			Common_CanSave(db);
		}
		[TestMethod]
		public void Ef_CanSave()
		{
			EmptyDatabase();
			IRepository<Product> db = new EfRepository<Product>(new EfDbMapper(connectionString)); 
			Common_CanSave(db);
		}
		[TestMethod]
		public void Nh_CanSave()
		{
			EmptyDatabase();
			IRepository<Product> db = new NhRepository<Product>(NhModelsMapper.GetSession(connectionString));
			Common_CanSave(db);                
		}
		void Common_CanSave(IRepository<Product> db)
		{            
			db.Save(new Product { ProductName = "Optimus", Category = "Autobots", MinimumPrice = 7 }, null);
			db.Save(new Product { ProductName = "Bumble Bee", Category = "Autobots", MinimumPrice = 8 }, null);
			db.Save(new Product { ProductName = "Megatron", Category = "Decepticon", MinimumPrice = 9 }, null);
						

						
			Assert.AreEqual(7 + 8 + 9, db.All.Sum(x => x.MinimumPrice));
			Assert.AreEqual(3, db.All.Count());
		}
	}
}

Thursday, August 11, 2011

Handling Sql Server script errors gracefully

I think there's nothing we can do about Sql Server treatment with DDL error severity, some of it are handled automatically (forcibly rolling back transaction for example) by Sql Server itself.

What we can just do is make our script code cope around it and provide script users with descriptive error.

An example:

--	drop table thetransformersmorethanmeetstheeye
--	select * from thetransformersmorethanmeetstheeye



--	first batch begins here			

	begin tran
			
	create table thetransformersmorethanmeetstheeye(i int); -- non-erring if not yet existing
	
	-- even there's an error here, @@ERROR will become 0 on next batch
	ALTER TABLE [dbo].[Table1]  WITH CHECK ADD  CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2])
	REFERENCES [dbo].[Table2] ([field3], [field4]);				
	
go	-- first batch ends here



--	second batch begins here

	if @@TRANCOUNT > 0 begin		
		PRINT 'I have a control here if things needed be committed or rolled back';
		
		-- @@ERROR is always zero here, even there's an error before the GO batch. 
		-- @@ERROR cannot span two batches, it's always gets reset to zero on next batch
		PRINT @@ERROR; 
		
				
		-- But you can choose whether to COMMIT or ROLLBACK non-erring things here
		-- COMMIT TRAN;
		-- ROLLBACK TRAN;
			
	end
	else if @@TRANCOUNT = 0 begin
		PRINT 'Sql Server automatically rollback the transaction. Nothing can do about it';
	end
	else begin
		PRINT 'Anomaly occured, @@TRANCOUNT cannot be -1, report this to Microsoft!';
	end
	
--	second batch implicitly ends here	

Tuesday, August 9, 2011

How to make your controller unit-testing friendly

There are some code that don't have anything to do with program's correctness, and could fail your unit test, and you need to conditionally suppress them. Case in point, you cannot apply eager loading on mocked IQueryable with NHibernate's FetchMany, you'll will encounter this error when you do so.

System.InvalidOperationException: There is no method 'FetchMany' on type 'NHibernate.Linq.EagerFetchingExtensionMethods' that matches the specified arguments

Don't do this:
public MovieController : Controller
{
    
    IRepository<Movie> _db;
    public MovieController(IRepository<Movie> db)
    {
        _db = db;
    }

    public ViewResult ShowMovieInfo(int id)
    {
        var query = 
                (from x in _db.All
                where x.MovieId == id 
                select x).FetchMany(x => x.Quotes);

        var movie = query.Single();
        return View(movie);
    }
}

When you perform unit testing on your controller, it will fail on FetchMany; NHibernate will throw an exception when the IQueryable(the All property of _db repository) didn't came from NHibernate, e.g. mocked IQueryable.

To make your controller unit testing-friendly, isolate and conditionally suppress anything that are not a factor on program correctness. NHibernate's FetchMany is one of those, even without FetchMany you can verify the program correctness. FetchMany is for performance optimization only.


Do this instead:

public MovieController : Controller
{
    
    IRepository<Movie> _db;
    public MovieController(IRepository<Movie> db)
    {
        _db = db;
    }

    public ViewResult ShowMovieInfo(int id)
    {
        var query = 
                from x in _db.All
                where x.MovieId == id 
                select x;

        if (query.Provider.GetType() == typeof(NHibernate.Linq.NhQueryProvider))
            query = query.FetchMany(x => x.Quotes);

        var movie = query.Single();
        return View(movie);
    }
}


Note: On NHibernate 3.2, there's no more NhQueryProvider, it's replaced with DefaultQueryProvider

Don't initialize collection on your POCO/POJO constructor, it's dangerous on session.Merge

If you received this kind of error in NHibernate..

NHibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: TestProject.SampleModel.Product.PriceList


.., chances are you are using session.Merge on your code, an example code:


public void Save(Ent ent, byte[] version)
{
	try
	{
		using (var tx = _session.BeginTransaction())
		{
			object pkValue =
				typeof(Ent).InvokeMember(
					PrimaryKeyName,
					System.Reflection.BindingFlags.GetProperty, null, ent, new object[] { });

			
			_session.Evict(_session.Load<Ent>(pkValue));


			Type entType = typeof(Ent);
			entType.InvokeMember(VersionName, System.Reflection.BindingFlags.SetProperty, null, ent, new object[] { version });
			
			Ent retObject = (Ent) _session.Merge(ent);                                        
			
			tx.Commit();
						
			object pkGenerated = typeof(Ent).InvokeMember(PrimaryKeyName, System.Reflection.BindingFlags.GetProperty, null, retObject, new object[]{});
			typeof(Ent).InvokeMember(PrimaryKeyName, System.Reflection.BindingFlags.SetProperty, null, ent, new object[] {  pkGenerated });

			object retRowVersion = typeof(Ent).InvokeMember(VersionName, System.Reflection.BindingFlags.GetProperty, null, retObject, new object[] { });
			typeof(Ent).InvokeMember(VersionName, System.Reflection.BindingFlags.SetProperty, null, ent, new object[] { retRowVersion });
			
			
		}
	}
	catch (StaleObjectStateException)
	{
		throw new DbChangesConcurrencyException();
	}
}



[TestMethod]
public void Common_CanUpdate(IRepository<Product> db)
{
	// Arrange            	
	var px = new Product { ProductName = "Bumble Bee", Category = "Autobots", MinimumPrice = 8 };
	db.Save(px, null);
	
	var fromWeb = 
		new Product 
		{ 
			ProductId = px.ProductId, 
			ProductName = px.ProductName, 
			Category = px.Category, 
			MinimumPrice = px.MinimumPrice, 
			RowVersion = px.RowVersion 
		};

	// Act
	string expecting = "Bumble Bee Battle Mode";
	fromWeb.ProductName = expecting;         
	db.Save(fromWeb, fromWeb.RowVersion); // this line causes the NHibernate.HibernateException above
	
	
	// Assert            
	Assert.AreEqual(expecting, db.Get(fromWeb.ProductId).ProductName);
	Assert.AreNotEqual(px.ProductName, db.Get(fromWeb.ProductId).ProductName);	
}

This produces an exception on session.Merge:
var fromWeb = 
	new Product 
	{ 
		ProductId = px.ProductId, 
		ProductName = px.ProductName, 
		Category = px.Category, 
		MinimumPrice = px.MinimumPrice, 
		RowVersion = px.RowVersion

		// Collection initializer is missing here
	};

NHibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: TestProject.SampleModel.Product.PriceList

To fix that exception, you must explicitly set your collections, then the exception will be gone.

Depending on your program requirements, you may either do this:

var fromWeb = 
	new Product 
	{ 
		ProductId = px.ProductId, 
		ProductName = px.ProductName, 
		Category = px.Category, 
		MinimumPrice = px.MinimumPrice, 
		RowVersion = px.RowVersion,
		
		PriceList = new List<ProductPrice>()		
	};	

Or this:

var fromWeb = 
	new Product 
	{ 
		ProductId = px.ProductId, 
		ProductName = px.ProductName, 
		Category = px.Category, 
		MinimumPrice = px.MinimumPrice, 
		RowVersion = px.RowVersion,
		
		PriceList = px.PriceList
	};	



Though you might be tempted to initialize collections on your constructor, it's a dangerous thing to do so when using session.Merge..

public class Product
{
	public virtual int ProductId { get; set; }
	public virtual string ProductName { get; set; }
	public virtual string Category { get; set; }        
	public virtual decimal MinimumPrice { get; set; }

	public virtual IList<ProductPrice> PriceList { get; set; }
	
	public virtual byte[] RowVersion { get; set; }
	
	public Product()
	{
		PriceList = new List<ProductPrice>();
	}
}



..,the corresponding database rows of children entities will be emptied by NHibernate when you forgot to populate the List items

[TestMethod]
public void Common_CanUpdate(IRepository<Product> db)
{


    // Arrange                
    var px = new Product
        {
            ProductName = "Bumble Bee",
            Category = "Autobots",
            MinimumPrice = 8
        };
    px.PriceList =
        new List<ProductPrice>()
        {
            new ProductPrice { Product = px, Price = 234, EffectiveDate = DateTime.Today },
            new ProductPrice { Product = px, Price = 300, EffectiveDate = DateTime.Today.AddDays(100) }
        };    


    
        db.Save(px, null);
    
    var fromWeb = 
        new Product 
        { 
            ProductId = px.ProductId, 
            ProductName = px.ProductName, 
            Category = px.Category, 
            MinimumPrice = px.MinimumPrice, 
            RowVersion = px.RowVersion 
            
            // PriceList collection was not set here. However, it was automatically inititialized in constructor
        };

    // Act
    string expecting = "Bumble Bee Battle Mode";
    fromWeb.ProductName = expecting;         
    db.Save(fromWeb, fromWeb.RowVersion); 
    
    // session.Merge won't have any problem if the PriceList is not null. 
    // However, NHibernate will empty the corresponding database
    // rows of your children entities if the List has no items. Really dangerous
        
    
    
    // Assert            
    Assert.AreEqual(expecting, db.Get(fromWeb.ProductId).ProductName);
    Assert.AreNotEqual(px.ProductName, db.Get(fromWeb.ProductId).ProductName);    
}

To err on the safe side, don't initialize any collections on your POCO's constructor. Let the exception be an indicator of code smell in your program, you cannot make the exception go away by merely suppressing it; and sometimes it introduces bigger problem if you do so.

Monday, August 8, 2011

Max problem with empty IQueryable

Max receives an error on IQueryable if it has no rows

decimal ny = _repoProduct.GetAll()
    .Where(x => x.ProductId != x.ProductId)
    .Max(x => x.MinimumPrice);

On IQueryable obtained from array or anything in-memory list, this is the error:

System.InvalidOperationException: Sequence contains no elements

On IQueryable obtained from NHibernate:
NHibernate.Exceptions.GenericADOException: Could not execute query[SQL: SQL not available] ---> System.ArgumentNullException: Value cannot be null.

On IQueryable obtained from Entity Framework:
System.InvalidOperationException: The cast to value type 'Decimal' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.

The easiest way to rectify that problem is to make the MinimumPrice in your model class as nullable decimal. But that could be a problem if your views has dependent logic on the nullness of the property of your model. Another approach is to cast the target element to nullable type before passing it to aggregate function.

decimal nx = _repoProduct.GetAll()
    .Where(x => x.ProductId != x.ProductId)
    .Select(x => new { TheMinimumPrice = (decimal?)x.MinimumPrice })
    .Max(x => x.TheMinimumPrice) ?? 0;

Since we have only one element on Select, the Linq can be shortened to:
decimal nx = _repoProduct.GetAll()
    .Where(x => x.ProductId != x.ProductId)
    .Select(x => (decimal?)x.MinimumPrice)
    .Max() ?? 0;

NHibernate is testable too

On my last post regarding Entity Framework is infinitely testable, we can also do that on NHibernate too. We just need to suppress NHibernate from performing its fetching function when the provider of the given IQueryable is not of NHibernate type.

This is the code to accomplish that:

public static class UnitTestFriendlyNhFetching
{
    public static IQueryable<TOriginating> NhxFetch<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector)        
    {
        // Prior to NHibernate 3.2, use NhQueryProvider
        // (query.Provider is NHibernate.Linq.NhQueryProvider)

        if (query.Provider is NHibernate.Linq.DefaultQueryProvider)
            return query.Fetch(relatedObjectSelector);
        else
            return query;
    }

    
    public static IQueryable<TOriginating> NhxFetchMany<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, IEnumerable<TRelated>>> relatedObjectSelector)
    {
        // Prior to NHibernate 3.2, use NhQueryProvider
        // (query.Provider is NHibernate.Linq.NhQueryProvider)

        if (query.Provider is NHibernate.Linq.DefaultQueryProvider)
            return query.FetchMany(relatedObjectSelector);
        else
            return query;
    }
}


Example use:

public class MovieController : Controller
{
    IRepository<Movie> _rm = null;
 
    public HomeController(IRepository<Movie> rm)
    {            
        _rm = rm;
    }
 
    public ViewResult Index()
    {        
        // NHibernate NhxFetchMany won't complain even if _rm.All came from mocked IQueryable only
        return View(_rm.All.Where(x => x.Title == "Thor")
            .NhxFetchMany(x=> x.Quotes));    
    }    
}

This is the repository interface
public interface IRepository<Ent> where Ent : class
{
    IQueryable<Ent> All { get; }
    void Save(Ent ent, byte[] version);
    void VersionedDelete(object id, byte[] version);
    Ent LoadStub(object id);
 
    string PrimaryKeyName { get; set; }
    string VersionName { get; set; }
}

The code above can work on aggregate root only, i.e. it can only fetch the children and parent of a given aggregate root, but it cannot further fetch the grandchildren of an aggregate root; ThenFetch and ThenFetchMany can only be chained after Fetch and FetchMany.

If we can find how to cast IQueryable to NHibernate's INhFetchRequest<TOriginating, TRelated>, then we can include unit testing-friendly extension methods for grandchildren entities