config.ru for Sinatra Applications

10 ธ.ค.

Sinatra::Application class is a Rack objec, i.e. it responds_to? call(env).

Per this documentation, it’s as easy as requiring your application file and run:

#config.ru
require './app'run Sinatra::Application

Cascade Merge Entity in NHibernate

13 ก.ย.

Thou shall not forget to change thy configuration from cascade="save-update" to cascade="merge" when thou uses Session.Merge() to save thy object.

ป้ายกำกับ:

Unable to find ‘Spring.Proxy’ version 1.0.0.0

31 ส.ค.

If you get this error when calling Spring-based service hosted on IIS, and your service uses NHibernate for data access, and your service returns some objects that must be serialized…check your object graph.

Make sure there is no NHibernate proxy in the object graph (i.e. do not use lazy loading).

Include Null Properties in NHibernate QBE

14 มี.ค.

To include properties with null values in NHibernate’s Query By Example (QBE), simply call ExcludeNone() on your created example.

For example, a class called ProductSize has 5 properties called Id, Dimension1, Dimension2, Dimension3, and Dimension4. All properties except Dimension1 are nullable. The combination of all dimension fields are unique for each record.

If I pass in an Example object with only Dimension1 set to 1, I want a search criteria like:

where Dimension1 = 1 and Dimension2 is null and Dimension3 is null and Dimension4 is null

NHibernate’s defaul behavior is to exclude all properties with null values when you create an Example object. So, this code would not work:

var productSize = new ProductSize(1, 2, null, null);
var sampleSize = Example.Create(productSize);
var result = session.CreateCriteria<ProductSize>()
    .Add(sampleSize)
    .UniqueResult();

The generated SQL expression is

where Dimension1 = 1 and Dimension2 = 2

As a result, more than one record may be returned because the query does not include Dimension3 and Dimension4, and you’ll get an IncorrectResultSizeDataAccessException.

Here’s the code that works:

var productSize = new ProductSize(1, 2, null, null);
var sampleSize = Example.Create(productSize).ExcludeNone();  // Include all properties.
var result = session.CreateCriteria<ProductSize>()
    .Add(sampleSize)
    .UniqueResult();

Using ExcludeNone() tells NHibernate to generate SQL expression based on all properties, including those will null values.

ป้ายกำกับ:

BindingSource xxxChanged Event Behavior

8 มี.ค.

Some observations on .NET BindingSource CurrentChanged/PositionChanged/ListChanged event behaviors. Here’s the code for producing the observations.

private DataTable table = new DataTable();
private int i = 1;

private void Form1_Load(object sender, EventArgs e)
{
    // Hack: To prevent CurrentChanged from raising more than once, uncomment the line below.
    // bindingSource1.DataSource = table;

    table.Columns.Add("c1", typeof(string));
    for (int j = 0; j < 5; j++)
    {
        var row = table.NewRow();
        row["c1"] = "row " + i;
        i++;
        table.Rows.Add(row);
    }
    dataGridView1.DataSource = bindingSource1;
    bindingSource1.DataSource = table;
}

When we set DataSource for bindingSource1, CurrentChanged event is raised 3 times, ListChanged event twice, and PositionChanged event once.

When removing the last row, PositionChanged is raised twice (Position set to -1), while CurrentChanged and ListChanged are raised once per each event.

Clearing the table has the same effect has removing the last row.

Removing the first row (when there are more rows below) does not raise PositionChanged event because the Position stays the same. Only the selected value changes.

In summary:

  • CurrentChanged works well in general, but we need to apply some hack to make sure the event is not raised many times on first initialization.
  • PositionChanged works well for initialization, but not for tracking addition, removal, or clearing. Use it if you only care about position, and be aware that clearing raises the event twice.
  • ListChanged…haven’t found a use case for it yet.

Update (Mar 8, 2010)…
After playing with the code some more, a semi-automatic approach will hopefully keep me more sane while debugging in the long run. The idea is when setting the DataSource, use unsubscribe-resubscribe procedure as mentioned in this post, then get the Current value manually. When adding, removing, clearing, selecting items, use CurrentChanged event as usual.

How To Test NHibernate Eager Loading with Spring Framework

6 มี.ค.

After spending a few nights trying to test NHibernate eager loading with Spring.NET framework’s AbstractTransactionalDbProviderSpringContextTests, I finally found the trick.

First, clear the session (cache). Second, reload the object. Third, close the session. Finally, verify that the dependent object was loaded correctly and not proxied.

The code below shows a test to check that the product type is loaded with a product.

