Wednesday, January 26, 2011

Tutorial: Hello ORM with ADO.NET EDM

I hope, you might already know what OOP is. Most of .net developers know what OOP is and what the features of OOP are. They may also learn some OOP tutorials. However, just knowing is not enough and it is the most important to know how to put in place in real world projects.

Before ADO.NET EDM is released, I couldn't find any useful way to use OOP in real world applications. Most of my applications are data centric applications and our lives were so easy with Microsoft Data Binding and ADO.NET DataAdapter. All we need to do is to provide CRUD store procedures to DataAdapter, drag-drop DataGridView to form and bind DataGridView with DataAdapter. That's all!!! It was amazingly easy and really straightforward. All insert/update/delete are handled by the DataGridView and DataAdapter.

If you create your own business entities or business objects, it is all your responsibilities to persist these business objects to data store. It is just a nightmare to handle all these things by hand instead of using frameworks to do our jobs. I don't say that it is impossible to do these things. It just gives us more works. Instead of concentrating on the business logic, we need to share our man-days wasting on the persistence.

Now it is time to say thanks to Microsoft for bringing in Object Relational Mapping feature to the .Net world. Microsoft introduced Entity Data Model (EDM) with Visual Studio 2008 SP1. Entity Data Model and Entity Data Framework will do our jobs for persisting these business objects.

Here I would like to show you the very basic but insight tutorial on EDM. Visual Studio has built-in EDM designer. But it hides all the great details and it isn't good for the loosely coupled mappings. EDM designer only supports Properties and it doesn't support to add behaviors to our business objects. Although you can achieve it by partial class, I don't prefer that way. By hand-writing the whole mapping, you will get whole freedom.

There are three main files to define the mapping between entity objects and the relational database.
1) .csdl file. (CSDL stands for Conceptual Schema Definition Language and it is the description of our entity classes.
2) .ssdl file. (SSDL stands for Storage Shchema Definition Language and it is the description of relational database model.
3). .msl file (MSL stands for Mapping Specification Language and it is the mapping between conceptual model and the storage model.)

I hope you got a small picture of what EDM is. Now it's time to show you the sample application.

First of all I'll create an entity class called User. 
User.cs

/*
MyModels is XML Schema namespace and User is the entity type, both are defined in .csdl file. These attributes map the entity c# class "User" with the entity type "User" defined in .csdl file*/
[EdmEntityTypeAttribute(NamespaceName = "MyModels", Name = "User")]
    [Serializable()]
    [DataContractAttribute(IsReference = true)]
    ///Our class must extend EntityObject
    public class User : EntityObject
    {
       public static User CreateUser(global::System.Int32 userID)
       {
            User user = new User();
            user.UserID = userID;
            return user;
       }

        /*These attributes state that this property needs to be 
        mapped to the database column.*/
        [EdmScalarPropertyAttribute(EntityKeyProperty = true, IsNullable = false)]
        [DataMemberAttribute()]
        public global::System.Int32 UserID
        {
            get
            {
                return _UserID;
            }
            set
            {
                if (_UserID != value)
                {
                    _UserID = StructuralObject.SetValidValue(value);
                }
            }
        }
        private global::System.Int32 _UserID;
        

        [EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]
        [DataMemberAttribute()]
        public global::System.String Username
        {
            get
            {
                return _Username;
            }
            set
            {
                _Username = StructuralObject.SetValidValue(value, true);
                
            }
        }
        private global::System.String _Username;
        

        [EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = true)]
        [DataMemberAttribute()]
        public global::System.String Password
        {
            get
            {
                return _Password;
            }
            set
            {
                _Password = StructuralObject.SetValidValue(value, true);
                
            }
        }
        private global::System.String _Password;        

        //Custom Business logic added for demonstration
        public string SayHello()
        {
            return "Hello, my name is " + _Username;
        }
    }

Next thing is to add the DataAdapter like class. It will handle all CRUD processing based on the provided mapping files and it will keep track of all the entity state changes and will do Insert/Update/Delete accordingly.

