Sunday, November 27, 2011

Flexigrid CRUD(inline form) with ASP.NET MVC

In this post, I'll show you the supporting code for placing a form within flexigrid.


First, declare a variable for the html string of your form:

<script type="text/javascript">
    var formHtml = "";
    

    function canRender() {
        return _canRender;
    }

    $(function () {
        _canRender = false;
    });

</script>


We use canRender so we can defer the execution of javascript of the form's html string

<div id="editor" style="visibility: hidden">
    @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "theForm" }))
    {
        <fieldset>
            <legend>Person</legend>

            @Html.HiddenFor(x => x.PersonId)
            @Html.HiddenFor(x => x.RowVersion)

            <div class="editor-label">
                @Html.LabelFor(x => x.Username)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.Username)
                @Html.ValidationMessageFor(x => x.Username)
            </div>


            <div class="editor-label">
                @Html.LabelFor(x => x.Firstname)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.Firstname)
                @Html.ValidationMessageFor(x => x.Firstname)
            </div>

            <div class="editor-label">
                @Html.LabelFor(x => x.Lastname)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.Lastname)
                @Html.ValidationMessageFor(x => x.Lastname)
            </div>


            <div class="editor-label">
                @Html.LabelFor(x => x.Country.CountryId)
            </div>
            <div class="editor-field">

                <table style="border-style: none">
                <tr>
                <td style="border-style: none">
                @Html.AjaxComboBoxFor(x => x.Country.CountryId, "/Countries/Management/Lookup/", "/Countries/Management/Caption/",
                    new { }, new { sub_info = true, can_render_callback = "canRender", textbox_width = 200 })    
                </td>
                <td style="border-style: none">
                @Html.ValidationMessageFor(x => x.Country.CountryId)
                </td>
                </tr>
                </table>                     
            </div>
            

            <div class="editor-label">
                @Html.LabelFor(x => x.FavoriteNumber)
            </div>
            <div class="editor-field">
                @Html.EditorFor(x => x.FavoriteNumber)
                @Html.ValidationMessageFor(x => x.FavoriteNumber)                
            </div>

            <p>
                <input type="submit" value="Save" />
                <input type="button" id="Closer" value="Close" />
            </p>


            
        </fieldset>
        
        <div style="max-width: 500px; width: 500px;">
            @Html.JsAccessibleValidationSummary(excludePropertyErrors: true)            
        </div>
    }
<script type="text/javascript">

    $(function () {


        if (!canRender()) return;


        var scope = $('#theForm');
        parseDynamicContent(scope);




        $('#Closer', scope).click(function (e) {
            closeForm($(scope));
        });

        $('input[type=submit]', scope).click(function (e) {
            try {


                e.preventDefault();

                if (!scope.valid()) {
                    // alert('has invalid');
                    return;
                }

                save(scope);

                // closeForm(scope);
            } catch (e) {
                alert("Error " + e);
            }

        });

        $(scope).attr('id', guid());
    });

    function save(scope) {        

        $.ajax({
            url: '/People/Management/SaveViaAjax',
            type: 'POST',
            data: $(scope).serialize(),
            success: function (result) {

                var isOk = $(scope).modelValidation(result);


                if (isOk) {

                    var isNew = $('#PersonId', scope).val() == '';

                    if (isNew) {
                        $('#PersonId', scope).val(result.PersonId);
                    }

                    $('#RowVersion', scope).val(result.RowVersion);


                    if (isNew) {
                        $(scope).closest('table').flexReload();
                    }
                    else {


                        setFgEditText(scope, 'Username', $('#Username', scope).val());
                        setFgEditText(scope, 'Firstname', $('#Firstname', scope).val());
                        setFgEditText(scope, 'Lastname', $('#Lastname', scope).val());
                        setFgEditText(scope, 'FavoriteNumber', $('#FavoriteNumber', scope).val());

                        setFgEditText(scope, 'Country', $('#Country_CountryId', scope).ajc().getText());

                        closeForm(scope);
                    }

                }


                // $('#firstTable').flexReload();



            },
            error: function (a, b, c) {
                alert(a.statusText);
                alert(b);
                alert(c);
            }
        });                       //ajax
    }//save

</script>
</div>


Here's the flexigrid setup and getting the form's html string:

