Saturday, October 23, 2010

Troubleshooting NHibernate on MonoDevelop

If you received this kind of error when you run your MonoDevelop NHibernate program even though you already add the necessary dialect in your app.config file ...

Unhandled Exception: NHibernate.MappingException: Could not compile the mapping document: AwesomeNHibernate.RecordCompanyDatabase.hbm.xml ---> System.InvalidOperationException: Could not find the dialect in the configuration

..., just change your app.config's Copy to output directory property from Do not copy to Always copy

Friday, October 22, 2010

NHibernate ORM How-To. The Object, the Relational, the Mapping. In 4 Easy to Follow Steps

1. The Object aspect, sans relational. This will run as is(actually you can skip this Step 1, the Step 2 contains both object and its relational query):

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

namespace AwesomeNHibernate
{
 class Program
 {
  static void Main(string[] args)
  {
   IList<Musician> musicians = SampleMusicianList();                            
     
   foreach (Musician musician in musicians)
   {
    Console.WriteLine("*****\n{0}\n\nAge: {1}\n", musician.MusicianInfo, musician.Age);
   }

   Console.ReadLine();
      
  }


  static IList<Musician> SampleMusicianList()
  {
   return new List<Musician>
   {
    new Musician { MusicianId = 1, MusicianName = "Elvis Presley", BirthYear = 1935, FavoriteInstrument = "Guitar" },
    new Musician { MusicianId = 2, MusicianName = "Jimi Hendrix", BirthYear = 1942, FavoriteInstrument = "Electric Guitar" },
   };
  }

 }//Program

 public class Musician
 {
  public int MusicianId { get; set; }
  public string MusicianName { get; set; }
  public int BirthYear { get; set; }
  public string FavoriteInstrument { get; set; }



  public int Age
  {
   get
   {
    return DateTime.Today.Year - BirthYear;
   }
  }
  
  public string MusicianInfo
  {
   get
   {
    return string.Format("Musician ID: {0}\nName: {1}\nFavorite Instrument: {2}", MusicianId, MusicianName, FavoriteInstrument);
   }
  }        
  
 }//Musician
}//Awesome

2. The Relational aspect. This is a complete ORM code but will not run yet. There's already object-relational stuff happening in this code, but it's not mapped to a live database yet, shall run after you're done with steps 3 and 4.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using NHibernate.Cfg;
using System.Reflection;



namespace AwesomeNHibernate
{
 class Program
 {
     
  static void Main(string[] args)
  {
   IList<Musician> musicians = MusicianList();                            
     
   foreach (Musician musician in musicians)
   {
    Console.WriteLine("*****\n{0}\n\nAge: {1}\n", musician.MusicianInfo, musician.Age);
   }

   Console.ReadLine();
      
  }
 
  
  // Relational...
  static ISession OpenSession()
  {
   var c = new Configuration();
   c.AddAssembly(Assembly.GetCallingAssembly());
   ISessionFactory f = c.BuildSessionFactory();
   return f.OpenSession();
  }


  static IList<Musician> MusicianList()
  {
   using (ISession session = OpenSession())
   {
    IQuery query = session.CreateQuery("from Musician order by BirthYear, MusicianName");

    IList<Musician> musicians = query.List<Musician>();

    return musicians;
   }

  }
  // ...Relational

 }//Program

 public class Musician
 {
  public int MusicianId { get; set; }
  public string MusicianName { get; set; }
  public int BirthYear { get; set; }
  public string FavoriteInstrument { get; set; }



  // The beauty of ORM, it allows you to offload compute-intensive routine from database to front-end(e.g. webserver) or middle-tier        
  public int Age
  {
   get
   {
    return DateTime.Today.Year - BirthYear;
   }
  }

  
  public string MusicianInfo
  {
   get
   {
    return string.Format("Musician ID: {0}\nName: {1}\nFavorite Instrument: {2}", MusicianId, MusicianName, FavoriteInstrument);
   }
  }        
  
 }//Musician
}//Awesome