BusinessEntities.cs
//this class must inherit ObjectContext class
public class BusinessEntities : ObjectContext
    {
        /*The first parameter is the connection name in App.config file. The second is the container name defined in config files*/
        public BusinessEntities()
            : base("name=BusinessEntitiesConString", "BusinessEntities")
        {
            this.ContextOptions.LazyLoadingEnabled = true;
        }
        
        public ObjectSet<User> User
        {
            get
            {
                if ((_User == null))
                {
                    _User = base.CreateObjectSet<User>("User");
                }
                return _User;
            }
        }
        private ObjectSet<User> _User;
    }

The last thing is to create a Form and see the results. I'll add one DataGridView and two buttons to the form.
Form1.cs
public partial class Form1 : Form
{
   //Create a instance variable of our context class
   BusinessEntities be = new BusinessEntities();

   ///This method will retrieve all user objects from database  
   ///and show in the datagridview
   private void button1_Click(object sender, EventArgs e)
   {
            
       dataGridView1.DataSource = from user in be.User
                                       select user;
   }
   

   /*You can Add/Edit/Delete records in DataGridView and call this method to save all the changes back to DB*/
   private void button2_Click(object sender, EventArgs e)
   {
       be.SaveChanges();
   }   
}

OK, we are done with the coding part. Now let's go to the config part.

First we need to add Database (.mdf) file to the solution root folder. I'll call it mydb.mdf.  Double click the database file to open in database explorer. Then add new table to the database.
UserID int PK not null
Username varchar(50)
Password varchar(50)

Next add connection string to the App.config.
<add name="BusinessEntitiesConString" connectionString="metadata=.\MyModel.csdl|.\MyModel.ssdl|.\MyModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\mydb.mdf;Integrated Security=True;User Instance=True;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />

Please note that the connection string name will be used from our BusinessEntities constructor.

Please note that you need to set "Copy to output directory" properties of below three files to Copy Always or Copy if newer.

Then add MyModel.csdl file. This file describe the conceptual model of our business domain models. This file contains container, entity type and other attributes and those are used to map with our .cs class files.
<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="MyModels" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
  <EntityContainer Name="BusinessEntities" annotation:LazyLoadingEnabled="true">
    <EntitySet Name="User" EntityType="MyModels.User" />
  </EntityContainer>
  <EntityType Name="User">
    <Key>
      <PropertyRef Name="UserID" />
    </Key>
    <Property Name="UserID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
    <Property Name="Username" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <Property Name="Password" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
  </EntityType>
</Schema>

The attribute values such as "MyModels, BusinessEntities, MyModels.User" could be any thing. But please make sure that these names reflect the names used in the attributes in our .cs classes.

Then add MyModel.ssdl file. This file reflect the schema of our database.
<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="MyModels.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
  <EntityContainer Name="MyModelsStoreContainer">
    <EntitySet Name="TB_User" EntityType="MyModels.Store.TB_User" store:Type="Tables" Schema="dbo" />
  </EntityContainer>
  <EntityType Name="TB_User">
    <Key>
      <PropertyRef Name="UserID" />
    </Key>
    <Property Name="UserID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
    <Property Name="Username" Type="varchar" MaxLength="50" />
    <Property Name="Password" Type="varchar" MaxLength="50" />
  </EntityType>
</Schema>

The last thing is to add mapping file MyModel.msl. This file provides mapping between above two files, conceptual and storage.

<?xml version="1.0" encoding="utf-8"?>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2008/09/mapping/cs">
  <EntityContainerMapping StorageEntityContainer="MyModelsStoreContainer" CdmEntityContainer="BusinessEntities">
    <EntitySetMapping Name="User">
      <EntityTypeMapping TypeName="MyModels.User">
        <MappingFragment StoreEntitySet="TB_User">
          <ScalarProperty Name="UserID" ColumnName="UserID" />
          <ScalarProperty Name="Username" ColumnName="Username" />
          <ScalarProperty Name="Password" ColumnName="Password" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>
  </EntityContainerMapping>
</Mapping>

This file map the entities, containers and properties from the above two xml files.

Ok, we are almost there. The very final thing to do is to press F5 and enjoy.

















Thanks for reading and hope worth reading!
:)