Download Windows Developer Preview (pre-beta version of Windows 8 for developers)

The Windows Developer Preview is a pre-beta version of Windows 8 for developers. These downloads include prerelease software that may change without notice. The software is provided as is, and you bear the risk of using it. It may not be stable, operate correctly or work the way the final version of the software will. It should not be used in a production environment. The features and functionality in the prerelease software may not appear in the final version. Some product features and functionality may require advanced or additional hardware, or installation of other software.

Windows Developer Preview with developer tools English, 64-bit (x64)

DOWNLOAD (4.8 GB)
Sha 1 hash - 6FE9352FB59F6D0789AF35D1001BD4E4E81E42AF
All of the following come on a disk image file (.iso). See below for installation instructions.

  • 64-bit Windows Developer Preview
  • Windows SDK for Metro style apps
  • Microsoft Visual Studio 11 Express for Windows Developer Preview
  • Microsoft Expression Blend 5 Developer Preview
  • 28 Metro style apps including the BUILD Conference app

System Requirements

Windows Developer Preview works great on the same hardware that powers Windows Vista and Windows 7:

  • 1 gigahertz (GHz) or faster 32-bit (x86) or 64-bit (x64) processor
  • 1 gigabyte (GB) RAM (32-bit) or 2 GB RAM (64-bit)
  • 16 GB available hard disk space (32-bit) or 20 GB (64-bit)
  • DirectX 9 graphics device with WDDM 1.0 or higher driver
  • Taking advantage of touch input requires a screen that supports multi-touch

How to install the Windows Developer Preview from an ISO image

The Windows Developer Preview is delivered as an .iso image that must be converted into installation media stored on a DVD or a USB flash drive. On Windows 7, the easiest way to convert this file is to use Windows Disc Image Burner. On Windows XP and Windows Vista, a third-party program is required to convert an .iso file into installable media—and DVD burning software often includes this capability. Note: The .iso file that contains the developer tools requires a large capacity DVD called a DVD-9, as well as a DVD burner that can handle dual-layer (DL) DVDs. Most modern burners should be able to handle this format.

More Details :
Windows Developer Preview downloads
The new Windows Dev Center
www.microsoft.com/presspass/events/build/

Windows 8

Share this post: | | | |

Mencicipi EntityFramework CodeFirst (EF 4.1)

Pada Entity Framework (EF) ada 3 hubungan yang bisa terjadi antara Model dan Database. Yaitu :

Database First: Ini yang biasa kita lakukan. Kita menciptakan Model dari Database yg sudah exist. Biasa disebut juga reverse-engineering.
Model First: Kita menciptakan Model terlebih dahulu lalu kemudian kita menciptakan Database dari Model tersebut.
Code First: Kita menciptakan database dari Code yang kita ketikkan.

Hal yang menarik dari CodeFirst adalah kita bisa menciptakan database dengan hanya sedikit usaha saja. Fitur ini terdapat dalam EF 4.1.

So, pada postingan kali ini kita akan melakukan semacam walkthrough untuk mencoba CodeFirst.

Bagi yang belum tau bagaimana memanfaatkan code first, pertama sekali kita harus mendownload EF 4.1 disini: http://www.microsoft.com/download/en/details.aspx?id=26825. Setelah itu lakukan penginstalan dan untuk mempergunakannya kita tinggal melakukan add reference ke EntityFramework.dll yang berada pada C:\Program Files (x86)\Microsoft ADO.NET Entity Framework 4.1\Binaries.

Diagram database

Lalu, Anggap misalnya kita mempunyai suatu model database seperti berikut ini:

DataPemakaianObat_Diagram.png

Diatas adalah diagram Database yang akan kita buat dengan CodeFirst. Itu adalah diagram database DataPemakaianObat pada sebuah rumah sakit. Yang terdiri dari 4 table :

Pasiens: Merepresentasikan seorang pasien rumah sakit. Pasien mempunyai poperty Diagnosa yg nilainya adalah deskripsi penyakit yang dideritanya.
Alamats: Merepresentasikan alamat dari pasien.
Obats: Merepresentasikan suatu obat.
PasienObats: Pada Pasien dan Obat terdapat suatu hubungan Many to Many. Untuk itulah table PasienObats exist. Dan dia mempunyai property Jumlah yang menunjukkan jumlah obat yang diberikan kepada Pasien.

So, database DataPemakaianObat ini akan diisi jika ada seorang pasien dirumah sakit yang diberikan obat.

Menambahkan reference

Jadi, kita sudah mempunyai diagram database yang akan kita bangun dengan CodeFirst. Lalu sekarang kita menuju ke VisualStudio. Create New ConsoleApps dan add reference ke : EntityFramework.dll, System.Data.Entity.dll dan System.ComponentModel.DataAnnotations.dll.

Lalu tambahkan perintah using seperti berikut ini :

   1:  using System.Data.Entity;
   2:  using System.ComponentModel.DataAnnotations;

Mendefinisikan class DbContext

Selanjutnya kita akan membuat class baru yang menginherit class DbContext. Tambahkan code berikut:

   1:  public class KonsumsiObatContext : DbContext
   2:  {
   3:      public KonsumsiObatContext(string ConnectionString)
   4:          : base(ConnectionString)
   5:      { }
   6:   
   7:      public DbSet<Pasien> Pasiens { get; set; }
   8:      public DbSet<Alamat> Alamats { get; set; }
   9:      public DbSet<Obat> Obats { get; set; }
  10:      public DbSet<PasienObat> PasienObats { get; set; }
  11:  }

Pada class KonsumsiObatContext diatas, kita memanggil base constructor untuk melewatkan sebuah ConnectionString

Mendefinisikan class-class Entity

Selanjutnya, tugas kita adalah mendefinikan class-class Entity, yaitu : Pasien, Alamat, Obat dan PasienObat.

Class Pasien

Kita mulai saja dengan defenisi class Pasien dengan menambahkan code berikut:

   1:  public class Pasien
   2:  {        
   3:      [Key]
   4:      public int PasienId { get; set; }
   5:      public string Nama { get; set; }
   6:      public DateTime Tgl_Lahir { get; set; }
   7:      public string Kelamin { get; set; }
   8:      public string Diagnosa { get; set; }
   9:   
  10:      //Navigation Property
  11:      public ICollection<PasienObat> PasienObats { get; set; }
  12:      public ICollection<Alamat> Alamats { get; set; }
  13:  }

Pada class Pasien diatas kita menggunakan attribute Key untuk mendefinikan properti PasienId sebagai PrimaryKey.

Class Alamat

Untuk class Alamat, kita definisikan dengan code berikut:

   1:  public class Alamat
   2:  {
   3:      [Key]
   4:      public int AlamatId { get; set; }
   5:      public string Jalan { get; set; }
   6:      public string Kota { get; set; }
   7:      public string Provinsi { get; set; }
   8:      public string Negara { get; set; }
   9:   
  10:      //Foreign Key Property
  11:      public int PasienId { get; set; }
  12:   
  13:      //Navigation Property
  14:      public Pasien Pasien { get; set; }
  15:  }

Class Obat

Untuk class Obat, kita definisikan dengan code berikut:

   1:  public class Obat
   2:  {
   3:      [Key]
   4:      public int ObatId { get; set; }
   5:      public string Nama { get; set; }
   6:      public int Dosis { get; set; }
   7:      public string Satuan { get; set; }
   8:      public string Manufaktur { get; set; }
   9:      public double Harga { get; set; }
  10:   
  11:      //Navigation Property
  12:      public ICollection<PasienObat> PasienObats { get; set; }
  13:  }

Class PasienObat

Selanjutnya yang terakhir adalah class PasienObat. class PasienObat ini merepresentasikan Join Table antara table Pasien dan Obat. Tambahkan code berikut untuk mendefinisikannya:

   1:  public class PasienObat
   2:  {
   3:      //property PasienID yg merupakan ForeignKey
   4:      //dan bagian dari Composite PrimaryKey 
   5:      [Key]
   6:      [Column("PasienId", Order = 0)]
   7:      public int PasienId { get; set; }
   8:   
   9:      //property ObatID yg merupakan ForeignKey
  10:      //dan bagian dari Composite PrimaryKey 
  11:      [Key]
  12:      [Column("ObatId", Order = 1)]
  13:      public int ObatId { get; set; }
  14:   
  15:      public int Jumlah { get; set; }
  16:   
  17:      //Navigation Property
  18:      public Pasien Pasien { get; set; }
  19:      public Obat Obat { get; set; }
  20:  }

Pada class PasienObat diatas, kita harus mendifinisakan urutan dari Composite PrimaryKey. Kita melakukannya dengan menggunakan attribute Column dan men-set property Order sesuai dengan nilai urutan.

Running the Code

Seperti yang kita lihat diatas, pada dasarnya kita hanya mendefinisikan struktur Model database yang kita inginkan. Hal ini kita lakukan dengan menggunakan beberapa attribute. Tapi, selain menggunakan attribute masih ada cara lain untuk melakukannya yaitu dengan menggunakan class DbModelBuilder. Tapi pada walkthrough ini kita cukup hanya menggunakan Attribute untuk melakukannya dengan tujuan untuk membuatnya ringkas. Pada saat runtime nanti EntityFramework akan menebak struktur dari Model yang kita definisikan tersebut. Dan jika valid, maka dia akan meng-compose SQL Commands. Dab SQL Commands ini lah yang akan di execute untuk meng-create Database.