<script type="text/javascript">


    $(function () {
        // main..
        setupForm();

        setupFirstTable();
        

        
        // ..main
    });


    function setupForm() {
        formHtml = $('#editor').html();
        $('#editor').remove();        
    }

    function setupFirstTable() {
        $('#firstTable').flexigrid({
            url: '/People/Management/List',
            dataType: 'json',
            colModel: [
                    { display: 'User', name: 'Username', width: 150, sortable: true, align: 'left' },
                    { display: 'Firstname', name: 'Firstname', width: 150, sortable: true, align: 'left' },
                    { display: 'Lastname', name: 'Lastname', width: 150, sortable: true, align: 'left' },
                    { display: 'Favorite#', name: 'FavoriteNumber', width: 150, sortable: true, align: 'left' },
                    { display: 'Country', name: 'Country', width: 150, sortable: true, align: 'left' },
                    { display: 'RowVersion', name: 'RowVersion', width: 150, sortable: true, align: 'left', hide: true }
                ],

            buttons: [
                    { name: 'Add', bclass: 'add', onpress: add },
                    { separator: true },
                    { name: 'Edit', bclass: 'edit', onpress: edit },
                    { separator: true },
                    { name: 'Delete', bclass: 'delete', onpress: del }
                ],

            singleSelect: true,
            sortname: 'Lastname',
            sortorder: 'asc',
            usepager: true,
            title: 'Persons',
            useRp: true,
            rp: 5,
            rpOptions: [5, 10, 15, 20, 25, 40],
            showTableToggleBtn: true,
            width: 900,
            height: 'auto',
            preProcess: function (data) {
                var rp = getFgRowsPerPage($('#firstTable'));
                for (i = data.rows.length; i < rp; ++i) {
                    data.rows.push({ 'id': '', 'cell': ['', '', '', '', '', ''] });
                }                
                return data;
            }
        });    // flexigrid

        setupGrid($('#firstTable'));

    } //setupFirstTable



    function add(com, grid) {
        try {            
            closeFormByGrid(grid);            
            showAddFormByGrid(grid, formHtml);            
        } catch (e) {
            alert('error ' + e);
        }
    }


    function edit(com, grid) {

        closeFormByGrid(grid);


        var items = $('.trSelected', grid);
        var item = items[0];
        var pk = item.id.substr(3);

        if (pk.length == 0) return;

        

        $.ajax({
            url: '/People/Management/GetUpdated/' + pk,
            type: 'POST',
            success: function (data) {



                showEditForm(item, formHtml, function () {

                    var form = $('form', grid);


                    $('#PersonId', form).val(data.Record.PersonId);
                    $('#Username', form).val(data.Record.Username);
                    $('#Firstname', form).val(data.Record.Firstname);
                    $('#Lastname', form).val(data.Record.Lastname);


                    $('input[id=Country_CountryId]', form).val(data.Record.CountryId);
                    $('#FavoriteNumber', form).val(data.Record.FavoriteNumber);
                    $('#RowVersion', form).val(data.Record.RowVersion);

                    
                    $('#Country_CountryId', form).ajc().showCaption();
                    

                    setFgEditText(grid, 'Username', data.Record.Username);
                    setFgEditText(grid, 'Firstname', data.Record.Firstname);
                    setFgEditText(grid, 'Lastname', data.Record.Lastname);                    
                    setFgEditText(grid, 'FavoriteNumber', data.Record.FavoriteNumber);


                }); //showEditForm

            } //success
        }); //ajax
    }//edit

    function del(com, grid) {

        var deleteIt = confirm('Do you want to delete the selected record?');

        if (!deleteIt) return;

        var pk = getCurrentRowPk(grid);
        var version = getFgGridColumnText(grid, 'RowVersion');

        // alert(pk + " " + version + " " + encodeURIComponent(version));


        $.ajax({
            url: '/People/Management/Delete',
            type: 'POST',
            data: 'pk=' + pk + '&version=' + encodeURIComponent(version),
            success: function (result) {
                if (result.IsOk) {
                    $('#firstTable').flexReload();
                }
            }
        });

    }


</script>


Lastly we put this at the end of the html:
<script>
    $(function () {        
        _canRender = true;
    });

</script>


Here's the Flexigrid form-inliner helper functions:

function showEditForm(selectedTr, html, assignerFunc) {

    $(selectedTr).after('<tr class="fgEdit" editId=' + selectedTr.id + '><td width="1" colspan="20" style="border-width: thin; border-top: thick; border-color: #EEE; white-space: normal;"><span></span></td></tr>');
    

    var content = $('td > span', $(selectedTr).next());

    // var form = $(content).hide().html(html);
    var form = $(content).html(html);
    
    assignerFunc();
    
    $(content).show();
    
}


