Aracılığıyla paylaş


ADO.NET ile kullanıcı tanımlı türler oluşturma

Şunlar için geçerlidir: SQL Server

Kullanıcı tanımlı tür (UDT) tanımınızı kodlarken, UDT'yi sınıf olarak mı yoksa yapı olarak mı uyguladığınıza ve seçtiğiniz biçim ve serileştirme seçeneklerine bağlı olarak çeşitli özellikleri uygulamanız gerekir.

Bu bölümdeki örnekte, Point UDT'nin struct (veya Visual Basic'te Structure) olarak uygulanması gösterilmektedir. Point UDT, özellik yordamları olarak uygulanan X ve Y koordinatlarından oluşur.

UDT tanımlarken aşağıdaki ad alanları gereklidir:

  • C#
  • Visual Basic .NET
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

Microsoft.SqlServer.Server ad alanı, UDT'nizin çeşitli öznitelikleri için gereken nesneleri içerir ve System.Data.SqlTypes ad alanı, derlemede kullanılabilen SQL Server yerel veri türlerini temsil eden sınıfları içerir. Derlemenizin düzgün çalışması için gereken başka ad alanları da olabilir. Point UDT, dizelerle çalışmak için System.Text ad alanını da kullanır.

Not

/clr:pure ile derlenen UDT'ler gibi Visual C++ veritabanı nesneleri yürütme için desteklenmez.

Öznitelikleri belirtme

Öznitelikler, UDT'lerin depolama gösterimini oluşturmak ve UDT'leri değere göre istemciye iletmek için serileştirmenin nasıl kullanıldığını belirler.

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute gereklidir. Serializable özniteliği isteğe bağlıdır. Ayrıca, UDT'nin dönüş türü hakkında bilgi sağlamak için Microsoft.SqlServer.Server.SqlFacetAttribute belirtebilirsiniz. Daha fazla bilgi için bkz. CLR tümleştirmesi:CLR yordamları için özel öznitelikler.

Nokta UDT öznitelikleri

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute, Point UDT için depolama biçimini Nativeolarak ayarlar. IsByteOrdered trueolarak ayarlanır ve bu da sql Server'da karşılaştırma sonuçlarının yönetilen kodda aynı karşılaştırmanın gerçekleştiği gibi aynı olmasını garanti eder. UDT, UDT null değerinin farkında olmasını sağlamak için System.Data.SqlTypes.INullable arabirimini uygular.

Aşağıdaki kod parçası, Point UDT özniteliklerini gösterir.

  • C#
  • Visual Basic .NET
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,
  IsByteOrdered=true)]
public struct Point : INullable { ... }

Null atanabilirlik uygulama

Derlemeleriniz için öznitelikleri doğru belirtmeye ek olarak, UDT'nizin de null atanabilirliği desteklemesi gerekir. SQL Server'a yüklenen UDF'ler null algılamalıdır, ancak UDT'nin null değeri tanıması için UDT'nin System.Data.SqlTypes.INullable arabirimini uygulaması gerekir.

CLR kodu içinden bir değerin null olup olmadığını belirlemek için gereken IsNulladlı bir özellik oluşturmanız gerekir. SQL Server bir UDT'nin null örneğini bulduğunda, UDT normal null işleme yöntemleri kullanılarak kalıcı hale gelir. Sunucu, gerekirse UDT'yi seri hale getirmek veya seri durumdan çıkartmak için zaman kaybetmez ve null UDT depolamak için alan harcamaz. Bu null denetimi, CLR'den bir UDT getirildiğinde gerçekleştirilir ve bu da null UDT'leri denetlemek için Transact-SQL IS NULL yapısını kullanmanın her zaman çalışması gerektiği anlamına gelir. IsNull özelliği de sunucu tarafından bir örneğin null olup olmadığını test etmek için kullanılır. Sunucu UDT'nin null olduğunu belirledikten sonra yerel null işlemesini kullanabilir.

IsNull get() yöntemi herhangi bir şekilde özel durumla ilgili değildir. @p Point değişkeni Nullise @p.IsNull varsayılan olarak 1değil NULLolarak değerlendirilir. Bunun nedeni, IsNull get() yönteminin SqlMethod(OnNullCall) özniteliğinin varsayılan olarak false olmasıdır. nesnesi Nullolduğundan, özellik istendiğinde nesne seri durumdan çıkarılamaz, yöntem çağrılmaz ve varsayılan "NULL" değeri döndürülür.