So, selanjutnya kita tinggal membuat class Program untuk meng-create Database dari struktur yang sudah kita definisikan diatas. Codenya seperti berikut :

   1:  class Program
   2:  {
   3:      static void Main(string[] args)
   4:      {
   5:          var connString = "Data Source=localhost; " +
   6:                           "Initial Catalog=KonsumsiObat; " +
   7:                           "Integrated Security=True; " +
   8:                           "MultipleActiveResultSets=True";
   9:          using (var context = new KonsumsiObatContext(connString))
  10:          {                
  11:              if (!context.Database.Exists())
  12:              {                
  13:                  context.Database.Create();                
  14:                  Console.WriteLine("Database created");
  15:              }
  16:   
  17:              #region   Isi Data             
  18:              var newPasien = new Pasien
  19:              {
  20:                  Nama = "Joni",
  21:                  Kelamin = "Laki-laki",
  22:                  Diagnosa = "Demam",
  23:                  Tgl_Lahir = DateTime.Parse("05-05-1985")
  24:   
  25:              };
  26:   
  27:              var newAlamat = new Alamat
  28:              {
  29:                  Jalan = "Jl. Merdeka No. 55",
  30:                  Kota = "Medan",
  31:                  Provinsi = "Sumatera Utara",
  32:                  Negara = "Indonesia",
  33:                  Pasien = newPasien
  34:              };
  35:   
  36:              var newObat = new Obat
  37:              {
  38:                  Nama = "Paracetamol Syrup",
  39:                  Manufaktur = "PT. Kimia Farma",
  40:                  Dosis = 60,
  41:                  Satuan = "ml",
  42:                  Harga = 10000
  43:              };
  44:   
  45:              var newPasienObat = new PasienObat
  46:              {
  47:                  Pasien = newPasien,
  48:                  Obat = newObat,
  49:                  Jumlah = 1
  50:              };
  51:   
  52:              context.Pasiens.Add(newPasien);
  53:              context.Alamats.Add(newAlamat);
  54:              context.Obats.Add(newObat);
  55:              context.PasienObats.Add(newPasienObat);
  56:              context.SaveChanges();
  57:              #endregion
  58:   
  59:              var pasien = context.Pasiens
  60:                              .Include("Alamats")                             
  61:                              .Include("PasienObats.Obat")
  62:                              .FirstOrDefault();
  63:   
  64:              Console.WriteLine("Nama: {0} \nAlamat: {1} \nObat: {2}",
  65:                  pasien.Nama,
  66:                  pasien.Alamats.First().Kota,
  67:                  pasien.PasienObats.First().Obat.Nama
  68:              );
  69:                               
  70:          }
  71:      }
  72:  }

Pada code diatas, kita mencreate Database menggunakan object Database yang merupakan property dari object DbContext (Line 13). Setelah itu seperti yg disebutkan diatas, EntityFramework akan memeriksa struktur dari Model yang kita definisikan dan meng-compose Commands yang akhirnya diexecute untuk mengcreate Database.

Pada bagian terakhir kita melakukan penginputan data dan melakukan suatu query ke database (Line 17 - line 62) untuk melakukan testing terhadap Database yang baru kita create.

OK. Itulah walkthrough tentang CodeFirst, semoga bermanfaat.

Share this post: | | | |

Implementasi Interface secara Biasa dan secara Explisit

Pada postingan kali ini saya ingin membahas tentang Implementasi Interface secara biasa dan Implementasi interface secara Explisit.

Seperti kita tau interface itu adalah sebuah class yang mendefinisikan satu atau lebih member.

Tetapi member-member tersebut tidak mempunyai implementasi.

Class lain yang meng-implementasi interface lah yang harus mendefenisikan implementasi untuk member-member tersebut.

Nah, didalam C# bila kita meng-implement suatu interface, kita mempunyai 2 opsi untuk melakukannya.

Yaitu secara biasa, atau secara Explisit.

Kalau kita meng-implementasi suatu interface secara biasa, maka member-member tersebut bisa diakses dari class yang meng-implementasi interface tersebut tanpa perlu melakukan casting ke interface tersebut.

Tapi, jika kita meng-implemetasi interface secara Explisit, maka member-member tersebut hanya bisa diakses kalau kita melakukan casting ke interface tersebut.

Untuk lebih jelasnya kita bisa perhatikan kode berikut :

Misal, kita punya interface ISomeInterface seperti ini :

   1:  public interface ISomeInterface
   2:  {
   3:      void SomeMethod();
   4:  }

Lalu kita punya class Base yang mengimplementasi ISomeInterface. Kalau kita meng-implementasi interface secara biasa, maka kita melakukannya seperti ini :

public class Base : ISomeInterface
{
    public void SomeMethod()
    {
        //Base.SomeMethod
    }
}

Lalu kalau kita mau melakukan implementasi interface secara Explisit, kita melakukannya seperti ini :

public class Derived : Base, ISomeInterface
{
    void ISomeInterface.SomeMethod()
    {
        //ISomeInterface.SomeMethod
    }
}

Class Derived diatas adalah turunan dari Base dan meng-implementasi ISomeInterface secara explisit. Kita bisa lihat cara penulisan SomeMethod yang harus ditulis secara FullName.

Seperti yang disebutkan diatas, Interface yang diimplement secara biasa, maka member-membernya bisa diakses dari Class yang meng-implement interface tersebut tanpa melakukan casting. Sedangkan Interface yang diimplement secara eksplisit, maka untuk mengakses member-membernya kita harus melakukan casting ke interface tersebut. Contohnya pada kode dibawah ini :

Derived myDerived = new Derived();

//output : Base.SomeMethod
myDerived.SomeMethod();

//output : ISomeInterface.SomeMethod
((ISomeInterface)myDerived).SomeMethod();

Untuk bahan pengamatan, kita bisa melakukan coding sendiri dan mempelajari dampak dari pengimplementasian interface secara biasa atau secara explisit. Seperti kode dibawah ini, yang mana ditambahkan satu buah interface lagi yaitu IOtherInterface dan kemudian di-implement secara explisit oleh Base Class dan Derived Class.

public interface ISomeInterface
{
    void SomeMethod();
}

public interface IOtherInterface
{
    void OtherMethod();
}   

public class Base : ISomeInterface, IOtherInterface
{
    public void SomeMethod()
    {
        Console.WriteLine("Base.SomeMethod");
    }

    void IOtherInterface.OtherMethod()
    {
        Console.WriteLine("IOtherInterface.OtherMethod, Base");
    }      
}

public class Derived : Base, ISomeInterface, IOtherInterface
{
    void ISomeInterface.SomeMethod()
    {
        Console.WriteLine("ISomeInterface.SomeMethod");
    }

    void IOtherInterface.OtherMethod()
    {
        Console.WriteLine("IOtherInterface.OtherMethod, Derived");
    }       
}
Base myBase = new Base();
Derived myDerived = new Derived();

//output : Base.SomeMethod
myBase.SomeMethod();

//output : IOtherInterface.OtherMethod, Base
((IOtherInterface)myBase).OtherMethod(); 

//output : Base.SomeMethod              
myDerived.SomeMethod();

//output : ISomeInterface.SomeMethod
((ISomeInterface)myDerived).SomeMethod();

//output : IOtherInterface.OtherMethod, Derived
((IOtherInterface)myDerived).OtherMethod();
Lalu bagaimana, bila kita meng-implement interface secara biasa dan juga secara explisit ?, seperti contoh berkut ini :
public interface ISomeInterface
{
    void SomeMethod();
}

public class OtherClass : ISomeInterface
{
    public void SomeMethod()
    {
        Console.WriteLine("This is the OtherClass's Method");
    }

    void ISomeInterface.SomeMethod()
    {
        Console.WriteLine("ISomeInterface.SomeMethod");
    }
}
ISomeInterface mySomeInterface = new OtherClass();
//output : ISomeInterface.SomeMethod
mySomeInterface.SomeMethod();

Ketika interface tersebut kita implement secara biasa dan secara eksplisit, Maka pemanggilan SomeMethod akan memanggil SomeMethod yang implementasinya dilaksanakan secara eksplisit. Dan SomeMethod yang diimplement secara biasa, akan dianggap sebagai member biasa dari OtherClass, not the member of the interface.



Share this post: | | | |
Posted by Agus Syahputra with no comments
Filed under: ,

Mengetahui EntityState pada Entity Framework

Pada EF (Entity Framework) Masing-masing object yang telah di-Materialized mempunyai state-nya masing-masing.

Daftar state tersebut bisa kita lihat pada enumeration EntityState yang terdapat pada Namespace System.Data.

Ini adalah daftar state tersebut :

   1:  public enum EntityState
   2:  {        
   3:      Detached = 1,     
   4:      Unchanged = 2,        
   5:      Added = 4,        
   6:      Deleted = 8,        
   7:      Modified = 16,
   8:  }

From MSDN :

Detached : The object exists but is not being tracked. An entity is in this state immediately after it has been created and before it is added to the object context. An entity is also in this state after it has been removed from the context by calling the Detach method or if it is loaded by using a NoTracking MergeOption. There is no ObjectStateEntry instance associated with objects in the Detached state.

Unchanged : The object has not been modified since it was attached to the context or since the last time that the SaveChanges method was called.

Added : The object is new, has been added to the object context, and the SaveChanges method has not been called. After the changes are saved, the object state changes to Unchanged. Objects in the Added state do not have original values in the ObjectStateEntry.

Deleted : The object has been deleted from the object context. After the changes are saved, the object state changes to Detached.