function showAddFormByGrid(grid, formHtml) {

    var tbl = $('.bDiv table', grid);
    showAddForm(tbl, formHtml);    
}

function showAddForm(tbl, formHtml) {
    var tbody = $('tbody', tbl);
    if (tbody.length == 0) {
        $(tbl).append($('<tbody/>'));
        tbody = $('tbody', tbl);
    }
    

    $(tbody).prepend('<tr class="fgEdit"><td width="1" colspan="20" style="border-width: thin; border-top: thick; border-color: #EEE; white-space: normal"><span></span></td></tr>');
    var content = $('tr td span', tbody);
        
    $(content).html(formHtml);


}





Flexigrid CRUD(inline form) example with ASP.NET MVC, complete with model validation:
http://code.google.com/p/flexigrid-crud-example/downloads/list


Known issues:

1. Not with flexigrid per se; but when inside of flexigrid, the jQuery Ajax ComboBox's result area is far from its textbox when using Firefox or IE, have made a work-around in Chrome though. If you could lend a help in correcting that problem of jQuery Ajax ComboBox, I'll be more than glad to accept a patch.

2. If the flexigrid has no rows, the inline form is not in full width. Accepting patch for flexigrid code or helper code.



Sample output


jQuery Ajax ComboBox's detached result screenshot(IE and Firefox problem)

$.post returns incorrect empty string

You'll receive [object XMLDocument] in your jQuery $.post's data ...

var $xhr2 = $.post( 
 options.init_src,
 {
  'lookupId': theValue,
  'field': options.field,
  'primary_key': options.primary_key,
  'db_table': options.db_table
 },
 function (data) {
  $input.val(data); // textbox receives [object XMLDocument]  
 }
);


..., when you return empty string from your controller:

public string Caption(string lookupId)
{
 return "";
}


In order to prevent that problem, just add "text" parameter to your $.post method return type


var $xhr2 = $.post( 
 options.init_src,
 {
  'lookupId': theValue,
  'field': options.field,
  'primary_key': options.primary_key,
  'db_table': options.db_table
 },
 function (data) {
  $input.val(data); // receives "" string now.  
 },
 "text"
);

Monday, October 31, 2011

Not all Linq provider approaches are the same

Whereas NHibernate Linq don't have a problem immediately reaching C#'s functions..

public JsonResult GetUpdated(Guid id)
{
    var js = new JsonResult();

    js.Data =
        new
        {
            Record = (from p in _person.All
                      where p.PersonId == id
                      select new { 
                          p.PersonId, 
                          p.Username, 
                          p.Firstname, 
                          p.Lastname, 
                          p.FavoriteNumber, 
                          p.Country,
                          RowVersion = CustomStringFunctionHere(p.RowVersion ?? new byte[] { })
                      } ).Single()
        };

    return js;
}

string CustomStringFunctionHere(byte[] a)
{
    return "AAA" + Convert.ToBase64String(a) + "BBB";
}

..Entity Framework does:

LINQ to Entities does not recognize the method 'System.String CustomStringFunctionHere(Byte[])' method, and this method cannot be translated into a store expression.


NHibernate do the projection differently, its select expression is not being translated to its store equivalent.

Sunday, October 23, 2011

Updates on jQuery Ajax ComboBox ASP.NET MVC Helper

Added string-based name on the helpers.

Download the code from: http://code.google.com/p/ienablemuch-jquery-ajax-combobox-aspnetmvc/downloads/list

Get the demo code at: http://code.google.com/p/jquery-ajax-combobox-aspnet-mvc-helper/downloads/list


Example:

@Html.AjaxComboBox("CategoryId", 
    "/Category/Lookup",
    "/Category/Caption",
    new { style = "width: 300px " },
    new { sub_info = true })


Nothing was changed on strongly-typed helpers, it's still the preferred way:
@Html.AjaxComboBoxFor(model => model.CategoryId,
  "/Category/Lookup",
  "/Category/Caption",
  new { style = "width: 300px " },
  new { sub_info = true })

Wednesday, October 12, 2011

Sample ASP.NET Ajax Leaky Abstraction

Below is the work-around for problem on non-working ASP.NET UpdateProgress(with AssociatedUpdatePanelID) when the control(e.g. Button) that causes the post-back is outside of the UpdatePanel. This is the cause of the error:

http://stackoverflow.com/questions/1187953/updateprogessbar-is-not-working-when-setting-associatedupdatepanelid


This is the solution for ASP.NET AJAX leaky abstraction :-)