3. The Mapping aspect

Create RecordCompanyDatabase.hbm.xml file and add it to your project, then put this content...

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="AwesomeNHibernate" namespace="AwesomeNHibernate" >

  <class name="Musician" lazy="false" table="musician">
    <id name="MusicianId" column="musician_id">
      <generator class="sequence">
        <param name="sequence">musician_id_seq</param>
      </generator>
    </id>

    
    <property name="MusicianName" column="musician_name"/>
    <property name="BirthYear" column="birth_year"/>
    <property name="FavoriteInstrument" column="favorite_instrument"/>
    
    
  </class>

</hibernate-mapping>  


After adding that xml file, change the RecordCompanyDatabase.hbm.xml's Build Action property from Content to Embedded Resource. Keyboard shortcut: Ctrl+W+S, Ctrl+W+P, F4(or Alt+Down)



Alternatively, if your NHibernate objects are organized to different assemblies and/or namespace, you can put the explicit namespace name and assembly name in the class' name itself. i.e. class name="NamespacenameHere.ClassnameHere, AssemblynameHere"


<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">

  <class name="AwesomeNHibernate.Musician, AwesomeNHibernate" lazy="false" table="musician">
    <id name="MusicianId" column="musician_id">
      <generator class="sequence">
        <param name="sequence">musician_id_seq</param>
      </generator>
    </id>

    
    <property name="MusicianName" column="musician_name"/>
    <property name="BirthYear" column="birth_year"/>
    <property name="FavoriteInstrument" column="favorite_instrument"/>
    
    
  </class>

</hibernate-mapping>  


