Saturday, September 24, 2011

Task-based Asynchronous Pattern

Here's a simple implementation of task-based asynchronous pattern

using System;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;

namespace TestAsync
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // inherently synchrononous operation
        public int ComputeSomething(int n)
        {
            // Problem is O(N)

            while (n-- > 0)
                Thread.Sleep(1000);

            return 7;
        }

        // Wrap the synchronous operation on Task-based Async Pattern to make the operation operate asynchronously
        Task<int> ComputeSomethingAsync(int n)
        {
            Task<int> t = new Task<int>(() =>
                {
                    return ComputeSomething(n);
                });

            t.Start();
            return t;
        }



        private void button1_Click(object sender, EventArgs e)
        {
            // instead of doing things synchronously
            if (false)
            {
                int n = ComputeSomething(10);
                MessageBox.Show("Returned value is " + n.ToString());
            }

            // do things asynchronously instead
            if (true)
            {
                Task<int> t = ComputeSomethingAsync(10);
                t.ContinueWith(x =>
                {
                    if (x.IsFaulted)
                    {
                        MessageBox.Show(x.Exception.Message);
                    }
                    else
                    {
                        MessageBox.Show("Returned value is " + x.Result.ToString());
                    }

                });
            }

        }//button1_Click


    }//class Form1
}

For asynchronous operation, TAP asynchronous design pattern is easier to use than BeginXXX, EndXXX, IAsyncResult combo async pattern

On next C# version(C# 5), making routines asynchronous will be a lot easier with its built-in await and async functionality

Thursday, September 15, 2011

string.Empty vs ""

Regarding string.Empty vs "". string.Empty is performant(using this word comes with a risk of sounding like a sales brochure) than "", but that is only prior .NET 2.0. Prior .NET 2.0 (i.e. 1.1 and 1.0), every literal strings even they are similar, are allocated their own slots in memory, hence declaring "" once and pre-assigning it in string.Empty is warranted and thus faster and saves space than merely using "" strings everywhere. string.Empty is faster than "", but that is on .NET 1.0 and 1.1 only

But on .NET 2.0 onwards, every similar literal strings are interned.

Interning is the process of looking at all similar string literals in an executable and making them point to one memory address only, i.e. all similar string literals share the same address.

On .NET 2.0 and onwards, every similar empty strings(any similar strings for that matter, even non-empty ones) doesn't allocate new memory

string.Empty is still nice, it is readable and one can easily glean the code intent. But that is debatable, I believe that "" is more readable than string.Empty. In the same vein that 0 is readable than int.Zero. Any takers of this code? if(i == int.Zero)

And string.Empty could be slower than "" on 2.0 and onwards, string.Empty is another indirection during runtime. "" is just that, a memory address has just one location, never-changing.



using System; 

namespace StringInterning 
{ 
    class Program 
    { 
        unsafe static void Main(string[] args) 
        { 
            string s = "Hello"; 
            string t = "Hello"; 
             

            fixed (char* dst = s) 
            { 
                dst[0] = 'M'; 
                dst[1] = 'i'; 
            } 
             

            Console.Write("Enter greeting: "); 
            string greet = Console.ReadLine(); // try to enter the word: Hello 
            Console.WriteLine(); 


            if (greet == "Hello") 
                Console.WriteLine("Friendly"); // if you enter Hello for greet, this will not print. funneh :D 
            else 
                Console.WriteLine("This will print instead"); // this will print instead 

            Console.WriteLine("S {0}", s); // will print Millo 
            Console.WriteLine("T {0}", t); // will print Millo 
            Console.WriteLine("Hey {0}", "Hello"); // will print Hey Millo 

            Console.WriteLine(); 

            fixed (char* dst = " ") 
            { 
                *dst = 'Y'; 
            } 

            Console.WriteLine("Value of {0}{1}", " ", " "); // this will print YY. funneh :D 


            Console.ReadLine(); 
        } 
    } 

} 

Enter greeting: Hello 

This will print instead 
S Millo 
T Millo 
Hey Millo 

Value of YY 


And the great Jon Skeet uses.., surprise! --> ""

here's his rationale:

http://stackoverflow.com/questions/263191/in-c-should-i-use-string-empty-or-string-empty-or/263257#263257