http://stackoverflow.com/questions/996957/why-does-update-progress-does-not-fire-when-associatedupdatepanelid-is-set


An implementation:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="WebApplication7._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script type="text/JavaScript" language="JavaScript">
        function pageLoad() {
            var manager = Sys.WebForms.PageRequestManager.getInstance();
            manager.add_beginRequest(OnBeginRequest);
        }
        function OnBeginRequest(sender, args) {
            var postBackElement = args.get_postBackElement();
            if (postBackElement.id == 'MainContent_Button1') {
                var up = $get('MainContent_UpdateProgress1');
                up.style.display = "block";
            }
        }
    </script>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to ASP.NET!
    </h2>
    <p>
        To learn more about ASP.NET visit <a href="http://www.asp.net" title="ASP.NET Website">
            www.asp.net</a>.
    </p>
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="Button1" />
        </Triggers>
        <ContentTemplate>
            <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
        </ContentTemplate>
    </asp:UpdatePanel>
    <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
    <asp:UpdateProgress ID="UpdateProgress1" AssociatedUpdatePanelID="UpdatePanel1" runat="server">
        <ProgressTemplate>
            Loading ...</ProgressTemplate>
    </asp:UpdateProgress>
</asp:Content>


This sort of leaky abstraction on ASP.NET of simulating states in an otherwise stateless protocol is making me embrace MVC+jQuery combo even more.

And someday, you won't get any answer on this type of question on forums, as it will be deemed too localized a few years from now. Five years or more, give or take, most developers will be using MVC+jQuery or node.js+jQuery already, and you will hardly find someone able to answer this ASP.NET type of question.

Monday, October 10, 2011

When is null is not a null? This is not a philosophical SQL question :-)

Answer: When it is still inside of Linq Expression


Linq operation is one of the rare occasion where C# semantics is not of WYSIWYG one.


var employees =
from x in db.Employees
where x.Manager == null
orderby x.LastName
select new { x.FirstName, x.LastName, x.Manager, ManagerName = x.Manager.LastName };
 
GridView1.DataSource = employees;
GridView1.DataBind();



Given the code above, even if x.Manager is null (or just look like one) the code x.Manager.LastName is not an absurd one. x.Manager == null has no bearing on C# nullability. As far as an ORM is concerned, x.Manager == null needed be translated to its equivalent relational syntax, i.e. that code needed be translated to table2.EmployeeId IS NULL. An ORM is able to do that, as the ORM's mappings contains info on what columns connect one table to another table. The x.Manager.LastName in Linq's select clause is also being translated by Linq Expression provider(e.g. ORM), that's why there won't be any null runtime error on x.Manager.LastName when the LastName is being accessed.


Continuing the code above, when using AsEnumerable this code's null..

var z =
from y in employees.AsEnumerable()
where y.Manager == null
select new { y.FirstName, y.LastName, y.Manager, ManagerName = y.Manager.LastName };

GridView1.DataSource = z;
GridView1.DataBind();


..is now a C#'s null. This code will have a null runtime exception on y.Manager.LastName then.



Perhaps to make code analysis a lot simpler, any code that is still in scope of Linq's Expression should be thought of not as a C#'s construct, and as such, are still subject for translation to whatever SQL syntax it needed be translated, C#'s language rules doesn't come into play when an Expression is still in scope. Linq Expression provider could even translate y.Manager == null to any kind of code; much like C++/C# operator overloading, a plus operator could mean minus, a minus could mean plus. And as far as Linq Expression provider is concerned, the code select new { y.FirstName, y.LastName, y.Manager, ManagerName = y.Manager.LastName } could be translated up to the Linq provider's liking and such can be translated to any kind of code, y.Lastname could be automatically translated to an expression that automatically lowercases, e.g. SELECT Lastname = LCASE(LastName), to even a random one :-)


C# rules could only take effect when things are materialized to objects already, e.g. when using AsEnumerable, ToArray, ToList.


This is the ideal Linq code for properties(e.g. FullName from concatenation in C#) in classes that are not present in database:

var employees =
    (from x in db.Employees
    where x.Manager == null
    orderby x.LastName
    select x) // code here and up are being translated to back-end equivalent, i.e. SQL
 
 
    // code here and below, be it Linq-style or Lambda-style, now follows the normal C# rules,
    // note the null coalesce (??) operator
    .AsEnumerable()
    .Select(y => new
        {
        y.FullName, // Lastname and Firstname is concatenated on C# getter
        ManagerName = (y.Manager ?? new NorthwindModel.Employee()).FullName
    }); 

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


    }
}

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; }
}