Modified : One of the scalar properties on the object was modified and the SaveChanges method has not been called. In POCO entities without change-tracking proxies, the state of the modified properties changes to Modified when the DetectChanges method is called. After the changes are saved, the object state changes to Unchanged.

Sesuai keterangan diatas, maka ketika EntityObject sudah berada di Memory, mereka pasti memiliki salah satu state yang terdapat pada Enumeration EntityState (Detached, Unchanged, Added, Deleted, Modified).

Untuk lebih jelas-nya kita bisa menjalankan code berikut. Yang isinya adalah, meng-query sembarang Entity dari database, dan melakukan suatu Modifikasi pada EntityObject tersebut dan kemudian mengamati state yang mereka miliki :

   1:  using (var context = new NorthwindEntities())
   2:  {
   3:      context.ContextOptions.LazyLoadingEnabled = false;
   4:      var customers = new List<Customer>();
   5:      
   6:      //Detached EntityState
   7:      var detached = context.Customers.First();
   8:      context.Detach(detached);
   9:      customers.Add(detached);
  10:   
  11:      //Unchanged EntityState
  12:      var unchanged = context.Customers
  13:                      .OrderByDescending(c => c.CustomerID).First();
  14:      customers.Add(unchanged);
  15:   
  16:      //Added EntityState
  17:      var added = new Customer();
  18:      added.CustomerID = "AGUTR";
  19:      added.ContactName = "Agus Syahputra";
  20:      context.Customers.AddObject(added);
  21:      customers.Add(added);   
  22:   
  23:      //Deleted EntityState
  24:      var deleted = context.Customers.Take(2).ToList()
  25:                    .OrderByDescending(c => c.CustomerID).First();
  26:      context.DeleteObject(deleted);
  27:      customers.Add(deleted);
  28:   
  29:      //Modified EntityState
  30:      var modified = context.Customers.Take(3).ToList()
  31:                     .OrderByDescending(c => c.CustomerID).First();
  32:      modified.ContactName += modified.ContactName;
  33:      customers.Add(modified);
  34:   
  35:      foreach (var c in customers)
  36:      {
  37:          Console.WriteLine("{0} - |{1}|", c.CustomerID, c.EntityState);
  38:      }    
  39:  }

Pada code diatas saya menggunakan Database Northwind, yang bisa didownload di situs Microsoft or search from internet.

Pada code diatas, apabila SaveChanges dipanggil, maka EF akan meng-create Commands untuk dikirim ke database sesuai dengan state dari masing-masing Entity Object, misal, pada EntityObject yang memiliki state Added EF akan meng-create Insert Command dan kemudian mengirimkannya ke database untuk diExecute. Tetapi untuk Entity yang memiliki state Unchanged dan Detached maka ObjectContext tidak akan meng-create suatu Command untuk dikirim ke database.

Ini adalah output dari program diatas :

Pada output, kita bisa melihat bahwa masing-masing Entity mempunyai statenya masing-masing.

OK. Sekian dulu.

Share this post: | | | |
Posted by Agus Syahputra with no comments
Filed under: ,

Mengimplementasi SQL Server's Odd View pada EF 4

Kemarin ada pertanyaan di stackoverflow.com, detailnya you can see here. 

Pertanyaannya  :

I have a View which looks similar to this:

SELECT Id, Name
FROM Users
UNION ALL
SELECT NULL as [Id], NULL as [Name]

When I try to map to this view in Entity Framework, it just fails. I don't get an error, but the view does not exist in my data store. Why is this? Is there a way around it?

Sebenarnya, bukan EF tidak mendukung Union tetapi T-SQL-nya yg agak aneh. Jadinya EF gak bisa meng-create Metadata untuk View tsb.

So, untuk lebih jelasnya kita bisa melakukan simulasi.

 Anggap kita punya tabel di database seperti ini :


Lalu kita create View dengan nama ContactOddView dengan T-SQL seperti ini : 

   1:  CREATE VIEW [ContactCustomer]
   2:  AS
   3:  SELECT con.ContactID, con.Name
   4:  FROM Contacts AS con
   5:  UNION ALL
   6:  SELECT NULL AS ContactID, NULL AS Name

Lalu kita create Model di Visual Studio dan select View yg baru kita create di database :

 

Tetapi,Visual Studio akan gagal meng-create metadata untuk View ContactOddView. Ini bisa kita lihat Entity Model untuk ContactOddView tidak tampak di Model Designer. Dan untuk lebih pastinya kita bisa buka edmx file dengan XML Editor. Dan pada SSDL section kita akan melihat suatu pesan Error like this :

<!--Errors Found During Generation:
warning 6013: The table/view 'PhoneBook.dbo.ContactOddView' does not have a primary key defined and no valid primary key could be inferred. 
This table/view has been excluded. To use the entity, you will need to review your schema, add the correct keys, and uncomment it.
     <EntityType Name="ContactOddView"> <Property Name="ContactID" Type="int" />     <Property Name="Name" Type="varchar" MaxLength="50" /> </EntityType>-->

Sesuai dengan pesan errornya, ini terjadi karena VS tidak bisa menebak PK yg valid untuk View tersebut.

So, bagaimana caranya agar kita menampilkan View tersebut ?

Kita bisa melakukannya dengan mengotak-atik edmx file. Yaitu dengan meng-create sendiri metadata untuk view tersebut dengan tujuan untuk menentukan PK untuk View tersebut. Disini kita harus membuat metadata baru pada SSDL, CSDL, dan MSL section pada edmx file.

So akan ada 3 langkah untuk melakukannya :

1. Typing pada SSDL section

Kita mulai saja pada SSDL section. Kita harus menambahkan metadata ke dalam element  EntityContainer like this :

   1:  <EntityContainer Name="PhoneBookModelStoreContainer">
   2:            <EntitySet Name="ContactOddView" EntityType="PhoneBook.Store.ContactOddView" store:Type="Views">
   3:              <DefiningQuery>
   4:                SELECT con.ContactID, con.Name
   5:                FROM Contacts AS con
   6:                UNION ALL
   7:                SELECT NULL AS ContactID, NULL AS Name
   8:              </DefiningQuery>
   9:            </EntitySet>

 
Lalu kita definisakan juga Metadata untuk EntityType PhoneBook.Store.ContactOddView. Yang mana, disinilah kita akan menetapkan PK untuk View tersebut (Yang menjadi inti permasalahannya karena VS tidak bisa meng-infer valid PK untuk ContactOddView ). Masukkan code berikut pada bagian pendefinisian EntityType :

   1:  <EntityType Name="ContactOddView">
   2:          <Key>
   3:            <PropertyRef Name="ContactID" />
   4:          </Key>
   5:          <Property Name="ContactID" Type="int" Nullable="false" StoreGeneratedPattern="None" />
   6:          <Property Name="Name" Type="varchar" Nullable="true" MaxLength="50" />        
   7:        </EntityType>
 

2. Typing pada CSDL section

Karena CSDL merupakan cerminan dari SSDL. Kita tinggal ikutin aja metadata yg baru kita define di SSDL.

The First  Step is, mendefinisakan metadata untuk EntitySet ContactOddView. So, tambahkan code berikut pada element EntityContainer :

<EntitySet Name="ContactOddView" EntityType="PhoneBookModel.ContactOddView" />
Lalu kita define metadata untuk EntityTypenya yaitu PhoneBookModel.ContactOddView. Dengan menambahkan code berikut pada pendifinisian EntityType :
   1:  <EntityType Name="ContactOddView">
   2:            <Key>
   3:              <PropertyRef Name="ContactID" />
   4:            </Key>
   5:            <Property Name="ContactID" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="None"/>
   6:            <Property Name="Name" Type="String" Nullable="true" MaxLength="50" Unicode="false" FixedLength="false"/>
   7:          </EntityType> 
 

3. Typing pada MSL section

Pada MSL section kita harus mendefinisikan Mapping antara EntitySet di CSDL dan SSDL.
So tambahkan code berikut ini didalam element EntityContainerMapping  :

   1:  <EntitySetMapping Name ="ContactOddViews">
   2:              <EntityTypeMapping TypeName="PhoneBookModel.ContactOddView">
   3:                <MappingFragment StoreEntitySet="ContactOddViews">
   4:                  <ScalarProperty Name="ContactID" ColumnName="ContactID" />
   5:                  <ScalarProperty Name="Name"  ColumnName ="Name"/>                
   6:                </MappingFragment>
   7:              </EntityTypeMapping>
   8:            </EntitySetMapping>

 

OK. selesai sudah kita memodifikasi edmx file. Sekarang kita bisa buka edmx file tersebut di Model Designer dan ContactOddView pun sudah bisa ditampilkan.

Sekarang saatnya kita harus mengetes Model tersebut dengan melakukan suatu query.

Misal, kita melakukan query di database seperti ini :

SELECT * FROM ContactOddView

Maka result yg dihasilkan yaitu :


So now, kita melakukan pengetesan dari Visual Studio. Misalnya dengan code seperti ini :

   1:  using (var context = new PhoneBookEntities())
   2:  {
   3:      var qry = context.ContactOddViews;
   4:      foreach (var q in qry)
   5:      {
   6:          if (q != null)
   7:          {
   8:              object name = q.Name;
   9:              if (q.Name == null) { name = "null"; }
  10:              Console.WriteLine("{0} - {1}", q.ContactID, name);
  11:          }
  12:          else
  13:          {
  14:              Console.WriteLine("null - null");
  15:          }
  16:      }
  17:  }

Dan outputnya :