Örnek

Aşağıdaki örnekte is_Null değişkeni özeldir ve UDT örneği için null durumunu tutar. Kodunuz is_Nulliçin uygun bir değer tutmalıdır. UDT ayrıca UDT'nin null değer örneğini döndüren Null adlı bir statik özelliğe de sahip olmalıdır. Bu, örneğin veritabanında gerçekten null olması durumunda UDT'nin null değer döndürmesine olanak tanır.

  • C#
  • Visual Basic .NET
private bool is_Null;

public bool IsNull
{
    get
    {
        return (is_Null);
    }
}

public static Point Null
{
    get
    {
        Point pt = new Point();
        pt.is_Null = true;
        return pt;
    }
}

IS NULL ile IsNull karşılaştırması

Point bir CLR UDT olduğu Points(id int, location Point)şemasını ve aşağıdaki sorguları içeren bir tablo düşünün:

  • Sorgu 1:

    SELECT ID FROM Points
    WHERE NOT (location IS NULL); -- Or, WHERE location IS NOT NULL;
    
  • Sorgu 2:

    SELECT ID FROM Points
    WHERE location.IsNull = 0;
    

Her iki sorgu da null olmayan konumlara sahip noktaların kimliklerini döndürür. Sorgu 1'de normal null işleme kullanılır ve UDF'lerin seri durumdan çıkarılması gerekmez. Öte yandan sorgu 2'nin null olmayan her nesneyi seri durumdan çıkarması ve IsNull özelliğinin değerini almak için CLR'ye çağırması gerekir. Açıkçası, IS NULL kullanmak daha iyi bir performans sergiler ve bir UDT'nin IsNull özelliğini Transact-SQL koddan okumak için hiçbir zaman bir neden olmamalıdır.

Peki, IsNull özelliğinin kullanımı nedir? İlk olarak, CLR kodu içinden bir değerin null olup olmadığını belirlemek için gereklidir. İkinci olarak, sunucunun bir örneğin null olup olmadığını test etmek için bir yönteme ihtiyacı vardır, bu nedenle bu özellik sunucu tarafından kullanılır. Null olarak belirlendikten sonra, bunu işlemek için yerel null işlemesini kullanabilir.

Ayrıştırma yöntemini uygulama

Parse ve ToString yöntemleri, UDT'nin dize gösterimlerine ve bu gösterimlerden dönüştürmelere olanak sağlar. Parse yöntemi bir dizenin UDT'ye dönüştürülmesini sağlar. static (veya Visual Basic'te Shared) olarak bildirilmeli ve System.Data.SqlTypes.SqlStringtüründe bir parametre almalıdır.

Aşağıdaki kod, X ve Y koordinatlarını ayıran Point UDT için Parse yöntemini uygular. Parse yöntemi, System.Data.SqlTypes.SqlStringtüründe tek bir bağımsız değişkene sahiptir ve X ve Y değerlerinin virgülle ayrılmış dize olarak sağlandığını varsayar. Microsoft.SqlServer.Server.SqlMethodAttribute.OnNullCall özniteliğini false olarak ayarlamak, Parse yönteminin null bir Point örneğinden çağrılmasını engeller.

  • C#
  • Visual Basic .NET
[SqlMethod(OnNullCall = false)]
public static Point Parse(SqlString s)
{
    if (s.IsNull)
        return Null;

    // Parse input string to separate out points.
    Point pt = new Point();
    string[] xy = s.Value.Split(",".ToCharArray());
    pt.X = Int32.Parse(xy[0]);
    pt.Y = Int32.Parse(xy[1]);
    return pt;
}

ToString yöntemini uygulama

ToString yöntemi, Point UDT'yi bir dize değerine dönüştürür. Bu durumda, Point türünün Null örneği için "NULL" dizesi döndürülür. ToString yöntemi, X ve Y koordinat değerlerinden oluşan virgülle ayrılmış bir System.String döndürmek için System.Text.StringBuilder kullanarak Parse yöntemini tersine çevirir. InvokeIfReceiverIsNull varsayılan değeri false olduğundan, Point null örneğini denetlemek gereksizdir.

  • C#
  • Visual Basic .NET
private Int32 _x;
private Int32 _y;

public override string ToString()
{
    if (this.IsNull)
        return "NULL";
    else
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(_x);
        builder.Append(",");
        builder.Append(_y);
        return builder.ToString();
    }
}