We can't even use string.Empty on parameters, it will not compile:
public static void Funny(string message = string.Empty) // this will not compile. must use ""
{
    Console.WriteLine("Hahah {0}", message);
}


string.Empty can't even be used on attributes, it will not compile:
[Label(string.Empty)] // this will not compile. must use ""


string.Empty can't even be used on switch, it will not compile:
string s = Console.ReadLine();
switch (s) 
{
case "Hahah":
    break;

case string.Empty: // this will not compile too. must use ""
    break;
}



For those who are dogmatic on using string.Empty. Though it's not your problem, it's the language designer's choice, you have to do some concessions and you shall just write "" anyway.



Perhaps programmers using string.Empty love to code so much ツ

Wednesday, September 14, 2011

C# is a very type-safe language

In C#, type-safety is not only a compile-time thing, it's also type-safe during runtime, this will not compile:

Student s = new Person();

This is not allowed in C++ too, C++ is type-safe too, albeit not as strongly type as C#, which we will discuss later what makes C++ type-safety a bit weaker, this will not compile too:


Student *s = new Person();


Now, let's try to coerce C# to point Student object to Person object, this will compile:

Student s = (Student) new Person();

Let's try the same with C++, this will compile too:

Student *s = (Student *) new Person();



The two codes above, though both will compile, that is where C# and C++ diverge, not only in terms of type safety but also in terms of runtime security(which we will discuss immediately). During runtime, C# will raise an error; whereas C++ will happily do your bidding, it will not raise any error. Now, how dangerous is that kind of code?


Imagine this is your object:

class Person
{
 public int favoriteNumber;
}

class Student : Person
{
 public int idNumber;
}



If you allocate memories for Person, for example, if it so happen that the locations of these memory allocations are adjacent to each other: Person pA = new Person(); Person pB = new Person(); If the runtime will let us point Student s to Person pA, there's a higher chance that the assignments on fields of Student s could overwrite the content of adjacent Person pB object.



To make the principle more concrete, let's do it in a language which leniently allow that sort of thing.

#include <cstdio>


class Person
{
public:
   int favoriteNumber;

};


class Student : public Person
{
public:
 int idNumber;
};


int main()
{
 
 Person *p = new Person[2];
 p[0].favoriteNumber = 7;
 p[1].favoriteNumber = 6;

 printf("\nP1 Fave# %d", p[0].favoriteNumber);
 printf("\nP2 Fave# %d", p[1].favoriteNumber);


 void *objek = (void *) &p[0];
 // above code is equivalent to C#'s:
 // object objek = p[0];
 
 Student *s = (Student *) objek;
 // above code is equivalent to C#'s:
 // Student s = (Student) objek;
 
 
 s->idNumber = 9;  
 printf("\n\n");
 printf("\nS# %d", s->idNumber);
 printf("\nP1 Fave# %d", p[0].favoriteNumber);
 printf("\nP2 Fave# %d", p[1].favoriteNumber);


 p[1].favoriteNumber = 8;
 printf("\n\n");
 printf("\nS# %d", s->idNumber);
 printf("\n\n");
  
}


The output of that code:


P1 Fave# 7
 P2 Fave# 6
 
 
 S# 9
 P1 Fave# 7
 P2 Fave# 9
 
 
 S# 8



Can you spot the anomalies? We assign a value 9 to student's idNumber, yet P2's favoriteNumber also changes. We changed the P2's favoriteNumber, yet student's idNumber also changes. Simply put, Student's field(s) overlap other objects' field(s) location , so that's the problem if a language allows arbitrary pointing of objects to any object type.


.........Person
[0xCAFE] (favoriteNumber) : 7
[0xCAFF] (favoriteNumber) : 6


Student points to first person(which has an adjacent person):

.........Person                Student
[0xCAFE] (favoriteNumber) : 7  (favoriteNumber)
[0xCAFF] (favoriteNumber) : 6  (idNumber)


If a language allows pointing Student to a Person's memory location(0xCAFE), what will happen if we change the value of Student's idNumber? the adjacent second person's favoriteNumber will be changed too, an unintentional corruption of memory. Worse yet, if this is done on purpose, it is a potential security problem. Think if Student can point to any object type, the idNumber could be used to peek other object's contents, even those other object's fields are private

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"));


    }
}