OK, dari sini bisa kita simpulkan bahwa ketika kita ingin mendefinisikan suatu Model yang agak aneh yg Visual Studio or EF tidak bisa meng-create metadata untuk Model tersebut, Kita bisa melakukan modifikasi manual terhadap file edmx, dengan mendifinisikan metadata yg diperlukan pada SSDL, CSDL dan MSL section. Pada kasus ini kita mempergunakan element <DefiningQuery> untuk mendefinisikan T-SQL. For more information tentang <DefiningQuery> bisa dilihat di MSDN library.

OK. Semoga post ini berguna.

Share this post: | | | |
Posted by Agus Syahputra with 2 comment(s)
Filed under: , ,

Lazy Loading di Entity Framework 4

By Default di Entity Framework 4 (EF) Lazy Loading adalah enabled.

So, ketika kita meng-create Model, maka setting Lazy Loading akan bernilai true, kita bisa melihatnya di Model Properties.

Dengan setting Lazy Loading enabled maka process load Navigation Properties (Entity Collection or Entity Reference)

akan ditangguhkan.

Sebagai contoh misalkan kita create Model dari database Northwind.

Lalu misalkan kita punya kode seperti ini :

   1:  using (var context = new NorthwindEntities())
   2:  {
   3:      context.ContextOptions.LazyLoadingEnabled = true;
   4:      var sup = context.Suppliers.Where(s => s.Products.Count > 4);
   5:      foreach (var s in sup)
   6:      {
   7:          Console.WriteLine("Company : {0}", s.CompanyName);
   8:          foreach (var p in s.Products)
   9:          {
  10:              Console.WriteLine("{0} - {1}", p.ProductName, p.UnitPrice);
  11:          }
  12:          Console.WriteLine();
  13:      }
  14:  }

Maka EF akan mengirimkan beberapa command ke database untuk di execute.

Command yang pertama adalah untuk meng-query Supplier yg products-nya > 4

Sebagai hasil dari query ini, variabel sub akan berisi 2 supplier yg products-nya > 4

Lalu command yang lainnya akan dikirim ke database dan diexecuted setiap kali program meng-query s.Products (Line 8).

So, dalam kasus diatas, sekali program berjalan akan ada 3 perjalanan ke database yang terjadi. Pertama untuk meng-query sup dan yg dua lagi untuk meng-query s.Products. Ini bisa kita lihat dengan SQL Profiler :



Jika jumlah element sup hanya 2 supplier, maka itu mungkin tidak menjadi soal untuk melakukan 2 kali perjalan tambahan bolak-balik ke database. Tetapi bayangkan apabila dari hasil query sup tersebut menghasilkan lebih banyak element, misalkan 100 Supplier mempunyai jumlah products > 4. Maka akan ada 100 perjalanan tambahan bolak-balik ke database yg terjadi pada waktu kita meng-iterasi melalui foreach.

So, pada kasus-kasus tertentu Lazy Loading mungkin akan menjadi opsi yang kurang mantap karena bisa menurunkan performa(banyak perjalanan bolak-balik ke database).

Pada kasus diatas kita bisa mengatasi ini dengan men-disable Lazy Loading dengan men-set Lazy Loading to False.

context.ContextOptions.LazyLoadingEnabled = false;  

Lalu kita bisa melakukan Eager Load pada Entity Collection Products dengan menggunakan method Include dari class ObjectQuery like this :

var sup = context.Suppliers.Include("Products").Where(s => s.Products.Count > 4);

Dengan demikian maka Command yang dikirim kedatabase hanya 1 kali saja (1 kali perjalan bolak balik ke database). Yaitu waktu kita meng-query sup maka Command yang dibentuk oleh ObjectQuery juga akan meng-query Products, yang menghasilkan lebih banyak data yang dikirim balik(data yang berisi suppliers dan products). Tetapi ini masih lebih menguntungkan dari pada harus melakukan perjalan bolak-balik yang berlebihan ke database.

Share this post: | | | |

Sekilas tentang Garbage Collection dan Garbage Collector

Kemarin saya sempat menjawab pertanyaan di forum sebelah tentang Garbage Collection.

Dan saya ingin men-sharenya disini, mana tau berguna hehehe... :

Pertanyaan :

permisi para sesepuh sekalian.
saya ada hal yang agak ragu nih tentang Java.
yaitu mengenai "Garbage Collector".

Garbage Collector ini dikatakan ada di Java yang berguna untuk mendestroy Object yang sudah terbuat.

nah kalau setau saya, contoh seperti di bahasa Pemrograman C++, tidak ada yang namanya Garbage Collector. yang ada hanya Desctructor(~).
Di C++, Destructor dapat dipanggil oleh user untuk menghancurkan Object yang sudah tidak terpakai yaitu dengan menggunakan keyword delete pada pointer yang menunjuk suatu objek.

Pertanyaan saya di sini, Bagaimanakah cara kerja Garbage Collector tersebut?
karena di Java tidak ada yang namanya Destructor, sehingga tidak dapat menghapus objek sesuai dengan keinginan user.

Banyak juga yang mengatakan bahwa Garbage Collector lah yang menghapus Object yang telah tidak dipakai secara otomatis.
Nah Otomatisnya itu pas kapan yah?
apakah pada saat Program di Terminate?
kalau pada saat Program di Terminate, nampaknya masalah Boros Memory tidak akan teratasi apabila Object akan di hapus pada saat terakhir program di terminate. pastinya program yang selalu full dijalankan terus akan sering terjadi Down krna memory yang berlebihan.

Menurut Asumsi saya, Garbage Collector dijalankan pada saat suatu object tidak di tunjuk oleh satupun Object Reference.

Contohnya:

class Manusia
{
public Manusia()
{
System.out.println("Manusia terbentuk");
}
}
public class Program
{
public static void main(String[]args)
{
Manusia man;
man = new Manusia();//membuat objek pertama(sebut saja objek ke 1) yang ditunjuk oleh Object Reference man.
man = new Manusia();//membuat objek kedua(sebut saja objek ke 2) yang ditunjuk lagi oleh Object Reference man.
}
}

jadi pada saat Object Reference man telah menunjuk objek kedua. maka objek pertama tidak ada yang dapat mengaksesnya. bisa di bilang dia telah tidak dapat digunakan lagi.
kalau asumsi saya pada saat seperti inilah Garbage Collector aktif untuk menghapus objek ke 1 pada saat itu juga.

kira-kira Asumsi saya ini bener apa tidak??

Mohon Pencerahannya dari para sesepuh. . .
kalau bisa sih dijawab dari pendapat sesepuh dan juga disertakan link yang cukup memberikan informasi. . .

Terima kasih.

 

Jawaban :

Sebelumnya saya ingin mencoba menjawab secara singkat. Tapi ini adalah hanya berdasarkan pengetahuan saya tentang Garbage Collector di C#. Yang saya rasa kurang lebih sama dengan fungsi Garbage Collector di Java.

C# atau Java disebut sebagai Managed Language. Karena kita tidak perlu berurusan langsung dengan proses pembebasan memory seperti di C. Semuanya dilaksanakan otomatis oleh CLR di C# atau JRE di Java.
CLR : Common Language Runtime
JRE : Java Runtime Environment

Yang menjadi pertanyaan adalah bagaimanakah CLR melakukan pembebasan memory?

Singkatnya, CLR punya yang namanya suatu mekanisme Garbage Collection. Yaitu bisa diartikan sebagai proses pengumpulan sampah. Sampah yang dimaksud disini tentu saja object yang sudah tidak diperlukan lagi didalam aplikasi (misalnya, object yang sudah di-null-kan).

Garbage Collection itu adalah suatu mekanisme yang dilaksanakan dengan algoritma tersendiri untuk melacak state dari object(object masih terpakai atau tidak) dan untuk melakukan penghapusan object.

Garbage Collection terus berlangsung selama program kita berjalan. Pada saat-saat tertentu Garbage Collection akan melakukan pencatatan state dari suatu object. Dan apabila object tersebut sudah tidak terpakai lagi(misalnya tidak ada variabel yang merujuk kepada object tersebut) maka object ini kemudian akan dianggap sampah dan apabila program memerlukan memory tambahan maka object tersebut akan dihapus dan memory-nya dibebaskan. Yang melaksanakan semua mekanisme ini disebut sebagai Garbage Collector(Istilah lainnya Garbage Collector adalah sebuah bagian dari CLR yang menjalankan mekanisme Garbage Collection).

Jadi pada C#, object yang di-null-kan tidak langsung dihapus dari memory. Itu semua terserah dari Garbage Collector dan bagaimana Algoritma Garbage Collection itu dilaksanakan.

So, Kita tidak tau secara pasti kapan suatu object dihapus dari memory, tetapi ada suatu keadaan dimana, ketika object dihapus dari memory dan kita ingin program kita melaksanakan sesuatu. Untuk itu maka C# punya yang namanya Destructor.
Destructor di C# tidak lebih dari sebuah method yang akan dipanggil oleh CLR sesaat sebelum object dihapus dari memory. Makanya Destructor di C# namanya harus didahului dengan tanda ~, misalnya ~SomeClass. Tujuannya supaya program kita tidak bisa memanggil Destructor secara langsung. Jadi Destructor di C# bisa dikatakan sangat berbeda dengan destructor di C. Oleh karena itu Destructor di C# disebut juga Finalize Method. 

 

Pertanyaan :

gw selama ngoding c# ga pernah manggil destructor deh..
kira2 kasus kyk gmn ya perlu manggil destructor?

 