[Test]
public void GetProductShouldAlsoLoadProductType()
{
    var productType = productTypeRepository.Get(0);

    Product obj = new Product("A product", productType);

    productRepository.Store(obj);

    Flush();

    // Empty session cache. Clear all loaded objects.
    SessionFactory.GetCurrentSession().Clear();   // (1)

    var loaded = productRepository.Get(obj.Id); // (2)
    Assert.That(loaded, Is.Not.Null);

    // Close current session to make sure relevant objects are not proxied.
    EndTransaction();                                           // (3)

    Assert.That(NHibernateUtil.IsInitialized(loaded.ProductType), Is.True);
    Assert.That(loaded.ProductType.Name, Is.Not.Null.Or.Empty);
}

Note that the order of execution is important. You must clear the cache (1) before reloading the object (2) before closing the session (3) before testing your assertions.

If we do not clear the session cache in step (1), reloading the object (2) then closing the session (3) will still make the test pass. The reason is that even though the session is closed, the proxied object is there in the cache, literally meaning it has been loaded (and initialized). You can access the product type’s properties without any errors.

On the other hand, if you clear the session cache in step (1), loading the object in step (2) will create a proxy to ProductType class instead of a fully initialized object. When you close the session (3), the assertions will fail, and if you access any property of product type (besides its ID), you’ll get an exception saying the session has been closed.

ป้ายกำกับ:

Strong-typed DataSet เป็นอันตรายสำหรับสถาปัตยกรรมระบบ

29 ม.ค.

สำหรับใครก็ตามที่ต้องทำงานกับระบบที่มีขนาดย่อมขึ้นไปจนถึงใหญ่ ควรหลีกเลี่ยง strong-typed DataSet/DataAdapter ให้ไกลที่สุดเท่าที่จะเป็นไปได้ เพราะ strong-typed DataSet:

  1. ไม่เหมาะกับการสร้าง domain model ที่ดีเหมือน PONO และไม่ natural เพราะมันเป็นเหมือน container ขนาดใหญ่ที่ปนเรื่อง domain กับ persistence ไว้ด้วยกัน (DataTable ไม่ใช่ domain model)
  2. ความที่เป็น container ขนาดใหญ่ทำให้มีต้นทุนการ serialize/deserialize สุง (และจะยิ่งน่าเบื่อเมื่อ DataSet คุณมีประมาณ 100 table) ทั้งตอนพัฒนาด้วย designer และตอนใช้งานใน form (บางครั้งการสร้าง DataSet ขนาดใหญ่เพื่อที่จะใช้แค่ไม่กี่ตารางใช้เวลามากกว่าการ load form เสียอีก)
  3. ทำให้ต้องยึดติดกับ designer และ code generator ที่ทำงานช้า (สำหรับคนที่ไม่มีเครื่องแรง ๆ)
  4. ทำให้ขาดความยืดหยุ่นในการทำงานกับ database อื่น ๆ และยากที่จะ decouple เรื่อง persistence ออกมา ถ้าคุณต้องการเปลี่ยน DB คุณต้อง generate DataSet ใหม่ และไปแก้ไข compile error ที่อื่น ๆ อีกด้วย
  5. ไม่เหมาะกับการทำงานใน application layer (service layer) เพราะ strong-typed TableAdapter ไม่มีอะไรที่ expose เรื่องการจัดการ transaction แบบ conversation (ถ้าใช้ TransactionScope อาจจะปวดหัวมากขึ้นกับปัญหาอื่น เช่นการ configure DTC เป็นต้น)

สรุปได้ว่า การใช้ strong-typed DataSet เป็นศูนย์กลางของสถาปัตยกรรมระบบเพื่อแทน model และ data layer ทำให้ระบบขาดความยืดหยุ่นในระยะยาว

ถ้าคุณเปลี่ยน model เป็น PONO หลังจาก DataSet คุณมีประมาณ 100 ตาราง ก็แทบไม่ต่างอะไรกับการ rewrite ระบบใหม่ เพราะเปลี่ยน model ก็ต้องเปลี่ยนเรื่อง persistence และต้องเปลี่ยน UI อีกต่างหาก (ไม่ว่าจะเป็นการเรียกใช้ data layer หรือ data binding กับ model)

คำแนะนำ: หลีกเลี่ยง strong-typed DataSet หรือเปลี่ยนไปใช้สถาปัตยกรรมที่อิงกับ PONO ให้เร็วที่สุดก่อนที่ DataSet จะมีขนาดใหญ่เกินไป

Spring: TypeInitializationException เวลาเรียก GetContext()

1 ม.ค.