UDT özelliklerini kullanıma sunma

Point UDT, System.Int32türünde genel okuma-yazma özellikleri olarak uygulanan X ve Y koordinatlarını kullanıma sunar.

  • C#
  • Visual Basic .NET
public Int32 X
{
    get
    {
        return this._x;
    }
    set
    {
        _x = value;
    }
}

public Int32 Y
{
    get
    {
        return this._y;
    }
    set
    {
        _y = value;
    }
}

UDT değerlerini doğrulama

UDT verileriyle çalışırken, SQL Server Veritabanı Altyapısı ikili değerleri otomatik olarak UDT değerlerine dönüştürür. Bu dönüştürme işlemi, değerlerin türün serileştirme biçimine uygun olup olmadığını denetlemeyi ve değerin doğru seri durumdan çıkarılabilmesini sağlar. Bu, değerin ikili forma geri dönüştürülebilmesini sağlar. Bayt sıralı UDF'ler söz konusu olduğunda, sonuçta elde edilen ikili değerin özgün ikili değerle eşleşmesini de sağlar. Bu, geçersiz değerlerin veritabanında kalıcı olmasını engeller. Bazı durumlarda bu denetim düzeyi yetersiz olabilir. UDT değerlerinin beklenen bir etki alanında veya aralıkta olması gerektiğinde ek doğrulama gerekebilir. Örneğin, tarih uygulayan bir UDT, gün değerinin belirli bir geçerli değer aralığı içinde yer alan pozitif bir sayı olmasını gerektirebilir.

Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute.ValidationMethodName özelliği, veriler UDT'ye atandığında veya UDT'ye dönüştürüldüğünde sunucunun çalıştırdığı bir doğrulama yönteminin adını sağlamanıza olanak tanır. ValidationMethodName, bcp yardımcı programı, BULK INSERT, DBCC CHECKDB, DBCC CHECKFILEGROUP, DBCC CHECKTABLE, dağıtılmış sorgu ve tablosal veri akışı (TDS) uzaktan yordam çağrısı (RPC) işlemleri çalıştırılırken de çağrılır. ValidationMethodName için varsayılan değer null değeridir ve doğrulama yöntemi olmadığını belirtir.

Örnek

Aşağıdaki kod parçası, ValidatePointValidationMethodName belirten Point sınıfının bildirimini gösterir.

  • C#
  • Visual Basic .NET
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedType(Format.Native,
  IsByteOrdered=true,
  ValidationMethodName = "ValidatePoint")]
public struct Point : INullable { ... }

Bir doğrulama yöntemi belirtilirse, aşağıdaki kod parçasına benzeyen bir imzaya sahip olmalıdır.

  • C#
  • Visual Basic .NET