Jawaban :

Kalau misalnya agan coding pakai C#, ketika agan implement interface IDisposable agan harus manggil destructor.

 

 

 

Share this post: | | | |
Posted by Agus Syahputra with no comments
Filed under:

Kesalahan pada keyword lock

Untuk melakukan sinkronisasi terhadap thread, C# mempunyai lock keyword.

Keyword lock sebenarnya merupakan shorthand notation dari Try Finally block.

So, ketika compiler membaca code yang terdapat lock keyword, maka compiler akan menerjemahkannya menjadi seperti kode dibawah :

   1:   Boolean lockTaken = false; 
   2:     try { 
   3:        Monitor.Enter(key, ref lockTaken); 
   4:        // This code has exclusive access to the data... 
   5:     } 
   6:     finally { 
   7:        if (lockTaken) Monitor.Exit(key); 
   8:     }

Tetapi sebenarnya ada suatu masalah dengan penggunaan keyword lock.

Untuk lebih jelasnya kita perhatikan kode di bawah ini :

   1:  static class Program
   2:  {
   3:      static object key = new object();
   4:      static object[] data = { "Joni", "Pria" };
   5:     
   6:      static void UpdateData(object Nama, object Kelamin)
   7:      {
   8:          data[0] = Nama;        
   9:          data[1] = Kelamin;
  10:      }
  11:      static void AccessData(object value)
  12:      {
  13:          object[] objects = (object[])value;
  14:          try
  15:          {
  16:              lock(key)
  17:              {                
  18:                  int left = (int)objects[0];
  19:                  int top = (int)objects[1];
  20:                  Console.SetCursorPosition(left, top);
  21:   
  22:                  Console.WriteLine("ThreadID:{0} Read || Nama = {1}, Kelamin = {2}", 
  23:                      Thread.CurrentThread.ManagedThreadId, data[0], data[1]);
  24:   
  25:                  UpdateData(objects[2], objects[3]);
  26:              }            
  27:          }
  28:          catch (Exception ex)
  29:          {
  30:              MessageBox.Show(ex.Message);
  31:          }
  32:      }
  33:      static void Main()
  34:      {
  35:          ThreadPool.QueueUserWorkItem(AccessData, new object[] { 0, 0, "Susi", "Wanita" });
  36:          ThreadPool.QueueUserWorkItem(AccessData, new object[] { 0, 2, "Sule", "Pria" });            
  37:   
  38:          Console.ReadLine();
  39:      }
  40:  }

Pada kode diatas terdapat dua buah ThreadPool Thread yang keduanya sama-sama mengakses 1 method yang sama yaitu method AccessData (lihat baris 35 dan 36) dalam waktu yang hampir bersamaan.

Ketika memasuki method AccessData setiap ThreadPool Thread akan membaca dan mengupdate field data (baris 22 dan baris 25) dari class Program dengan memanggil method UpdateData.

So, ketika kode diatas dijalankan output yang ditampilkan adalah sebagi berikut :

ThreadID:10 Read || Nama = Joni, Kelamin = Pria
ThreadID:11 Read || Nama = Susi, Kelamin = Wanita

Ketika mengakses method AccessData Thread 10 akan membaca dari field data yang berisi nama "Joni" dan kelamin "Pria".

Setelah itu Thread 10 akan mengupdate field data dengan nama "Susi" dan kelamin "Wanita".

Setelah Thread 10 selesai, maka Thread 11 akan mengakses method AccessData dan kemudian membaca dari field data yang berisi nama "Susi" dan kelamin "Wanita.

Setelah itu Thread 11 akan mengupdate field data dengan nama "Sule" dan kelamin "Pria".

Yang menjadi masalah adalah apa yang terjadi ketika Thread 10 mengupdate data tetapi sebelum selesai mengupdate data kemudian terjadi suatu Exception (terjadi exception dimethod UpdateData).

Untuk mensimulasikannya bisa dengan menambahkan keword throw didalam method UpdateData :

   1:  static void UpdateData(object Nama, object Kelamin)
   2:  {
   3:      data[0] = Nama;
   4:      throw new ApplicationException("Update Data Failed");
   5:      data[1] = Kelamin;
   6:  }

Yang terjadi adalah, Thread 11 akan membaca data yang salah. Seperti terlihat dalam output berikut :

ThreadID:10 Read || Nama = Joni, Kelamin = Pria
ThreadID:11 Read || Nama = Susi, Kelamin = Pria

Itu disebabkan karena ketika Thread 10 selesai meng-throw sebuah Exception (line 4) kemudian Thread 10 akan kembali ke method AccesData dan akan mengeksekusi Finally block dari keyword lock (source code paling atas, line 6) yang didalamnya berisi pemanggilan method Monitor.Exit(key). Yang berakibat key dilepas dan karena itu Thread 11 bisa memperoleh key dan memperoleh akses untuk membaca field data(yang datanya kita anggap corrupt karena Nama berhasil diupdate tetapi Kelamin masih belum terupdate, seperti terlihat dalam output diatas).

So, untuk mengatasi ini maka lebih baik untuk tidak menggunakan keyword lock dan lebih baik kita secara explisit menggunakan class Monitor. Seperti terlihat dalam code berikut :

   1:  static void AccessData(object value)
   2:      {
   3:          object[] objects = (object[])value;
   4:          try
   5:          {
   6:              Monitor.Enter(key);
   7:              int left = (int)objects[0];
   8:              int top = (int)objects[1];
   9:              Console.SetCursorPosition(left, top);
  10:   
  11:              Console.WriteLine("ThreadID:{0} Read || Nama = {1}, Kelamin = {2}",
  12:                  Thread.CurrentThread.ManagedThreadId, data[0], data[1]);
  13:   
  14:              UpdateData(objects[2], objects[3]);
  15:              Monitor.Exit(key);
  16:          }
  17:          catch (Exception ex)
  18:          {
  19:              MessageBox.Show(ex.Message);
  20:          }
  21:      }

Dengan menggunakan Class Monitor, maka ketika terjadi exception pada waktu Thread 10 mengeksekusi method UpdateData, Thread 10 akan berhenti (ter-block) sehingga method Monitor.Exit tidak ter-eksekusi yang mengakibatkan key tidak direlease. Sehingga Thread 11 tidak memperoleh akses untuk membaca field data.

So, sekali lagi. Lebih baik menggunakan Monitor class secara explisit dari pada menggunakan keyword lock. Karena exception bisa saja terjadi didalam block kode lock.

Dan karena keyword lock merupakan shorthand notation Try Finally block, maka Finally block yang berisi kode untuk merilis key akhirnya pasti akan dieksekusi. Sehingga key terlepas dan Thread lain bisa memperoleh akses didalam block lock (source code paling atas, line 4) yang mengakibatkan Thread lain tersebut membaca data yang corrupt.

Share this post: | | | |
Posted by Agus Syahputra with no comments
Filed under: ,

Perbedaan call dan callvirt (Bagaimana CLR memanggil method)

Pada postingan kali ini saya ingin membahas tentang perbedaan call dan callvirt pada pemanggilan Method.

Dengan tujuan untuk mengetahui bagaimana sebenarnya CLR bertingkah laku ketika memanggil method.

Disini kita akan menggunakan dua buah class, yaitu Base yang mempunyai sebuah method SomeMethod

dan class Derived yang merupakan turunan dari class Base.

   1:  class First
   2:  {
   3:      protected int counter = 0;
   4:      public virtual void SomeMethod(int max)
   5:      {
   6:          Console.WriteLine("{0} - First.SomeMethod", " ");
   7:          if (counter != max) SomeMethod(max);
   8:      }
   9:  }
  10:  class Second : First
  11:  {
  12:      public override void SomeMethod(int max)
  13:      {
  14:          Console.WriteLine("{0} - Second.SomeMethod", ++counter);
  15:          base.SomeMethod(max);
  16:      }
  17:      static void Main()
  18:      {
  19:          Second s = new Second();
  20:          s.SomeMethod(9);
  21:   
  22:          Console.ReadLine();
  23:      }
  24:  }

Pada line 7 dan 15 First.SomeMethod akan dipanggil oleh CLR.

Disini CLR akan memperlakukan 2 pemanggilan tersebut dengan cara yang berbeda.

pada line 15 Second.SomeMethod akan memanggil First.SomeMethod menggunakan sintax "base.SomeMethod(max)"

yang mana First.SomeMethod akan dipanggil tidak secara virtual oleh CLR, ini bisa kita lihat pada IL code yang dihasilkan :

L_0024: call instance void First::SomeMethod(int32) 

Disini CLR akan memanggil SomeMethod yang ada pada First class tanpa mengecek apakah First.SomeMethod telah di override

oleh class turunannya.

pada line 7 First.SomeMethod akan memanggil dirinya sendiri (recursive) menggunakan sintax "SomeMethod(max)"

yang mana First.SomeMethod akan dipanggil secara virtual oleh CLR, ini bisa kita lihat pada IL code yang dihasilkan :

L_0020: callvirt instance void First::SomeMethod(int32)

Ketika SomeMethod dipanggil secara virtual maka CLR akan memanggil SomeMethod yang ada pada First class.

Lalu kemudian CLR akan mengecek apakah SomeMethod telah dioverride oleh class turunannya, jika iya maka SomeMethod

pada class turunannya yang akan di panggil (Second.SomeMethod)


Pemanggilan method secara virtual (callvirt) terjadi pada instance method yang kita deklarasikan sebagai virtual.