4. The database

  • Connection. Put this in your App.config file
  • <?xml version="1.0" encoding="utf-8" ?>
    <configuration>  
      <configSections>
        <section name="hibernate-configuration"   type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
      </configSections>
    
      <hibernate-configuration  xmlns="urn:nhibernate-configuration-2.2" >
        
        <session-factory>
    
          <property name="dialect">NHibernate.Dialect.PostgreSQLDialect</property>      
          <property name="connection.connection_string">Server=localhost;Database=test;User ID=postgres;Password=opensesame;</property>            
          <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
                   
        </session-factory>
                
      </hibernate-configuration>
      
    </configuration>
    
    Note: In order for this to work in MonoDevelop, change app.config's Copy to output directory property from Do not copy to Always copy If you don't want to clutter your app.config, you can put nhibernate settings on separate file instead, NHibernate look its settings from hibernate.cfg.xml file:
    <?xml version="1.0" encoding="utf-8" ?>
    
    <hibernate-configuration  xmlns="urn:nhibernate-configuration-2.2" >
     
     <session-factory>
     
       <property name="dialect">NHibernate.Dialect.PostgreSQLDialect</property>      
       <property name="connection.connection_string">Server=localhost;Database=test;User ID=postgres;Password=opensesame;</property>            
       <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
                 
     </session-factory>
             
    </hibernate-configuration>   
    
    Likewise, change hibernate.cfg.xml's Copy to output directory property from Do not copy to Always copy. And change the OpenSession code to:
    static ISession OpenSession()
       {
        var c = new Configuration(); 
        c.Configure(); // add this, this makes NHibernate look for its settings from hibernate.cfg.xml instead of app.config
        c.AddAssembly(Assembly.GetCallingAssembly());
        ISessionFactory f = c.BuildSessionFactory();     
        return f.OpenSession();
       }
    
    Note the c.Configure();, failing to add that code will result to:
    Unhandled Exception: NHibernate.MappingException: Could not compile the mapping document: AwesomeNHibernate.RecordCompanyDatabase.hbm.xml ---> System.InvalidOperationException: Could not find the dialect in the configuration
    
  • Download Postgres as your database
  • Supporting Components(add these 3 files to your Project's Reference)
    • NHibernate-2.1.2.GA-bin\Required_Bins\NHibernate.dll
    • NHibernate-2.1.2.GA-bin\Required_For_LazyLoading\LinFu\NHibernate.ByteCode.LinFu.dll
    • Npgsql2.0.10-bin-ms.net4.0\Npgsql.dll
    • Download:
  • Database content
  • create table musician
    (
    musician_id serial not null primary key,
    musician_name text,
    favorite_instrument text,
    birth_year int
    );
    
    insert into musician(musician_name, favorite_instrument, birth_year) values
    ('John Lennon','Guitar', 1940),
    ('Paul McCartney', 'Bass Guitar', 1942),
    ('George Harrison', 'Sitar', 1943),
    ('Ringo Starr', 'Drums', 1940);
    


You can now run the project. When we add the .NET 4.0 version of Npgsql, we must also change the Target Framework of our project to 4.0. Another note, if you received a compile-time error that type or namespace NHibernate could not be found even you already add the NHibernate.dll reference to your project, change the Target Framework from .NET Framework 4 Client Profile to .NET Framework 4

Note: To facilitate autocomplete for your NHibernate hbm.xml files, put these two files...

  • NHibernate-2.1.2.GA-bin\Required_Bins\nhibernate-configuration.xsd
  • NHibernate-2.1.2.GA-bin\Required_Bins\nhibernate-mapping.xsd

...to:

C:\Program Files (x86)\Microsoft Visual Studio 10.0\Xml\Schemas




To use the above NHibernate code on Sql Server, just change the three session-factory properties of App.config(or in hibernate.cfg.xml)...


<property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
<property name="connection.connection_string">Data Source=.\SQLEXPRESS;Initial Catalog=RecordCompany;Trusted_Connection=yes</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>

..., and change RecordCompanyDatabase.hbm.xml's Musician's id generator to:

<id name="MusicianId" column="musician_id">
    <generator class="native"/>
</id>    


...And on musician table, change the serial to its Sql Server equivalent

musician_id int identity(1,1) not null primary key

Another note, Postgresql's TEXT type is essentially the same as VARCHAR(MAX). So you also need to change the TEXT types to VARCHAR(MAX), e.g.

create table musician
(
musician_id int identity(1,1) not null primary key,
musician_name varchar(max),
favorite_instrument varchar(max),
birth_year int
);
 
insert into musician(musician_name, favorite_instrument, birth_year) values
('Kurt Cobain','Guitar', 1967),
('Krist Novoselic', 'Bass Guitar', 1965),
('Dave Grohl', 'Sitar', 1969)


If you will use TEXT type on SQL Server, you will encounter this error on your NHibernate query:


ADOException was unhandled

could not execute query
[ select musician0_.musician_id as musician1_0_, musician0_.musician_name as musician2_0_, musician0_.birth_year as birth3_0_, musician0_.favorite_instrument as favorite4_0_ from musician musician0_ order by musician0_.birth_year, musician0_.musician_name ]
[SQL: select musician0_.musician_id as musician1_0_, musician0_.musician_name as musician2_0_, musician0_.birth_year as birth3_0_, musician0_.favorite_instrument as favorite4_0_ from musician musician0_ order by musician0_.birth_year, musician0_.musician_name]

And it's not NHibernate's query fault. NHibernate just translates the HQL(Hibernate Query Language) to your RDBMS's SQL dialect. Sql Server cannot perform an ORDER BY on TEXT fields. Try to remove the order by BirthYear, MusicianName on your NHibernate's query, the program shall run as normal. NHibernate just reflect the fact Sql Server cannot perform ORDER BY on TEXT fields; one advice, avoid TEXT fields on Sql Server. NHibernate does a poor job on stating that fact ;-)

So if you use text on Sql Server Management Studio, and performed ordering on them,...

select * from musician order by birth_year, musician_name 

...this will be its actual error(programmer-friendly error):
The text, ntext, and image data types cannot be compared or sorted, except when using IS NULL or LIKE operator.