private bool ValidationFunction()
{
    if (validation logic here)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Doğrulama yöntemi herhangi bir kapsama sahip olabilir ve değer geçerliyse true döndürmelidir ve aksi takdirde false. Yöntem false döndürürse veya bir özel durum oluşturursa, değer geçerli değil olarak değerlendirilir ve bir hata oluşur.

Aşağıdaki örnekte kod yalnızca X ve Y koordinatlarının sıfır veya daha büyük değerlerine izin verir.

  • C#
  • Visual Basic .NET
private bool ValidatePoint()
{
    if ((_x >= 0) && (_y >= 0))
    {
        return true;
    }
    else
    {
        return false;
    }
}

Doğrulama yöntemi sınırlamaları

Sunucu, tek tek özellikler ayarlanarak veri eklendiğinde veya veriler bir Transact-SQL INSERT deyimi kullanılarak eklendiğinde değil, dönüştürme gerçekleştirirken doğrulama yöntemini çağırır.

Doğrulama yönteminin her durumda yürütülmesini istiyorsanız, özellik ayarlayıcılarından doğrulama yöntemini ve Parse yöntemini açıkça çağırmanız gerekir. Bu bir gereksinim değildir ve bazı durumlarda istenmeyebilir.

Doğrulamayı ayrıştırma örneği

ValidatePoint yönteminin Point sınıfında çağrıldığından emin olmak için, Parse yönteminden ve X ve Y koordinat değerlerini ayarlayan özellik yordamlarından çağırmalısınız. Aşağıdaki kod parçası, Parse işlevinden ValidatePoint doğrulama yöntemini çağırmayı gösterir.

  • C#
  • Visual Basic .NET
[SqlMethod(OnNullCall = false)]
public static Point Parse(SqlString s)
{
    if (s.IsNull)
        return Null;

    // Parse input string to separate out points.
    Point pt = new Point();
    string[] xy = s.Value.Split(",".ToCharArray());
    pt.X = Int32.Parse(xy[0]);
    pt.Y = Int32.Parse(xy[1]);

    // Call ValidatePoint to enforce validation
    // for string conversions.
    if (!pt.ValidatePoint())
        throw new ArgumentException("Invalid XY coordinate values.");
    return pt;
}

Özellik doğrulama örneği

Aşağıdaki kod parçası, X ve Y koordinatlarını ayarlayan özellik yordamlarından ValidatePoint doğrulama yönteminin nasıl çağrıldığını gösterir.

  • C#
  • Visual Basic .NET
public Int32 X
{
    get
    {
        return this._x;
    }
    // Call ValidatePoint to ensure valid range of Point values.
    set
    {
        Int32 temp = _x;
        _x = value;
        if (!ValidatePoint())
        {
            _x = temp;
            throw new ArgumentException("Invalid X coordinate value.");
        }
    }
}

public Int32 Y
{
    get
    {
        return this._y;
    }
    set
    {
        Int32 temp = _y;
        _y = value;
        if (!ValidatePoint())
        {
            _y = temp;
            throw new ArgumentException("Invalid Y coordinate value.");
        }
    }
}

Kod UDT yöntemleri

UDT yöntemlerini kodlarken, kullanılan algoritmanın zaman içinde değişip değişemeyeceğini göz önünde bulundurun. Öyleyse, UDT'nizin kullandığı yöntemler için ayrı bir sınıf oluşturmayı düşünebilirsiniz. Algoritma değişirse sınıfı yeni kodla yeniden derleyebilir ve UDT'yi etkilemeden derlemeyi SQL Server'a yükleyebilirsiniz. Çoğu durumda UDF'ler Transact-SQL ALTER ASSEMBLY deyimi kullanılarak yeniden yüklenebilir, ancak bu durum mevcut verilerle ilgili sorunlara neden olabilir. Örneğin, AdventureWorks2022 örnek veritabanına dahil edilen Currency UDT, ayrı bir sınıfta uygulanan para birimi değerlerini dönüştürmek için bir ConvertCurrency işlevi kullanır. Dönüştürme algoritmaları gelecekte öngörülemeyen şekillerde değişebilir veya yeni işlevler gerekebilir. ConvertCurrency işlevini Currency UDT uygulamasından ayırmak, gelecekteki değişiklikleri planlarken daha fazla esneklik sağlar.

Örnek

Point sınıfı, uzaklığı hesaplamak için üç basit yöntem içerir: Distance, DistanceFromve DistanceFromXY. Her biri, Point'den sıfıra olan uzaklığı, belirtilen noktadan Pointuzaklığı ve belirtilen X ve Y koordinatlarından Pointuzaklığı hesaplayarak bir double döndürür. DistanceFromXYher çağrıyı Distance ve DistanceFrom ve her yöntem için farklı bağımsız değişkenlerin nasıl kullanılacağını gösterin.

  • C#
  • Visual Basic .NET
// Distance from 0 to Point.
[SqlMethod(OnNullCall = false)]
public Double Distance()
{
    return DistanceFromXY(0, 0);
}

// Distance from Point to the specified point.
[SqlMethod(OnNullCall = false)]
public Double DistanceFrom(Point pFrom)
{
    return DistanceFromXY(pFrom.X, pFrom.Y);
}

// Distance from Point to the specified x and y values.
[SqlMethod(OnNullCall = false)]
public Double DistanceFromXY(Int32 iX, Int32 iY)
{
    return Math.Sqrt(Math.Pow(iX - _x, 2.0) + Math.Pow(iY - _y, 2.0));
}

SqlMethod özniteliklerini kullanma

Microsoft.SqlServer.Server.SqlMethodAttribute sınıfı, determinizmi belirtmek, null çağrı davranışında ve yöntemin bir mutator olup olmadığını belirtmek için yöntem tanımlarını işaretlemek için kullanılabilecek özel öznitelikler sağlar. Bu özellikler için varsayılan değerler varsayılır ve özel öznitelik yalnızca varsayılan olmayan bir değer gerektiğinde kullanılır.

Not

SqlMethodAttribute sınıfı SqlFunctionAttribute sınıfından devralır, bu nedenle SqlMethodAttributeFillRowMethodName ve TableDefinition alanlarını SqlFunctionAttribute'den devralır. Bu, tablo değerli bir yöntem yazılabileceği anlamına gelir ve bu durum böyle değildir. Yöntemi derler ve derleme dağıtılır, ancak çalışma zamanında şu iletiyle IEnumerable dönüş türüyle ilgili bir hata oluşur: "Derleme <assembly> sınıf <class> yöntem, özellik veya alan <name> geçersiz dönüş türüne sahip."

Aşağıdaki tabloda, UDT yöntemlerinde kullanılabilecek bazı ilgili Microsoft.SqlServer.Server.SqlMethodAttribute özellikleri açıklanır ve varsayılan değerleri listelenir.

Mülk Açıklama
DataAccess İşlevin SQL Server'ın yerel örneğinde depolanan kullanıcı verilerine erişim içerip içermediğini gösterir. Varsayılan değer DataAccessKind.None.
IsDeterministic İşlevin aynı giriş değerleri ve aynı veritabanı durumu verilen aynı çıkış değerlerini oluşturup oluşturmadığını gösterir. Varsayılan değer false.
IsMutator Yönteminin UDT örneğinde durum değişikliğine neden olup olmadığını gösterir. Varsayılan değer false.
IsPrecise İşlevin kayan nokta işlemleri gibi kesin olmayan hesaplamalar içerip içermediğini gösterir. Varsayılan değer false.
OnNullCall Null başvuru giriş bağımsız değişkenleri belirtildiğinde yöntemin çağrılıp çağrılmayacağını gösterir. Varsayılan değer true.

Örnek

Microsoft.SqlServer.Server.SqlMethodAttribute.IsMutator özelliği, UDT örneğinin durumunda değişikliğe izin veren bir yöntemi işaretlemenize olanak tanır. Transact-SQL, bir UPDATE deyiminin SET yan tümcesinde iki UDT özelliği ayarlamanıza izin vermez. Ancak, iki üyeyi değiştiren bir mutator olarak işaretlenmiş bir yönteminiz olabilir.

Not

Sorgularda mutator yöntemlerine izin verilmez. Bunlar yalnızca atama deyimlerinde veya veri değişikliği deyimlerinde çağrılabilir. Mutator olarak işaretlenmiş bir yöntem void döndürmezse (veya Visual Basic'te Sub değilse) CREATE TYPE hatayla başarısız olur.

Aşağıdaki deyim, Rotate yöntemine sahip bir Triangles UDT'nin varlığını varsayar. Aşağıdaki Transact-SQL update deyimi Rotate yöntemini çağırır:

UPDATE Triangles
SET t.RotateY(0.6)
WHERE id = 5;

Rotate yöntemi, SQL Server'ın yöntemi bir mutator yöntemi olarak işaretlemesi için trueIsMutatorSqlMethod öznitelik ayarıyla donatılmıştır. Kod ayrıca OnNullCallfalseolarak ayarlar ve giriş parametrelerinden herhangi biri null başvuru ise yöntemin null başvuru (Visual Basic'teNothing) döndürdüğünü sunucuya gösterir.

  • C#
  • Visual Basic .NET
[SqlMethod(IsMutator = true, OnNullCall = false)]
public void Rotate(double anglex, double angley, double anglez)
{
   RotateX(anglex);
   RotateY(angley);
   RotateZ(anglez);
}

Kullanıcı tanımlı biçime sahip bir UDT uygulama

Kullanıcı tanımlı biçime sahip bir UDT uygularken, UDT verilerini seri hale getirme ve seri durumdan çıkarma işlemlerini gerçekleştirmek için Microsoft.SqlServer.Server.IBinarySerialize arabirimini uygulayan Read ve Write yöntemleri uygulamanız gerekir. ayrıca Microsoft.SqlServer.Server.SqlUserDefinedTypeAttributeMaxByteSize özelliğini belirtmelisiniz.

Para birimi UDT

Currency UDT, SQL Server ile yüklenebilen CLR örneklerine dahildir.

Currency UDT, belirli bir kültürün parasal sistemindeki para miktarının işlenmesini destekler. İki alan tanımlamanız gerekir: CultureInfoiçin bir string , para birimini kimin dağıtıldığını belirten bir string (örneğin,en-us) ve CurrencyValueiçin bir decimal , para miktarı.

Karşılaştırmaları gerçekleştirmek için sunucu tarafından kullanılmasa da, Currency UDT tek bir yöntemi System.IComparable.CompareTokullanıma sunan System.IComparable arabirimini uygular. Bu, kültürler içindeki para birimi değerlerinin doğru bir şekilde karşılaştırılması veya sıralanması istenen durumlarda istemci tarafında kullanılır.

CLR'de çalışan kod, kültürü para birimi değerinden ayrı olarak karşılaştırır. Transact-SQL kodu için aşağıdaki eylemler karşılaştırmayı belirler:

  1. SQL Server'a karşılaştırmalar için diskte kalıcı ikili gösterimi kullanmasını söyleyen IsByteOrdered özniteliğini true olarak ayarlayın.

  2. UDT'nin diskte nasıl kalıcı olduğunu ve dolayısıyla UDT değerlerinin Transact-SQL işlemleri için nasıl karşılaştırılıp sıralı olduğunu belirlemek için Currency UDT için Write yöntemini kullanın.

  3. aşağıdaki ikili biçimi kullanarak Currency UDT'yi kaydedin:

    1. Kültürü, sağ tarafta null karakterlerle doldurma ile 0-19 baytları için UTF-16 ile kodlanmış bir dize olarak kaydedin.

    2. Para biriminin ondalık değerini içermek için 20 ve üzeri baytları kullanın.

Doldurmanın amacı, kültürün para birimi değerinden tamamen ayrıldığından emin olmaktır, böylece bir UDT Transact-SQL kodunda başka bir UDT ile karşılaştırıldığında kültür baytları kültür baytlarıyla karşılaştırılır ve para birimi bayt değerleri para birimi bayt değerleriyle karşılaştırılır.

Para birimi öznitelikleri

Currency UDT aşağıdaki özniteliklerle tanımlanır.

  • C#
  • Visual Basic .NET
[Serializable]
[SqlUserDefinedType(Format.UserDefined,
    IsByteOrdered = true, MaxByteSize = 32)]
    [CLSCompliant(false)]
public struct Currency : INullable, IComparable, IBinarySerialize
{ ... }

ibinaryserialize ile okuma ve yazma yöntemleri oluşturma

UserDefined serileştirme biçimini seçtiğinizde, IBinarySerialize arabirimini de uygulamanız ve kendi Read ve Write yöntemlerinizi oluşturmanız gerekir. Currency UDT'den aşağıdaki yordamlar, UDT'den okumak ve UDT'ye yazmak için System.IO.BinaryReader ve System.IO.BinaryWriter kullanır.

  • C#
  • Visual Basic .NET
// IBinarySerialize methods
// The binary layout is as follow:
//    Bytes 0 - 19:Culture name, padded to the right
//    with null characters, UTF-16 encoded
//    Bytes 20+:Decimal value of money
// If the culture name is empty, the currency is null.
public void Write(System.IO.BinaryWriter w)
{
    if (this.IsNull)
    {
        w.Write(nullMarker);
        w.Write((decimal)0);
        return;
    }

    if (cultureName.Length > cultureNameMaxSize)
    {
        throw new ApplicationException(string.Format(
            CultureInfo.InvariantCulture,
            "{0} is an invalid culture name for currency as it is too long.",
            cultureNameMaxSize));
    }

    String paddedName = cultureName.PadRight(cultureNameMaxSize, '\0');
    for (int i = 0; i < cultureNameMaxSize; i++)
    {
        w.Write(paddedName[i]);
    }

    // Normalize decimal value to two places
    currencyValue = Decimal.Floor(currencyValue * 100) / 100;
    w.Write(currencyValue);
}
public void Read(System.IO.BinaryReader r)
{
    char[] name = r.ReadChars(cultureNameMaxSize);
    int stringEnd = Array.IndexOf(name, '\0');

    if (stringEnd == 0)
    {
        cultureName = null;
        return;
    }

    cultureName = new String(name, 0, stringEnd);
    currencyValue = r.ReadDecimal();
}