Pada instance method yang tidak dideklarasikan sebagai virtual maka pemanggilan method tetap akan terjadi secara virtual (callvirt).

Tetapi, pemanggilan virtual disini hanya untuk mengecek apakah instance dari class yang mendeklarasikan method tersebut sudah 

dicreate atau tidak (dengan kata lain untuk mengecek object null atau tidak) tetapi tidak mengecek apakah method tersebut di override atau tidak.

Sedangkan pada static method, method akan selalu di panggil langsung, tidak secara virtual.

Dan jika memanggil method menggunakan keyword base (seperti pada line 15) maka method juga akan di panggil langsung, tidak secara virtual.

Kalau misalnya memanggil method dengan keyword base (seperti pada line 15) di panggil secara virtual(callvirt) maka SomeMethod pada 

Second class akan dipanggil berulang-ulang yang menyebabkan stack over flow.

So, yang bisa kita ambil disini adalah, pendeklarasian method sebagai virtual akan membuat pemanggilan method lebih lama.

Karena CLR akan bekerja dua kali :

yaitu untuk mengecek apakah object null atau tidak, lalu untuk mengecek apakah method tersebut sudah di override atau tidak.

Itulah perbedaan antara call dan callvirt yang mengatur bagaimana CLR memanggil suatu method.

Kalau kita mengerti bagaimana tingkah laku dari CLR maka kita bisa menerapkannya untuk membuat aplikasi kita lebih

fleksibel. Seperti yang di praktekkan pada code diatas yang melakukan pemanggilan recursive (seperti pada line 15) tetapi selalu diakhiri dengan pemanggilan

method pada class turunannya (Second.SomeMethod).

output program :

Share this post: | | | |
Posted by Agus Syahputra with no comments
Filed under: , ,

base dan this adalah object yang sama, not different

Bagi yang belum tau sebenarnya kalau base dan this itu adalah mengacu pada object yang sama.

So, postingan kali ini ingin menunjukkan hal tersebut.

OK, perhatikan kode dibawah ini :

internal class Base
{
    internal static Object This;  
 
    //Alamat Base kita simpan didalam Base.This
    public Base() { This = this; }    
}
internal class Derived : Base
{
    internal static Object BaseAddress;
    internal Derived()
    {
        //Alamat Derived kita simpan didalam Derived.This
        This = this;
        BaseAddress = Base.This;
    }
}
 
internal static class Program
{
    private static void Main()
    {         
        Derived d = new Derived();
 
        //Cek apakah isi variable d dan Derived.ThisAddress sama
        Console.WriteLine("Are d and Derived.ThisAddress same ? :" + (d == Derived.This));
 
        //Cek apakah base dan this sama
        Console.WriteLine("Are base and this same ? :" + (Derived.BaseAddress == Derived.This));
        Console.ReadLine();
    }
}

Pada kode diatas, kita menyimpan alamat dari base pada Base.This lalu kita simpan kembali ke Derived.BaseAddress.

Lalu alamat dari Derived (this) kita simpan pada Derived.This.

Dan untuk membuktikan bahwa base dan this adalah mengacu pada object yang sama, kita mempergunakan static class Program.

Dan apabila Program di jalankan akan menampilkan bahwa base dan this mengacu pada object yang sama.

Jadi dari sini dapat kita analisa bahwa pada saat object Derived diinisialisasi,

maka struktur data dari Base dibuat dahulu oleh CLR, kemudian CLR membahkan struktur data Derived kedalam struktur data

dari Base, itulah sebabnya ketika  kita menginisialisasi Derived kita juga harus menginisialisasi Base dengan cara memanggil constructor

dari Base(Walaupun pada kode diatas kita tidak melakukannya karena telah dilakukan secara implisit oleh CLR) . So, intinya ketika kita

meng-create object Derived sebenarnya itu hanyalah terdiri dari satu object yaitu Derived saja dan bukan Base dan Derived.

Jadi, kalau misalnya ada kode seperti ini pada Base :

internal class Base
{    
    internal event EventHandler SameNumbers;
    private Random rnd = new Random();
    private Object key = new Object();
    private Int32? cache = null;
    internal void SetScrambler(Int32 left, Int32 top)
    {
        lock (key)
        {
            Thread.Sleep(50);
            Int32? temp = rnd.Next(0, 100);
            Console.SetCursorPosition(left, top);
            Console.Write(temp);
            if (temp == cache) OnSameNumbers(EventArgs.Empty);
            else cache = temp;
        }
    }
    internal void ScramblerGo()
    {
        ThreadPool.QueueUserWorkItem(arg => { while (true) SetScrambler(0, 0); });
        ThreadPool.QueueUserWorkItem(arg => { while (true) SetScrambler(4, 0); });
    }
    protected virtual void OnSameNumbers(EventArgs e)
    {
        EventHandler temp = SameNumbers;
        if (temp != null) temp(this, e);
    }
}
Lalu kita buat Derived dengan kode seperti ini :
internal class Derived : Base
{
    protected override sealed void OnSameNumbers(EventArgs e)
    {
        MessageBox.Show("OnSameNumbers : Numbers are same...");
        base.OnSameNumbers(e);
    }
}
Sebenarnya pada kode base.OnSameNumbers(e) diatas, Derived mengacu pada struktur datanya sendiri.
Karena memang object base itu dan object this adalah object yang sama.
Jadi sekali lagi, intinya CLR hanya membuat satu struktur data (object) yaitu object Derived saja
yang classnya bisa dijelaskan dengan pseudocode berikut :
internal class Derived 
{
    internal event EventHandler SameNumbers;
 
    //dengan catatan : fields private berikut ini ada(exist) distruktur data dari Derived
    //tetapi tidak bisa diakses dari Derived karena diprivatekan oleh Base
    private Random rnd = new Random();
    private Object key = new Object();
    private Int32? cache = null;
    internal void SetScrambler(Int32 left, Int32 top)
    {        
        lock (key)
        {
            Thread.Sleep(50);
            Int32? temp = rnd.Next(0, 100);
            Console.SetCursorPosition(left, top);
            Console.Write(temp);
            if (temp == cache) OnSameNumbers(EventArgs.Empty);
            else cache = temp;
        }
    }
    internal void ScramblerGo()
    {
        ThreadPool.QueueUserWorkItem(arg => { while (true) SetScrambler(0, 0); });
        ThreadPool.QueueUserWorkItem(arg => { while (true) SetScrambler(4, 0); });
    }
    private void OnSameNumbers(EventArgs e)
    {
        MessageBox.Show("OnSameNumbers : Numbers are same...");
        EventHandler temp = SameNumbers;
        if (temp != null) temp(this, e);
    }    
}

Itu sebabnya bila kita jalankan dengan static class Program berikut :

internal static class Program
{
    private static void Main()
    {
        Derived d = new Derived();
        d.SameNumbers += (sender, e) => MessageBox.Show("In Lambda : Numbers are same...");
        d.ScramblerGo();
        Console.ReadLine();
    }
}
Maka, "OnSameNumbers : Numbers are same..." akan tampil lebih dulu dari pada "In Lambda : Numbers are same...".
Karena memang pada kenyataannya pada class Derived "OnSameNumbers : Numbers are same..." ditampilkan terlebih
dahulu baru kemudian Event diRaise.
Share this post: | | | |
Posted by Agus Syahputra with 2 comment(s)
Filed under: ,

Threading Model dan Synchronization Context

Pada .Net Framework terdapat yang namanya threading model yang terkait dengan suatu application model.

Contohnya ASP.Net Web Application mempunyai threading modelnya sendiri, sementara Windows Forms mempunyai threading

modelnya sendiri.

Threading Model pada Windows forms yaitu, thread yang bisa meng-update state dari suatu control

adalah thread yang telah meng-create control tersebut. Jadi thread pool thread tidak di izinkan untuk untuk mengupdate state

dari suatu control.

Contohnya pada code di bawah ini, kita mempunya suatu method ResponseToFunc yang akan di invoke ketika

thread pool thread selesai meng-invoke method targetnya si delegate(Func<Int32, Int32>).

internal sealed class MyForm : Form
{       
    private static Func<Int32Int32> func = arg =>
    {        
        Int32 result = 0;
        for (Int32 i = 0; i < arg; i++)
        {
            Thread.Sleep(300);
            Console.WriteLine("Result : " + (result = i));
        }
        return result;
    };
       