The how-to video:





























EDIT February 12, 2011

New approach, spoiler alert: 1 monolithic step only ;-)

One-stop-shop with Fluent NHibernate

Friday, October 15, 2010

NHibernate could not be found

If you see this kind of compile-time error even when you already add NHibernate to your project reference...

The type or namespace name 'NHibernate' could not be found (are you missing a using directive or an assembly reference?)

...Just change your project target framework. Click Project > YourProjectNameHere Properties > change Target Framework from .NET Framework 4 Client Profile to .NET Framework 4

Thursday, October 14, 2010

Sql Server IS DISTINCT FROM

How to test if two NULLS are equal in SQL Server, ala-C#. Before our dear DBAs waxes philosophical that NULL = NULL is unknown, NULL <> NULL is also unknown, we must note that comparing things that results to knowns has uses, say for example we want to make audit logger in trigger code, that trigger code must be optimized. We will have database performance problem if we don't detect the field when logging changes, as this will do unconditional logging, whether the pertinent field(s) changes or not. We will also have problem in our logging logic if we cannot properly detect if there is a change that have occured in nullable field.

As typified by this:
IF new.sub_account_of <> old.sub_account_of THEN
    // log changes here
ELSE
    // do nothing
END

That will not work, if one of them has null. We must use this(doesn't work on Sql Server, works on Postgres, DB2):

IF new.sub_account_of IS DISTINCT FROM old.sub_account_of THEN
    // log changes here
ELSE
    // do nothing
END


Truth table for C#-like null comparison:
   A ==    B
NULL == NULL : True
NULL ==    1 : False
NULL ==    2 : False
   1 == NULL : False
   1 ==    1 : True
   1 ==    2 : False
   2 == NULL : False
   2 ==    1 : False
   2 ==    2 : True

   A !=    B
NULL != NULL : False
NULL !=    1 : True
NULL !=    2 : True
   1 != NULL : True
   1 !=    1 : False
   1 !=    2 : True
   2 != NULL : True
   2 !=    1 : True
   2 !=    2 : False

For the code above, expand the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NullComparison
{
    class Program
    {
        struct Rec
        {
            public int? a, b;

            public Rec(int? a, int? b)
            {
                this.a = a;
                this.b = b;
            }            
        }


        static void Main(string[] args)
        {
            Console.WriteLine("{0,4} == {1,4}", "A", "B");
            foreach (Rec r in Values())
            {
                Console.WriteLine("{0,4} == {1,4} : {2}", 
                    r.a != null ? r.a.ToString() : "NULL", 
                    r.b != null ? r.b.ToString() : "NULL", 
                    r.a == r.b);                
            }

            Console.WriteLine();
            Console.WriteLine("{0,4} != {1,4}", "A", "B");
            foreach (Rec r in Values())
            {
                Console.WriteLine("{0,4} != {1,4} : {2}", 
                    r.a != null ? r.a.ToString() : "NULL", 
                    r.b != null ? r.b.ToString() : "NULL", r.a != r.b);                
            }

            Console.ReadLine();
            
        }

        static IEnumerable<Rec> Values()
        {
            yield return new Rec(null, null);
            yield return new Rec(null, 1);
            yield return new Rec(null, 2);
            yield return new Rec(1, null);
            yield return new Rec(1, 1);
            yield return new Rec(1, 2);
            yield return new Rec(2, null);
            yield return new Rec(2, 1);
            yield return new Rec(2, 2);
        }

    }//class Program

}//namespace NullComparison

If you want to go directly to Sql Server logic, click this, otherwise continue reading...

Now let's do it first in PostgreSQL to verify the logic of our code, since it has first class support for C#-like nullable values comparison behavior. For equality, it uses IS NOT DISTINCT FROM, for inequality it uses IS DISTINCT FROM, pretty intuitive isn't it? If I remember correctly, MySQL uses <=> for its IS DISTINCT FROM, I don't recall if there's an IS NOT DISTINCT FROM operator in MySQL.

To create the table(also works in SQL Server 2008):
select a,b into x_m
from 
(
 values
 (null,null),
 (null,1),
 (null,2),
 (1,null),
 (1,1),
 (1,2),
 (2,null),
 (2,1),
 (2,2)
)as x(a,b)

Without further ado, this is the logic of equality: coalesce(a = b or (a is null and b is null), false) as manual_comp

Why coalesce is needed there? It's when either of a or b is null, the equality comparison will result to null. So we need to translate null to false, for that we will use coalesce.

To test:
select a,b
,coalesce(a = b or (a is null and b is null), false) as manual_comp
,a is not distinct from b as built_in_comp
from x_m

Outputs:
 a | b | manual_comp | built_in_comp
---+---+-------------+---------------
   |   | t           | t
   | 1 | f           | f
   | 2 | f           | f
 1 |   | f           | f
 1 | 1 | t           | t
 1 | 2 | f           | f
 2 |   | f           | f
 2 | 1 | f           | f
 2 | 2 | t           | t
(9 rows)

Now how to check if b is different from a? use IS DISTINCT FROM, now how do we do it in manual SQL? One approach is we just put NOT on the whole expression, another one is we rewrite the query:

select a,b
,coalesce(a <> b and (a is not null or b is not null), true) as manual_comp
,a is distinct from b as built_in_comp
from x_m

Outputs:
 a | b | manual_comp | built_in_comp
---+---+-------------+---------------
   |   | f           | f
   | 1 | t           | t
   | 2 | t           | t
 1 |   | t           | t
 1 | 1 | f           | f
 1 | 2 | t           | t
 2 |   | t           | t
 2 | 1 | t           | t
 2 | 2 | f           | f
(9 rows)


As we can see, re-wiring rewriting the query seems a bit tedious to read:

coalesce(a <> b and (a is not null or b is not null), true) as manual_comp

Programmers also tend to re-read their logic if it involves multiple NOT EQUAL comparison. I cannot just help myself to skim-read that query. Maybe one of those NOT EQUAL comparisons could launch a missile!

Let's find one example how using only one NOT happens in real life. If an interviewer asked the interviewee about her favorite singer, and the answer is neither John, Paul, George nor Ringo, then the interviewer should go find another interviewee.

Now how most programmers(not all programmers) translate that to program?

IF ans != 'John' AND ans != 'Paul' AND ans != 'George' AND ans != 'Ringo' THEN 
    FindAnotherInterviewee
END

See the pattern there? You never read written instructions like this in real life "Please interview another person if the interviewee's answer is not john and not paul and not george and not ringo." In real life, you just use one NOT. The code above doesn't mimic the original English sentence anymore, it's hard to read that logic, it compels the programmer to re-read the logic, multiple NOT EQUALs promotes doubtful assertion, perhaps you've seen others prefer this (translation of if answer is neither Y, N nor C):

IF ans != 'Y' AND ans != 'N' AND ans != 'C' THEN ask again END

Over this readable one:
IF NOT(ans = 'Y' OR ans = 'N' or ans = 'C') THEN ask again END


So I shun multiple NOT EQUALs in logic, they make the program harder to read. So the English sentence of neither John,Paul,George nor Ringo, should be written like this:

IF NOT(ans = 'John' OR ans = 'Paul' OR ans = 'George' OR ans = 'Ringo') THEN     
    FindAnotherInterviewee
END

That directly mimics the english sentence with only one NOT. Actually the word nor from actual English sentence is a signal in itself that one should not use AND, should use OR instead when translating the English sentence to code.

So let's stick with readable programs.

To continue, for inequality comparisons, we just stick NOT in front of the equality expression.

select a,b
,not coalesce(a = b or (a is null and b is null), false) as manual_comp
,a is distinct from b as built_in_comp
from x_m

Here's the output:
 a | b | manual_comp | built_in_comp
---+---+-------------+---------------
   |   | f           | f
   | 1 | t           | t
   | 2 | t           | t
 1 |   | t           | t
 1 | 1 | f           | f
 1 | 2 | t           | t
 2 |   | t           | t
 2 | 1 | t           | t
 2 | 2 | f           | f
(9 rows)



Now let's go back to the main gist of this article, since up to this time of writing, Microsoft SQL Server 2008 still doesn't support IS DISTINCT FROM and IS NOT DISTINCT FROM operators. We must find our way to support those conditions.


So how to translate those IS DISTINCT FROM and IS NOT DISTINCT FROM to Sql Server?

Postgresql version:

select a,b
,coalesce(a = b or (a is null and b is null), false) as manual_comp
,a is not distinct from b as built_in_comp
from x_m

To translate to Sql Server-consumable one, use CASE WHEN:

select a,b,
    case 
    when a = b then 1
    when a is null and b is null then 1 
    else 0
    end as mssql_manual_comp
from x_m

Outputs:
a           b           mssql_manual_comp
----------- ----------- -----------------
NULL        NULL        1
NULL        1           0
NULL        2           0
1           NULL        0
1           1           1
1           2           0
2           NULL        0
2           1           0
2           2           1

(9 row(s) affected)

Working properly, now how to check if a IS DISTINCT FROM b? Since Sql Server don't have first-class support for booleans, we cannot just put NOT on front of CASE statement. This will result to incorrect syntax:
select a,b,
      NOT
      case 
      when a = b then 1
      when a is null and b is null then 1 
      else 0
      end as mssql_manual_comp
from x_m


So let's get some help from XOR operator to toggle the logic:
select a,b,
    case 
    when a = b then 1
    when a is null and b is null then 1 
    else 0
    end ^ 1 as mssql_manual_comp
from x_m

So what's the output for our inequality comparison?
a           b           mssql_manual_comp
----------- ----------- -----------------
NULL        NULL        0
NULL        1           1
NULL        2           1
1           NULL        1
1           1           0
1           2           1
2           NULL        1
2           1           1
2           2           0

(9 row(s) affected)

So our logic is correct, so now let's give some IS DISTINCT FROM love to Sql Server, which if I may add can already be done on other major RDBMS

To recap, how to do IS NOT DISTINCT FROM in Sql Server?
select a,b
from x_m
where a IS NOT DISTINCT FROM b

Here's the equivalent in Sql Server:
select a,b
from x_m
where  
    (case 
    when a = b then 1
    when a is null and b is null then 1 
    else 0
    end) = 1

Outputs:
a           b
----------- -----------
NULL        NULL
1           1
2           2

(3 row(s) affected)


And how to do IS DISTINCT FROM in Sql Server?
select a,b
from x_m
where a IS DISTINCT FROM b


Here's the equivalent in Sql Server:
select a,b
from x_m
where  
    (case 
    when a = b then 1
    when a is null and b is null then 1 
    else 0
    end) = 0

Outputs:
a           b
----------- -----------
NULL        1
NULL        2
1           NULL
1           2
2           NULL
2           1

(6 row(s) affected)


Please do note that we don't need to use XOR in our CASE expression in WHERE clause, we can just directly compare the CASE expression to 1 or 0. 1 denotes equality, 0 denotes inequality.


Regarding DeMorgan logic simplifier and why multiple != (aka <>) should be suspected of code smell http://stackoverflow.com/questions/4615113/how-can-the-and-and-or-key-word-used-together-in-a-select-query/4615182#4615182


UPDATE 2011-08-08

Here's another approach for IS NOT DISTINCT FROM:
select a,b
from x_m
where 
    (
        a = b
        or
        (a is null and b is null)
    )

For IS DISTINCT FROM:
select a,b
from x_m
where 
    a <> b
    or 
    ( 
        not (a is null and b is null )
        and ( a is null or b is null )
    )   


UPDATE 2014-04-08

Here's another approach for IS DISTINCT FROM: http://sqlfiddle.com/#!15/d41d8/1731
with x as
(
    select a, b
    from
    (
        values
        (null,null),
        (null,1),
        (null,2),
        (1,null),
        (1,1),
        (1,2),
        (2,null),
        (2,1),
        (2,2)
    )as x(a,b)
)
select *,
 
 
    x.a IS DISTINCT FROM x.b as perfect,
 
 
    x.a <> x.b as flawed,
 
 
    a <> b
    or
    (
        not (a is null and b is null )
        and ( a is null or b is null )
    )  
        as good_workaround,
 
 
 
    (x.a <> x.b or x.a is null or x.b is null)
    and not (x.a is null and x.b is null)
 
        as perfect_workaround
 
 
 
from x;

Output:
|      A |      B | PERFECT | FLAWED | GOOD_WORKAROUND | PERFECT_WORKAROUND |
|--------|--------|---------|--------|-----------------|--------------------|
| (null) | (null) |       0 | (null) |          (null) |                  0 |
| (null) |      1 |       1 | (null) |               1 |                  1 |
| (null) |      2 |       1 | (null) |               1 |                  1 |
|      1 | (null) |       1 | (null) |               1 |                  1 |
|      1 |      1 |       0 |      0 |               0 |                  0 |
|      1 |      2 |       1 |      1 |               1 |                  1 |
|      2 | (null) |       1 | (null) |               1 |                  1 |
|      2 |      1 |       1 |      1 |               1 |                  1 |
|      2 |      2 |       0 |      0 |               0 |                  0 |  

Wednesday, October 13, 2010

You can perform data manipulation on views

use master;
go

create database y_test;
go

use y_test;
go


create table employee
(
id int not null,
name varchar(100) not null,
gross_salary numeric(18,4) not null default '',
password varchar(100) not null default ''
);
go

insert into employee(id, name, gross_salary, password) 
select id, name, gross_salary, password 
from (values
(1, 'Jobs', 75000, 'dashing'),
(2, 'Ballmer', 30000, 'through'),
(3, 'Gatez', 90000, 'the snow')
) as x(id, name, gross_salary, password);
go

select * from employee;
go

create view hr_facing_employee_record as
select id+0 as id, name, gross_salary * (1 - .32 /* Darn Philippines Withholding Tax! */ ) as net_salary from employee;
go

select * from hr_facing_employee_record;
go

delete from hr_facing_employee_record where id = 2;
go

select * from hr_facing_employee_record;
go

-- won't be allowed, inserting records to database must be done in a controlled manner, i.e. must insert directly to table, for example, there might other mechanism how ID is generated/assigned, so must directly insert to table
insert into hr_facing_employee_record(id,name) values(9,'Torvalds');
go

select * from hr_facing_employee_record;
go


update hr_facing_employee_record set name = 'Gates' where id = 3;
go

select * from hr_facing_employee_record;
go


/* 

-- won't be allowed, password is not exposed in hr_facing_employee_record

update hr_facing_employee_record set name = 'Backdoor', password = 'open sesame' where id = 3;
go

select * from hr_facing_employee_record;
go
*/


-- won't be allowed, a view can protect the record integerity
update hr_facing_employee_record set id = 8 where id = 3;
go

select * from hr_facing_employee_record;
go

use master;
go

drop database y_test;
go


Somehow, updateable views is justified (Hello Postgresql! requesting ^_^ ), you can hide the actual table from the user and you can protect what needs to be protected and at the same time abstract some information. However, this problem could also be tackled by using column-level permissions feature of your favorite RDBMS. Each approach has their own appeal.


Sometimes programming into the language has some drawbacks. You won't know what features the alternative products can bring to the table, you sort of missing the proper way of expressing your idea/solution to a given problem domain.

Sql Server Views has updateable views, will keep that in mind, will keep that in mind, or rather in blog ;-)