ถ้าคุณเจอ TypeInitializationException แบบนี้เวลาเรียกใช้ ContextRegistry.GetContext():

An unhandled exception of type 'System.TypeInitializationException' occurred in WindowsApplication1.exe

Additional information: The type initializer for 'Spring.Context.Support.ContextRegistry' threw an exception.

ลองเช็คดูว่ามีการใช้ PropertyPlaceholderConfigurer หรือเปล่า? ถ้ามี ให้เพิ่ม handler สำหรับ section เข้าไปใน app.config ด้วย:

<configSections>
  ...
  <section name="databaseSettings"
           type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
</configSections>

ป้ายกำกับ:,

Spring.NET: ทำไมต้อง virtual สำหรับ method ที่มี [Transaction] attribute

30 ธ.ค.

กรณีที่ class มี method ที่ใช้ [Transaction] attribute และ class นั้นไม่มี interface เราต้อง declare method นั้นเป็น virtual เช่น

// Class does not define an interface.
public class CustomerDao
{
  // We must declare this transactional method as virtual.
  [Transaction]
  public virtual void Save(Customer obj)
  {
    // Save to database.
  }
}

ที่จริงเราต้อง define ทุกๆ method และ property ที่ต้องการให้ Spring ใช้เป็น virtual ด้วยซ้ำ เพราะว่า Spring สร้าง class proxy โดยการสร้างอีก class นึงที่ inherit มาจาก CustomerDao และ override ทุก public method/property ที่สามารถ override ได้ (Decorator pattern) เพื่อเพิ่ม advice เข้าไป ส่วน method ที่ไม่ใช่ virtual ไม่สามารถถูก override ได้ เพราะฉะนั้น Spring ก็จะไม่ยุ่งอะไรกับ method พวกนี้

[Transaction] attribute ไม่สามารถทำงานได้กับ method ที่ไม่มี virtual เพราะ Spring ไม่สามารถเพิ่ม transaction advice ครอบ method นั้นๆ ได้

ลองอ่านเพิ่มเติมได้ที่หัวข้อ 13.5.4 Proxying Classes

ป้ายกำกับ:

Spring.NET : NullReferenceException เวลาใช้ field ของ dependency พร้อมๆ กับ Transaction

30 ธ.ค.

สมมุติว่าเราตั้งค่า object อันนึงใน Spring.NET ชื่อ dataService ซึ่งใช้อีก object นึงชื่อ adoTemplate ทั้งสอง object ถูกตั้งค่าไว้แล้ว

<object id="adoTemplate" type="Spring.Data.Core.AdoTemplate, Spring.Data">
  <property name="DbProvider" ref="dbProvider"/>
</object>
<object id="dataService" type="MyApp.DataService, MyApp">
  <property name="AdoTemplate" ref="adoTemplate"/>
</object>

DataService เป็น class ใน service layer ธรรมดา ไม่มี interface ไม่มี base class อะไร

[Service]
public class DataService
{
  private AdoTemplate adoTemplate;
  
  public AdoTemplate AdoTemplate
  {
    set { adoTemplate = value; }
  }

  [Transaction]
  public void MyMethod()
  {
    // This code does not work.
    this.adoTemplate.Execute(...);
    this.adoTemplate.Execute(...);
  }
}

โค้ดด้านบนเป็นโค้ดที่ผิด เพราะสองสาเหตุ

สาเหตุแรกคือ เราไม่ได้ใช้ property AdoTemplate แต่ใช้ field ที่ชื่อ adoTemplate ใน MyMethod ที่ถูกแล้วเราควรใช้ property เพราะฟิลด์นี้อาจไม่ถูก set ค่าเวลา Spring สร้าง proxy ผลที่เกิดก็คือเราจะเจอ NullReferenceException เวลาถึงบรรทัด adoTemplate.Execute(…)

ข้อที่สองคือ เราไม่ได้ประกาศ AdoTemplate เป็น virtual ทำให้ Spring ไม่ได้ set ค่าของ AdoTemplate property เวลาสร้าง context

สรุปแล้ว โค้ดที่ถูกต้องคือ

[Service]
public class DataService
{
  private AdoTemplate adoTemplate;
  
  // Don't forget to make this virtual.
  public virtual AdoTemplate AdoTemplate
  {
    set { adoTemplate = value; }
  }

  [Transaction]
  public void MyMethod()
  {
    // Use property instead of field.
    AdoTemplate.Execute(...);
    AdoTemplate.Execute(...);
  }
}

ป้ายกำกับ:

Follow

Get every new post delivered to your Inbox.