    internal MyForm()
    {
        //Main Thread men-set state dari MyForm
        Console.WriteLine("Thread Name : {0}, IsThreadPoolThread : {1}",
            Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread);
        Width = Height = 400;        
    } 
    private void ResponseToFunc(IAsyncResult arg)
    {
        //ThreadPoolThread mengeksekusi method ini
        Console.WriteLine("IsThreadPoolThread : " + Thread.CurrentThread.IsThreadPoolThread);
        try
        {
            Int32 result = func.EndInvoke(arg);            
            Console.WriteLine("action complete, result = {0}", result);            
 
            //Thread Pool Thread tidak diizinkan untuk mengupdate state
            //dari suatu control, sehingga kode di bawah akan menyebabkan error.
            Text = "func complete";
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
 
    protected override void OnMouseClick(MouseEventArgs e)
    {        
        Text = "Initial Text";        
        func.BeginInvoke(5, ResponseToFunc, null);   
        base.OnMouseClick(e);
    }
}
internal static class Program
{    
    private static void Main()
    {        
        Thread.CurrentThread.Name = "MainThread";
        MyForm mf = new MyForm();
        Application.Run(mf); 
                 
        Console.ReadLine();
    }
} 

Pada code diatas akan menyebabkan error karena thread pool thread tidak di izinkan merubah Text dari form.



So, untuk meng-update Text dari MyForm maka kita harus menggunakan MainThread.

Karena MainThread yang menginisialisasi MyForm dan hanya MainThread yang bisa meng-update state dari MyForm.

Yang menjadi pertanyaan adalah "bagaimana kita bisa menggunakan MainThread dari dalam method(method ResponseToFunc) yang di eksekusi

oleh Thread Pool Thread ?"

Kita bisa menggunakan object SynchronizationContext untuk melaksanakan ini.

Kita mereferensi object dari SynchronizationContext dengan cara memanggil static property SynchronizationContext.Current

dari dalam method yang diakses oleh MainThread (dalam kasus ini method OnMouseClick).

Lalu kita panggil method Post dari object SynchronizationContext, dan kita isi parameternya dengan delegate yang method targetnya

berisi kode untuk meng-update Text dari MyForm. Untuk lebih jelasnya seperti dalam kode berikut :

internal sealed class MyForm : Form
{
    //field untuk menyimpan referensi dari object SynchronizationContext     
    private static SynchronizationContext syncContext; 
    private static Func<Int32Int32> func = arg =>
    {        
        Int32 result = 0;
        for (Int32 i = 0; i < arg; i++)
        {
            Thread.Sleep(300);
            Console.WriteLine("Result : " + (result = i));
        }
        return result;
    };
       
    internal MyForm()
    {
        //Main Thread men-set state dari MyForm
        Console.WriteLine("Thread Name : {0}, IsThreadPoolThread : {1}",
            Thread.CurrentThread.Name, Thread.CurrentThread.IsThreadPoolThread);        
        Width = Height = 400;        
    }
 
    //ResponseToFunc akan akan diinvoke setelah method target si delegate Func<Int32, Int32>
    //selesai di eksekusi
    private void ResponseToFunc(IAsyncResult arg)
    {
        //ThreadPoolThread mengeksekusi method ini
        Console.WriteLine("IsThreadPoolThread : " + Thread.CurrentThread.IsThreadPoolThread);
        try
        {
            Int32 result = func.EndInvoke(arg);            
            Console.WriteLine("action complete, result = {0}", result);
 
            //syncContext.Post akan menginvoke sebuah method yang meng-update
            //state dari MyForm dengan menggunakan MainThread.            
            syncContext.Post(arg => {
 
                //Update text dilakukan oleh MainThread 
                Text = "func complete";
            }, null);            
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
 
    protected override void OnMouseClick(MouseEventArgs e)
    {
        //Mendapatkan referensi ke object SynchronizationContext
        syncContext = SynchronizationContext.Current;
        Text = "Initial Text";        
        func.BeginInvoke(5, ResponseToFunc, null);   
        base.OnMouseClick(e);
    }
}



Gambar diatas menunjukkan MainThread berhasil mengupdate Text dari MainForm via
SynchronizationContext

 

Share this post: | | | |

Serialisasi pada object yang tidak serializable

Ini adalah kode yang menjelaskan bagaimana kita melakukan serialisasi pada object yang tidak serializable.

Disini kita menggunakan surrogate untuk melakukan serialization.

Dan apabila object yang akan kita serialkan tersebut ternyata type dari value-nya si field tidak serializable

maka field tersebut tetap kita serialkan tetapi valuenya kita set menjadi null.

 

//class SurrogateType yang meng-implementasi ISerializationSurrogate
//sebagai surrogate yang akan melaksanakan serialization 
//untuk type yang tidak serializable
internal sealed class SurrogateType : ISerializationSurrogate 
{
    //bindingflags sebagai flag yang menentukan karakterisitik field 
    //yang akan diserialkan
    private static BindingFlags bf = BindingFlags.Public | BindingFlags.Instance |
                                    BindingFlags.NonPublic | BindingFlags.Static;
 
    //obj(parameter pertama) adalah referensi ke object yang sesungguhnya(dalam hal ini object yang 
    //tipenya tidak serializable)
    public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
    {        
        //mendapatkan semua fields pada object
        FieldInfo[] arr_fi = obj.GetType().GetFields(bf);
     
        //mendapatkan semua fields value pada object
        Object[] values = FormatterServices.GetObjectData(obj, arr_fi);
        Int32 counter=0;        
        
        foreach (var v in arr_fi)
        {
            //cek apakah field tidak sama dengan null
            if (v.GetValue(obj) != null)
            {
                //jika value-nya si field tidak sama dengan null, periksa apakah type value
                //dari si field menggunakan attribute serializable (jadi yang diperiksa
                //bukan type dari si field, melainkan type valuenya).
                //Jika hasilnya betul maka set SerializationInfo dengan nama si field, dan value si field.
                //Jika tidak, maka type value dari si field tidak serializable dan kita set
                //SerializationInfo dengan nama si field tapi dengan value null.
                if (v.GetValue(obj).GetType().IsDefined(typeof(SerializableAttribute), false))
                {
                    info.AddValue(v.Name, values[counter]);
                }
                else
                {
                    info.AddValue(v.Name, null);
                }
            }
            //jika field sama dengan null maka set SerializationInfo dengan nama si field
            //dan valuenya kita isi dengan null
            else
            {
                info.AddValue(v.Name, null);
            }
            counter++;            
        }        
    }
 
    //obj(parameter pertama) adalah referensi ke object yang sesungguhnya(dalam hal ini object yang 
    //tipenya tidak serializable) yang telah di serialkan dan sedang di deserialkan. Objectnya sendiri
    //sudah ada didalam memory karena sudah diinstan-kan oleh formatter tetapi fields-nya masih 
    //kosong
    public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {   
        //mendapatkan semua fields dari object yang telah dibuat instan-nya oleh formatter
        //setelah serialization(dalam hal ini object masing kosong, fieldnya belum diisi
        //karena constructor tidak dipanggil dan method inilah yang akan meng-set fields
        //dari object tersebut) 
        //dan kita akan mengisi value dari fields tersebut dengan value yang terdapat
        //dalam object SerializationInfo
        FieldInfo[] arr_fi = obj.GetType().GetFields(bf);
                
        Object[] values = FormatterServices.GetObjectData(obj, arr_fi);                
        
        foreach (var v in arr_fi)
        {            
            v.SetValue(obj, info.GetValue(v.Name, v.FieldType));
        }        
        return obj;
    }
}
 
internal class Program
{
    private static Stream StreamMedium= new FileStream("file.txt"FileMode.Create);
    private static IFormatter formatter = new SoapFormatter();
    private static SurrogateSelector surrogate = new SurrogateSelector();
    private static Int32 Int32IsSerializable = 0;
    
    private static void SurrogateDemo(Object graph)
    {
        //print object sebelum serialisasi
        Console.WriteLine(graph);
 
        //assign tipe yang akan di-surrogate-kan
        surrogate.AddSurrogate(graph.GetType(), formatter.Context, new SurrogateType());
 
        //assign SurrogateSelector ke formatter
        formatter.SurrogateSelector = surrogate;
 
        //formatter akan menggunakan surrogate untuk melakukan 
        //serialization pada object
        formatter.Serialize(StreamMedium, graph);
 
        //nulling variabel graph
        graph = null;
        StreamMedium.Position = 0;
 
        //print state dari object yang telah diserialkan sebagai bukti
        //kalau serialization berhasil
        Console.WriteLine(new StreamReader(StreamMedium).ReadToEnd());
 
        ////formatter akan menggunakan surrogate untuk melakukan 
        //deserialization pada object
        graph = Deserializer(StreamMedium);
 
        //print object sebagai bukti kalau deserialisasi berhasil
        Console.WriteLine(graph);        
    }
 
    private static void Main()
    {        
        Object obj = new Program();
        SurrogateDemo(obj);        
    }
 
    public override string ToString()
    {
        return String.Format("Program.StreamMedium = {0}\n" +
                             "Program.formatter = {1}\n" +
                             "Program.surrogate = {2}\n" +
                             "Program.Int32IsSerializable = {3}\n",
                             StreamMedium, formatter, surrogate, Int32IsSerializable);
    }
}
Output Program :
Share this post: | | | |
Posted by Agus Syahputra with no comments

Munggunakan surrogate untuk melakukan serialisasi

Ini adalah sample kode yang menjelaskan bagaimana menggunakan surrogate untuk melakukan serialisasi.

Sehingga kita bisa take control pada class yang tidak melaksanakan ISerializable.

 

//class WillBeSurrogated yang tidak meng-implement ISerializable
[Serializable]
internal sealed class WillBeSurrogated
{
    private Int32 SerializedNumber;
 
    //field yang tidak diserialkan
    [NonSerialized]
    private Int32 UnserializedNumber;
    public WillBeSurrogated(Int32 SerializedNumber, Int32 UnserializedNumber)
    {
        this.SerializedNumber = SerializedNumber;
        this.UnserializedNumber = UnserializedNumber;
    }
    public override string ToString()
    {
        return String.Format("WillBeSurrogated.SerializedNumber = {0}\n" +
                             "WillBeSurrogated.UnserializedNumber = {1}\n", SerializedNumber, UnserializedNumber);
    }    
}
//class SurrogateType yang meng-implementasi ISerializationSurrogate //sebagai surrogate yang akan melaksanakan serialization  //untuk class WillBeSurrogated internal sealed class SurrogateType : ISerializationSurrogate  {     public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)     {         //mendapatkan fields dari obj WillBeSurrogated yang bisa diserialkan         MemberInfo[] arr_mi = FormatterServices.GetSerializableMembers(obj.GetType());         //mendapatkan value dari masing-masing fields di obj WillBeSurrogated         Object[] values = FormatterServices.GetObjectData(obj, arr_mi);         Int32 counter=0;         //menambahkan value dari obj WillBeSurrogated ke object SerializationInfo         foreach (var v in arr_mi)         {             info.AddValue(v.Name, values[counter]);              counter++;         }     }
    //obj pada parameter pertama adalah instant dari class WillBeSurrogated     //yang telah di-instantiate oleh formatter tapi belum diisi fields nya     public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)     {         //mendapatkan fields dari obj WillBeSurrogated yang bisa di serialkan         MemberInfo[] arr_mi = FormatterServices.GetSerializableMembers(obj.GetType());         //mendapatkan value dari masing-masing fields di obj WillBeSurrogated         Object[] values = FormatterServices.GetObjectData(obj, arr_mi);                 FieldInfo fi;         //menambahkan value dari obj SerializationInfo ke object WillBeSurrogated         foreach (var v in arr_mi)         {             fi = (FieldInfo)v;             fi.SetValue(obj, info.GetValue(fi.Name, fi.FieldType));         }                 return obj;     } }
internal
 class Program {     private static Stream CacheStream;     private static IFormatter formatter;         private static void SurrogateDemo()     {         Object wbs = new WillBeSurrogated(10, 20);         Console.WriteLine(wbs);         CacheStream = new MemoryStream();         formatter = new BinaryFormatter();         SurrogateSelector ss = new SurrogateSelector();         //assign tipe yang akan di-surrogate-kan         ss.AddSurrogate(typeof(WillBeSurrogated), formatter.Context, new SurrogateType());         //assign SurrogateSelector ke formatter         formatter.SurrogateSelector = ss;         //formatter akan menggunakan surrogate untuk melakukan          //serialization pada obj wbs         formatter.Serialize(CacheStream, wbs);         //nulling variabel wbs         wbs = null;         CacheStream.Position = 0;         //formatter akan menggunakan surrogate untuk melakukan          //deserialization pada obj wbs         wbs = formatter.Deserialize(CacheStream);         Console.WriteLine(wbs);             }     private static void Main()     {         SurrogateDemo();     } }
Share this post: | | | |
Posted by Agus Syahputra with no comments
Filed under: ,

Implementasi ISerializable pada Derived Type yang Inherited dari Base Type Non ISerializable

Ketika kita mau meng-implement ISerializable pada Derived type yang inherited dari Base type yang telah meng-implement ISerializable,

biasanya kita memanggil base.GetObjectData. Seperti yang ditunjukkan pada kode berikut :

[Serializable]
class Base : ISerializable
{
    Int32 FirstNumber;
    Int32 UnserializedNumber;
    public Base(Int32 FirstNumber)
    {
        this.FirstNumber = FirstNumber;
        UnserializedNumber = FirstNumber * 10;
    }
    protected Base(SerializationInfo info, StreamingContext context)
    {
        FirstNumber = info.GetInt32("FirstNumber");
    }    
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("FirstNumber", FirstNumber);
    }
    public override String ToString()
    {
        return String.Format("FirstNumber = {0}\n" + 
                             "UnserializedNumber = {1}", FirstNumber, UnserializedNumber);
    }
}
[Serializable]
class Derived : BaseISerializable
{
    Int32 SecondNumber;
    public Derived(Int32 SecondNumber): base(SecondNumber)
    {
        this.SecondNumber = SecondNumber * 2;
    }
    Derived(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        SecondNumber = info.GetInt32("SecondNumber");
    }
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("SecondNumber", SecondNumber);
 
        //memanggil base.GetObjectData() agar 
        //Serialization bisa berjalan dengan baik
        base.GetObjectData(info, context);
    } 
    public override string ToString()
    {
        return String.Format(base.ToString() + "\n" +
                            "SecondNumber = " + SecondNumber);
    }   
}
static class Program {     static MemoryStream CacheStream = null;         static BinaryFormatter formater = null;       static void Serializer(Object graph)     {         CacheStream = new MemoryStream();         formater = new BinaryFormatter();         formater.Serialize(CacheStream, graph);         Console.WriteLine("Serialization complete");     }     static Object Deserializer(MemoryStream stream)     {         formater = new BinaryFormatter();         FormatterConverter converter = new FormatterConverter();         stream.Position = 0;
return formater.Deserialize(stream);     }     static void Main()     {         Object d = new Derived(10);         Serializer(d);       
//nulling variable d         d = null;         d = Deserializer(CacheStream);         Console.WriteLine(d);             }     }
Tapi bagaimana apabila kita hendak meng-implement ISerializable pada Derived type
yang inherited dari Base type yang tidak meng-implement ISerializable ?  
Untuk melakukannya maka kita bisa mengenumerasi semua field dari object Derived dan menyimpannya kedalam object 
SerializationInfo, seperti yang ditunjukkan pada kode berikut sehingga object Derived bisa diserialkan.
[Serializable]
class BaseNotISerializable
{
    Int32 FirstNumber;
    Int32 UnserializedNumber;
    public BaseNotISerializable(Int32 FirstNumber)
    {
        this.FirstNumber = FirstNumber;
        UnserializedNumber = FirstNumber * 10;
    }
    public override String ToString()
    {
        return String.Format("FirstNumber = {0}\n" +
                             "UnserializedNumber = {1}", FirstNumber, UnserializedNumber);
    }
}
[Serializableclass DerivedISerializable : BaseNotISerializableISerializable  {     Int32 SecondNumber;     public DerivedISerializable(Int32 SecondNumber) : base(SecondNumber)     {         this.SecondNumber = SecondNumber * 2;     }         DerivedISerializable(SerializationInfo info, StreamingContext context) : base(0)     {         //mendapatkan semua field dari Derived yang bisa diserialkan         //dengan memanggil FormatterServices.GetSerializableMembers         MemberInfo[] arr_mi = FormatterServices.GetSerializableMembers(this.GetType());         FieldInfo fi;         //isi nilai semua field dengan value yang disimpan pada object SerializationInfo         foreach (var v in arr_mi)         {             fi=(FieldInfo)v;             fi.SetValue(this, info.GetValue(fi.Name, fi.FieldType));         }           }         public void GetObjectData(SerializationInfo info, StreamingContext context)     {         //mendapatkan semua field dari Derived yang bisa diserialkan         //dengan memanggil FormatterServices.GetSerializableMembers         MemberInfo[] arr_mi = FormatterServices.GetSerializableMembers(this.GetType());         FieldInfo fi;         //isi object SerializationInfo dengan nilai dari semua field          //yang ada pada derived dengan cara enumerasi         foreach (var v in arr_mi)         {             fi = (FieldInfo)v;             info.AddValue(fi.Name, fi.GetValue(this));         }             }     public override string ToString()     {         return String.Format(base.ToString() + "\n" +                             "SecondNumber = " + SecondNumber);     } }
static class Program
/*definition class Program*/     static void Main()     {         Object d = new DerivedISerializable(10);         Serializer(d);

//nulling variable d
        d = null;         d = Deserializer(CacheStream);         Console.WriteLine(d);     }     }
Share this post: | | | |
Posted by Agus Syahputra with no comments

KRITIK untuk ADMIN .netIndonesia. MOHON DIBACA

Sebelumnya maaf kalau postingan saya agak pedas. Tapi ini demi kemajuan netindonesia.net.

 Saya mau menanyakan sebenarnya komunitas ini(.netindonesia.net) ada yang ngurus gak sih.

-Karena saya coba daftar di milis tapi gak pernah diapprove.
-Kenapa selalu ada pesan download IE8 dihalaman depan, padahal saya menggunakan IE9.
-Trus saya ada menjawab beberapa pertanyaan di forum tapi apa yang saya posting gak pernah diapprove, kan kasian yang udah nanya gak pernah dapat jawaban.
-Saya juga promosiin komunitas ini di kaskus.us supaya semakin banyak yang join. Tapi ada yang bilang kalau dia sign up di sini gak pernah diapprove sampai sekarang.
-Trus materi-materi juga sekarang udah sangat jarang sekali diupdate(contohnya channel geeks gak pernah ada update lagi)
-Link Ebooks juga sering error, trus ebooknya gak ada(lebih bagus dihapus aja itu link).
-Trus editor untuk nulis blog juga parah, saya mau ngepost malah habis waktu untuk nge-format postingan saya. Saya heran kenapa editor sperti itu yang digunakan inikan komunitas untuk developer programmming, dan orang-orang yang mau belajar jadi developer, jadi kalau mau nge-post source code malah jadi repot karena editornya terbatas(nggak bisa format otomatis untuk source code)
-Kenapa alamat kontak(email address) para pengelola komunitas ini tidak ada, jadinya saya terpaksa posting di sini untuk menyampaikan masukan dan kritikan saya.
-Halaman about juga gak ada.

Untuk admin dan para pengelola komunitas ini, tolong dong dibenerin nih komunitas. Inikan komunitas untuk para developer/profesional. Masak ngurus komunitasnya sendiri gak becus. Komunitas ini kan dijadiin resource bagi kita-kita yang mau belajar, tapi  kalau manajemennya seperti ini siapa yang mau belajar di sini(ibaratnya kita ngajarin orang buat website tapi kita sendiri gak becus buat website)


Share this post: | | | |
Posted by Agus Syahputra with 4 comment(s)
More Posts Next page »