Змінні Nullable та як обробляти DBNull з бази за допомогою Extension-функції.
Visual Basic має величезну кількість типів даних (ось тут є повна документація по VB), але якщо підсумувати все дуже коротко, то є елементарні типи даних (Boolean, Byte, Char, Date, Decimal, Double, Integer, Long, SByte, Short, Single, UInteger, ULong, UShort), е такі самі прості, але з деякими особливостями - Constant, Literal, Enum, String, а є комплексні, що складаються з простих типів, наприклад Array (декількох різних типів), Structure, Object, Collection, XML. Самому можно побудувати не тільки складні типи даних, але й елементарні, наслідувані від ValueType, як це зроблено у NET 4.7 де побудований новий тип даних Tuples. Це лише крихітка всього, що існує в Бейсіці щодо навіть елементарних типів даних, наприклад окремі класи експортують власні типи елементарних даних, тобто наприклад існують типи SqlTypes як SqlFileStream, SqlXml, SqlMoney, SqlBinary, SqlGuid та багато інших.
Складні типи даних утворюються у бейсіці вкрай запаморочено. Наприклад існує цілий всесвіт Дженеріків та Lambda Expression, де є методи, які вміють працювати з будь-якими класами, є інтерфейси, контрейнси між ними, взагалі одні складні типи даних (такі як класи та інтерфейси) будуються на базі інших. Ось тут, наприклад, це у 2002-му році, коли замість VB6 тільки-тільки з'явився VB.NET, я намагався розібратися, як наслідуються одни класи від інших (картинка знизу сторінки) - але тоді таких речей як Generic, Lambda Expression та Extension function взагалі ще не існувало. Окремі типи (різновидності класів) обробляються вкрай специфічно, наприклад такі як Attributes, Delegate, Event, Exception, Nothing, AddressOf (а деякі особливі сутності такі як Namespace, Module взагалі не є обьектами-типами, хоча нагадують класи по синтаксісу). специфічні типи даних мають складні залежності між собою, наприклад будь-які Event's дуже просто замінити на Delegate, але наприклад щоб замінити AddressOF на Lambda Expression, треба спочатку добряче поламати голову. Існує також окреме питанні, яким чином можливо працювати у декількох потоках без зруйнування.
Деякі типи даних Visual Basic відповідають загальним типам даних .NET Framework (і мають еквівалент у C#), а деякі конструкції, такі як Module, або специфічні засоби наслідування (такі ік Shadow) або типи даних специфічні саме для Бейсіка (наприклад Nullable (of UInit32)) - неможливо запросто відтворити на інших мовах .NET (ось тут у мене зберігається повний опис специфіки Бейсіка щодо більш простих мов, таких як C# - VB-Csharp-Difference).
Все це непогано було б звести в одну багатомірну табличку, де по першій осі був би відкладений тип даних з повного набору, що взагалі існують у бейсіці, а по кожної інший осі була б одна характеристика типів даних - наприклад чи ці дані є елементарними, чи побудовані як комбінація елементарних, чи ці дані зберігаються по лінку (тобто змінюючи дані в одному місці, вона змінюється усюди), скільки пам'яті займає той чи інший тип даних, як можливо з'ясувати чи той чи інший тип даних конкретне значення, чи значення присвоюється при завантаженні програми з диску у пам'ять, чи пізніше, коли утворюється екземпляр класу, як можно конвертувати одні дані у інші, як з'ясувати тип цих даних, яким чином можна порівнювати дані у межах того ж самого типу даних, та у з іншими типами даних. От тільки як цю табличку спроектувати на двомірний папір чи екран?
Таким чином ми розуміємо, що дані, з якими вміють працювати програми на Бейсіці - це цілий всесвіт, який неможливо запросто навіть огорнути зором. Тому пишутся лише окремі нотатки з якогось крихітного питання. У цьому топіку я опишу малесеньку, але дуже важливу крапочку цього всесвіту - а саме, як працювати з даними наприклад типа Nullable (of integer) та як їх пов'язати з Nothing та DbNull, які повертаються з SQL-бази.
По-перше, якщо ми бажаємо прийняти з SQL такі дані у свою програму, то є найпростіший шаблон, як це можливо зробити з самими звичайними змінними взагалі без будь-яких складнощів.
Але у цьому засобі є велике питання. Наприклад неініціалізована ціла змінна у пам'яті має значення НОЛЬ, якщо в базі NULL, а як ви розумієте, 'NULL' і '0' у SQL-базі - це далеко не теж саме. Щоб почути і обробити у програмі різницю між 'NULL' та '0' ми повинні десь зробити якусь об'яву з Nullable (Of xxx). Така змінна дозволить надати їй значення Nothing або 0, і обробити в програмі це по різному.
Якщо ми використовуємо Linq-to-SQL, то таку об'яву зробить цей фреймворк самостійно, але, наприклад, якщо ми використовуємо більш старі засоби роботи з базою, наприклад DataSet/DataAdapter, то нам доведеться зробити таку об'яву у явному вигляді.
Але нажаль Nothing теж не одне й те ж саме, що NULL у базі (це залежить від фреймворка, за допомогою якого ви звертаєтесь до бази), до того ж писати ось так у кожній стрічці дуже незручно.
Тому хочеться щось зробити таке, що автоматично б обробляло NULL у базі, та у змінній залишався би Nothing якщо у цьому полі бази є NULL.
І тут раптово ми згадаємо про Extension-фунції, які дозволяють нам своїм кодом поширювати будь який класс існуючого фреймворку, головна вимога до поширюваного класу лише одна - поширювати можна лише статичну секцію класу, тобто додавати SHARED-методи, які працюють без екземплярів класу, а лише з тим статичним шаблоном класу, що спочатку роботи програми завантажується з диска.
Фраза дуже важка для порозуміння, але подивиться на скрін нище, оцей метод GetNullable я зробив сам і додав його до існуючого класу Data.DataRow.
Щоб порозумітися у цьому, і зробити таку Extension-функцію, потрібно твердо порозуміти такі речі.
- Data.DataRow - це статичний клас, який завжди існує навіть без утворення eкземпляра, тому він може бути поширений за допомогою Extension-функції
- Саме по собі поширення у Бейсіці робиться з об'явою Module, це теж статичний класс, який завантажується з диску відразу з готовим eкземпляром (тобто непотрібно робити NEW для утворення всього того, що ви напишете всередині Module). Сам по собі Module нагадую звичайний класс,(1) до якого ви вже автоматично застосований New, (2) всі об'яви в ньому зроблені квалифікатором Shared.
- Якщо до Module додати дві речі:
- атрибут <Runtime.CompilerServices.Extension()>
- Та перший параметр - ім'я статичного класу, що ви бажаєте поширити
Таким чином, нище ви можете побачити найпростішу з можливих, але дуже корисну Extension-функцію.
1: Imports Microsoft.VisualBasic
2:
3: Public Module Extension6
4:
5: <Runtime.CompilerServices.Extension()> _
6: Public Function GetNullable(Of T)(DataRow As Data.DataRow, FieldName As Object) As T
7: If Convert.IsDBNull(DataRow(FieldName)) Then
8: Return Nothing
9: Else
10: Return CType(DataRow(FieldName), T)
11: End If
12: End Function
13:
14: End Module
Це найпростіша з можливих Extension-функцій, згодом я став їх робити все більше й більше.
- How to reorder DataRow with Extension function, anonymous types, Lambda Expression and Linq Special Row Comparer (eng). - поширення того ж класу, що описаний на цієї сторінці, але набагато більш складна і корисна функція.
- Unit-тести для ASP.NET MVC - ось тут у розділі 5 описана дуже корисна функція для ASP.NET MVC - GetModel
- Amazing extension function CopyLinqDataMembersByName to expand Linq-to-SQL - не дуже складна, але дуже корисна для ASP.NET функція поширення Linq-to-SQL
- Мої поширення Linq-to-SQL - ось тут описані досить складні поширення Linq-to-SQL з Lambda Expression (але не настільки корисні, як дві попередні функціях перед нею вище).
- Extension function (ms doc)
Цікаво, що не існує можливості зробити Extension-функцію до простих типів даних, тобто мені завжди є бажання додати до цього набора якийсь свої функції, але нажаль це повністю неможливо.
Зробити можливо лише ось так, тобто спочатку потрібно мати екземпляр класу String, а його вже можливо поширювати своїми власними функціями.
У даному випадку це ось така моя функція.
1: Imports Microsoft.VisualBasic
2:
3: Public Module Extension5
4: <Runtime.CompilerServices.Extension()> _
5: Public Function GetRandomString(X As String, Len As Integer) As String
6: Dim RND As Random = New Random()
7: Const ARR1 As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
8: Return New String(Enumerable.Repeat(ARR1, Len).Select(Function(s) s(RND.Next(s.Length))).ToArray())
9: End Function
10: End Module
<SITEMAP> <MVC> <ASP> <NET> <DATA> <KIOSK> <FLEX> <SQL> <NOTES> <LINUX> <MONO> <FREEWARE> <DOCS> <ENG> <CHAT ME> <ABOUT ME> < THANKS ME> |