2017-12-26

Blogger: Sort Breadcrumbs by Label Frequency

日本語»

In my previous post, “Breadcrumb with Structured Data for Blogger”, I mentioned that breadcrumbs are listed in alphabetical order, not showing the actual hierarchy.

If the labels are in a hierarchical (tree) structure, a label at the upper level is used more often than (or equal to) the labels at lower levels. So sorting labels by their frequency order, you can show the breadcrumbs in hierarchical order approximately (except when labels have the same frequency).

In this post, I change the breadcrumbs’ order from alphabetical to labels frequency by JavaScript.

Prerequisites

  • Show the Labels gadget and check “Show number of posts per label” in “Configure Labels”.
  • The label gadget id must be Label1 (normally Label1).
  • The breadcrumb list id must be breadcrumb.
  • The breadcrumb list uses tags <li> and its structured data format is RDFa.

FYI

These prerequisites are based on the following posts.

JavaScript

Insert the following JavaScript just before </body> in Edit HTML.

JavaScript
<script type='text/javascript'>
//<![CDATA[
(function(){
  var label = document.getElementById('Label1');
  if (label == null) return;
  var breadcrumb = document.getElementById('breadcrumb');
  if (breadcrumb == null) return;
  // store label name and count into object
  var labelCounts = {};
  // 'div ul li' is for List, 'div>span' is for Cloud 
  var elms = label.querySelectorAll('div ul li, div>span');
  [].forEach.call(elms, function(elm) {
    if (elm.childElementCount == 2) {
      labelCounts[elm.children[0].innerHTML] = Number(elm.children[1].innerHTML.replace(/[()]/g, ''));
    }
  });
  // sort breadcrumbs by label frequency
  var i = 0;
  [].slice.call(breadcrumb.querySelectorAll('li'))
    .sort(function (a, b) {
      var a = a.textContent.replace(/\r?\n/g, '');
      var b = b.textContent.replace(/\r?\n/g, '');
      if (labelCounts[a] == null || labelCounts[b] == null) return 0;
      if (labelCounts[a] > labelCounts[b]) return -1;
      if (labelCounts[a] < labelCounts[b]) return 1;
    }).forEach(function(li) {
      // set content attribute for RDFa
      pos = li.querySelector('[property="position"]');
      if (pos != null) {
        pos.setAttribute('content', ++i);
      }
      breadcrumb.appendChild(li);
    });
})();
//]]>
</script>

Then, the breadcrumb list is displayed in label frequency order.

Note:

When the labels have the same frequency, the labels are sorted in alphabetical order (not changed).

Blogger ラベル頻度順パンくずリスト

English»

前回の記事「Blogger パンくずリスト(構造化データ対応)の作成」でBlogger のパンくずリストで複数のラベルを表示する場合、アルファベット順になることを書きました。

ラベルがツリー構造になっている場合、上位の階層にあるラベルほど使用頻度が高くなりますので、パンくずリストをラベル頻度順に並べることで「ほぼ」階層順に表示することができます。

アルファベット順のパンくずリストを JavaScript でラベル頻度順に並べ替えてみます。

前提

  • ラベルガジェットを表示し、「ラベル毎の投稿数」を表示していること。
  • ラベルガジェットの ID が Label1 であること
  • パンくずリストの ID が breadcrumb であること
  • パンくずリストが <li> タグを使用し、構造化データの形式が RDFa であること

参考

を参照してください。

JavaScript

次の JavaScript を HTML の編集</body> の前に挿入してください。

JavaScript
<script type='text/javascript'>
//<![CDATA[
(function(){
  var label = document.getElementById('Label1');
  if (label == null) return;
  var breadcrumb = document.getElementById('breadcrumb');
  if (breadcrumb == null) return;
  // store label name and count into object
  var labelCounts = {};
  // 'div ul li' is for List, 'div>span' is for Cloud 
  var elms = label.querySelectorAll('div ul li, div>span');
  [].forEach.call(elms, function(elm) {
    if (elm.childElementCount == 2) {
      labelCounts[elm.children[0].innerHTML] = Number(elm.children[1].innerHTML.replace(/[()]/g, ''));
    }
  });
  // sort breadcrumbs by label frequency
  var i = 0;
  [].slice.call(breadcrumb.querySelectorAll('li'))
    .sort(function (a, b) {
      var a = a.textContent.replace(/\r?\n/g, '');
      var b = b.textContent.replace(/\r?\n/g, '');
      if (labelCounts[a] == null || labelCounts[b] == null) return 0;
      if (labelCounts[a] > labelCounts[b]) return -1;
      if (labelCounts[a] < labelCounts[b]) return 1;
    }).forEach(function(li) {
      // set content attribute for RDFa
      pos = li.querySelector('[property="position"]');
      if (pos != null) {
        pos.setAttribute('content', ++i);
      }
      breadcrumb.appendChild(li);
    });
})();
//]]>
</script>

パンくずリストがラベル頻度順にソートされて表示されます。

ラベルの頻度が同じ場合は、アルファベット順(元のまま)となります。

2017-12-20

Breadcrumbs with Structured Data for Blogger

日本語»

A lot of blogs refer to

Breadcrumb for Blogger - Blogger Widgets

And the original idea is based on

Hoctro’s Archived Blog - Trang Lưu Trữ Học Trò: Archived Post: Adding a Breadcrumb Trail to your Blogger Post

Thank you, Hoktro-san, Aneesh-san. It’s still useful but has the following issues at present.

  • The structured data type definition is based on the obsolete Data-Vocabulary.org, not schema.org.
  • Not using ordered list (<ol> <li>), commonly used for breadcrumbs.

I try to modify these issues.

FYI

1. Add <b:includable>

The modified code snippet of <b:includable> is as follows.
It uses RDFa as a structured data format.

<b:includable id='breadcrumb' var='posts'>
<!-- breadcrumb start -->
<ol id='breadcrumb' typeof='BreadcrumbList' vocab='http://schema.org/'>
<b:if cond='data:blog.homepageUrl != data:blog.url'>
  <li property='itemListElement' typeof='ListItem'>
    <a expr:href='data:blog.homepageUrl' property='item' typeof='WebPage'>
    <span property='name'>Home</span></a>
    <meta content='1' property='position'/>
  </li>
  <b:if cond='data:blog.pageType == &quot;static_page&quot;'>
    <li><data:blog.pageName/></li>
  <b:else/>
    <b:if cond='data:blog.pageType == &quot;item&quot;'>
      <!-- breadcrumb for the post page -->
      <b:loop values='data:posts' var='post'>
        <b:if cond='data:post.labels'>
          <b:loop index='index' values='data:post.labels' var='label'>
          <!-- b:if cond='data:label.isLast == &quot;true&quot;' -->
            <li property='itemListElement' typeof='ListItem'>
              <a expr:href='data:label.url' property='item' typeof='WebPage'>
              <span property='name'><data:label.name/></span></a>
              <meta expr:content='data:index + 2' property='position'/>
            </li>
          <!-- /b:if -->
          </b:loop>
          <li><data:post.title/></li>
        <b:else/>
          <li>Unlabelled</li>
        </b:if>
      </b:loop>
    <b:else/>
      <b:if cond='data:blog.pageType == &quot;archive&quot;'>
        <!-- breadcrumb for the label archive page and search pages.. -->
        <li>Archives for <data:blog.pageName/></li>
      <b:else/>
        <b:if cond='data:blog.pageType == &quot;index&quot;'>
          <b:if cond='data:blog.pageName == &quot;&quot;'>
            <li>All posts</li>
          <b:else/>
            <li>Posts filed under <data:blog.pageName/></li>
          </b:if>
        </b:if>
      </b:if>
    </b:if>
  </b:if>
</b:if>
</ol>
<!-- breadcrumb end -->
</b:includable>

Insert this snippet before <b:includable id='main' var='top'> in Edit HTML.

* I recommend the backup before editing.

Note:

After saving it, the formatter of Edit HTML moves the snippet, as snippets are in an alphabetical order of id, and indent the first line of the snippet.

  • If you comment out <b:if cond='data:blog.homepageUrl != data:blog.url'> and end-tag </b:div>, you can display the breadcrumb trail at all times including top page.
  • If you comment in <!-- b:if cond='data:label.isLast == &quot;true&quot;' --> and <!-- /b:if -->, the breadcrumbs include only the last label (in alphabetical order).
    (When you use more than one label, labels are sorted forcibly in alphabetical order.)

Usage of Search Function in Edit HTML

In Edit HTML, press Ctrl + F keys, then search box is displayed in upper right corner.
Input the search string and press Enter key. Each time press Enter key, the cursor jumps to the next match.

2. Add <b:include>

Next, insert the following widget tag for display before the part of <b:include data='top' name='status-message'/>.

<b:include data='posts' name='breadcrumb'/> 
  • If you put it just before </div> corresponding to <div class='blog-posts hfeed'>, the breadcrumb list is placed at the bottom of the page.
  • If you put it before <b:if cond='!data:mobile'>, the breadcrumb list can be shown in mobile pages as well.

3. Add CSS

Finally, add CSS as follows. Please modify as needed.

/* breadcrumb */
ol#breadcrumb{
    font-size:13px;
    padding: 8px;
}
#breadcrumb li {
    display: inline;
    list-style-type: none;
}
#breadcrumb li:after {
    content: " > ";
}
#breadcrumb li:last-child:after {
    content: none;
}

It’s all done.

Structured Data Testing Tool

You can check whether the breadcrumb list is working properly with Google Structured Data Testing Tool.

You must input URLs showing breadcrumbs.

You may find some errors in BlogPosting (not breadCrumbList).
You don’t have to care about it unless you want to reflect these error items correctly to search results. See Structured Data testing Tool errors on Blogpost Blogger - Google Product Forum.

If you want to fix them, this site may help you.

Remaining issue

Breadcrumbs are listed in alphabetical order, not showing the actual hierarchy.
To be continued in Blogger: Sort Breadcrumbs by Label Frequency.

Blogger パンくずリスト(構造化データ対応)の作成

English»

多くのブログで参照されている元記事はこちらです。

Breadcrumb for Blogger - Blogger Widgets

さらに、その元記事はこちらです。

Hoctro’s Archived Blog - Trang Lưu Trữ Học Trò: Archived Post: Adding a Breadcrumb Trail to your Blogger Post

Hoctro さん、Aneesh さん、ありがとうございます。
記事の内容は今でも使えるものなのですが、少々古く、以下の問題があります。

  • 構造化データの定義が schema.org でなく、Data-Vocabulary.org になっている。
  • 一般的にパンくずリストで使われるタグ <ol><li> を使っていない。

これらの問題を修正してみます。

参考記事

1. <b:includable> の追加

上の問題を修正した <b:includable> は以下のようになります。
なお、構造化データの形式は RDFa を採用しています。

<b:includable id='breadcrumb' var='posts'>
<!-- breadcrumb start -->
<ol id='breadcrumb' typeof='BreadcrumbList' vocab='http://schema.org/'>
<b:if cond='data:blog.homepageUrl != data:blog.url'>
  <li property='itemListElement' typeof='ListItem'>
    <a expr:href='data:blog.homepageUrl' property='item' typeof='WebPage'>
    <span property='name'>Home</span></a>
    <meta content='1' property='position'/>
  </li>
  <b:if cond='data:blog.pageType == &quot;static_page&quot;'>
    <li><data:blog.pageName/></li>
  <b:else/>
    <b:if cond='data:blog.pageType == &quot;item&quot;'>
      <!-- breadcrumb for the post page -->
      <b:loop values='data:posts' var='post'>
        <b:if cond='data:post.labels'>
          <b:loop index='index' values='data:post.labels' var='label'>
          <!-- b:if cond='data:label.isLast == &quot;true&quot;' -->
            <li property='itemListElement' typeof='ListItem'>
              <a expr:href='data:label.url' property='item' typeof='WebPage'>
              <span property='name'><data:label.name/></span></a>
              <meta expr:content='data:index + 2' property='position'/>
            </li>
          <!-- /b:if -->
          </b:loop>
          <li><data:post.title/></li>
        <b:else/>
          <li>Unlabelled</li>
        </b:if>
      </b:loop>
    <b:else/>
      <b:if cond='data:blog.pageType == &quot;archive&quot;'>
        <!-- breadcrumb for the label archive page and search pages.. -->
        <li>Archives for <data:blog.pageName/></li>
      <b:else/>
        <b:if cond='data:blog.pageType == &quot;index&quot;'>
          <b:if cond='data:blog.pageName == &quot;&quot;'>
            <li>All posts</li>
          <b:else/>
            <li>Posts filed under <data:blog.pageName/></li>
          </b:if>
        </b:if>
      </b:if>
    </b:if>
  </b:if>
</b:if>
</ol>
<!-- breadcrumb end -->
</b:includable>

これを 、HTML の編集 画面で <b:includable id='main' var='top'> の前に挿入します。

※念のため、編集前にバックアップを取っておくことをおすすめします。

注:

保存すると場所が変わります。<b:includable> の定義場所に id がアルファベット順になるようにソートされます。また、先頭行のみインデントされます。

  • <b:if cond='data:blog.homepageUrl != data:blog.url'> と 対応する をコメントアウトすればトップページに表示することができます。
  • <!-- b:if cond='data:label.isLast == &quot;true&quot;' --> <!-- /b:if --> の箇所をコメントインすれば、(アルファベット順で)最後のラベルのみをパンくずリストに表示できます。
    (複数ラベルの場合もアルファベット順・あいうえお順となります。)

Blogger の HTML編集画面での検索機能の使い方

編集画面で Ctrl + F キーを押すと、右肩に Search ボックスが現れます。検索文字を入力後、Enter キーを押すたびに見つかった文字にカーソルが移動します。

2. <b:include> の追加

次に、表示用のウィジェットタグ

<b:include data='posts' name='breadcrumb'/> 

<b:include data='top' name='status-message'/> の前に挿入します。

  • <div class='blog-posts hfeed'> が閉じる </div> の前に置くとページ下部に表示できます。
  • <b:if cond='!data:mobile'> の前に置けば、モバイル版でも表示できます。

3. CSS の追加

最後に、以下のような CSS を追加します。(適宜カスタマイズしてください。)

/* breadcrumb */
ol#breadcrumb{
    font-size:13px;
    padding: 8px;
}
#breadcrumb li {
    display: inline;
    list-style-type: none;
}
#breadcrumb li:after {
    content: " > ";
}
#breadcrumb li:last-child:after {
    content: none;
}

以上で完成です。

構造化データテストツールについて

Google の構造化データテストツール でエラーがないか確認できます。

構造化データテストツールの URL には、パンくずリストが表示される記事ページを指定する必要があります。

構造化データテストを実施すると、BlogPosting に大量のエラーが見つかるかもしれません。 Structured Data testing Tool errors on Blogpost Blogger - Google プロダクト フォーラム によると、検索結果に反映したいわけでないなら気にしなくて良いようです。

修正したければ、こちらのサイトが役に立つかもしれません。

残課題

パンくずリストはアルファベット順に表示されており、実際の階層構造を表していません。

Blogger ラベル頻度順パンくずリスト へ続く。

2017-12-18

Blogger コメント欄のスクロールバーを消す

コメント欄のスクロールバー

コメント欄の横にスクロールバーが出るのが気になったので、以下の CSS を追加して出ないようにしました。

#comment-editor {
    height: 225px;
}

Chrome で 202px, FireFox で 225px, IE で 208px 必要でした。(元の height は 184px)

2017-12-17

How to get Label Count outside widgets in Blogger

Blogger’s data tag “label.count” is only available within Labels widgets.

To get the label count (post number per label) outside the widget, you need to use JavaScript.

This is the simple JavaScript snippet: store the pair of the “Label Name / Count” from the Labels widget into an object (aka associative array).

JavaScript
var labelCounts = {};
var label = document.getElementById('Label1');
// 'div ul li' is for List, 'div>span' is for Cloud 
var elms = label.querySelectorAll('div ul li, div>span');
[].forEach.call(elms, function(elm) {
  if (elm.childElementCount == 2) {
    labelCounts[elm.children[0].innerHTML] = Number(elm.children[1].innerHTML.replace(/[()]/g, ''));
  }
});

Then, you can get the count of labelName by:

JavaScript
labelCounts["labelName"]

Prerequisite

  1. Labels widget ID is “Label1”.
  2. Check “Show number of posts per label” in “Configure Labels”.

2017-12-15

Crystal Reports, developer version for Microsoft Visual Studio のダウンロード

SAP Crystal Reports, developer version for Microsoft Visual Studio のダウンロードがなかなか複雑なのでメモを残しておきます。

SAP Web サイトの歩き方

  1. 日本語ページ
    SAP Crystal Reports, developer version for Microsoft Visual Studio | Crystalソリューション

  2. [SAP Crystal Reports, developer version for Microsoft Visual Studioをダウンロードする] をクリックすると、ダウンロードページに行きます。
    www.crystalreports.com/crystal-reports-visual-studio/

  3. e-mail を入力して、[Free Download] をクリックすると、www.crystalreports.com/crvs/confirm/
    に遷移します。

  4. ここで最新版のダウンロードができるのですが、[Access to Previous Runtime Downloads] の [Lean More] をクリックすると、SCN (Sap Community Network) Wiki
    Crystal Reports, Developer for Visual Studio Downloads - Business Intelligence (BusinessObjects) - SCN Wiki
    に行けます。

このページにいろいろと有用なことが書いてあります。(ブックマークするなら SCN Wiki ですね。)

SCN Wiki のポイント

SCN Wiki にはなかなか重要なことが書いてありますので、一読をお奨めします。(英語が苦手な人は機械翻訳ででも)

SCN Wiki もなかなか読みづらいのですが、ポイントを列挙しておきます。

  • Support Pack は基本的に四半期リリース。
  • Support Pack はフルビルドなので、一つずつバージョンアップする必要はない。最新版は表の一番上にある。(後述のとおり、表は削除済み)
  • Support Pack は累積なので、過去のパッチは削除した。ただし、3つ4つ程度のリンクは残しておく。(後述のとおり、リンクではなく URL を打ち変える。)
  • 前のバージョンを入手するには URL の SP 番号の部分を直接変更する。
  • ダウンロードページは2017年6月から e-mail でサインインするようにした。e-mailを登録すると、アップグレード情報を送ってくれる。
  • e-mail の登録をスキップしたかったら、http://www.crystalreports.com/crvs/confirm/ に直接アクセスする。
    (2018-04-15 追記 この直接リンクの記載はなくなりました。)

Visual Studio 2017 への対応状況

現時点の SP の最新バージョンは 13.0.21 ですが、このバージョンから Visual Studio 2017 への対応を謳っております。

最初のバージョンであり、内部的にも大きな変更をしているようで、安定しているとは言い難い状況のようです。

Visual Studio 2015 と Visual Studio 2017 の共存環境で試したところ、

  • Visual Studio 2015: 稼働したがメニューが文字化け
  • Visual Studio 2017: 稼働せず( rpt ファイルがバイナリのまま(16進数)で表示される)

という状況でした。

一つ目は、
Crystal Report 13.0.21 Shortcut Menu Language Broken…… - SAP Answers
二つ目は、
How to integrate SAP Crystal Reports in Visual Studio 2017 - Stack Overflow
でなんとか回避できそうですが、試行錯誤する前に次のリリースを待つことにしました。

(2018-04-15 追記 13.0.22をインストールしたところ、Visual Studio2017についての上記不具合は解消しました。)

なお、ランタイムは1台に1バージョンのみ(サイドバイサイド不可)です。
Crystal report for Visual studio 2017 - SAP Answers


参考記事

一つ前のバージョンですが、分かりやすくまとめてくださっています。

Visual Studio 2015でCrystal Reportsを使えるようにする - Webサービスで起業を目指すプログラマーblog

Visual Studio 2015で帳票の出力をしようと思い「Crystal Reports」を使うことにしました。標準ではインストールされていないので、別途ダウンロードとインストールが必要です。 その時の手順をまとめましたので、参考にして下さい。 環境 OS Windows 10 Visual Studio 201…

公式Q&Aのリンクを貼っておきます。

2017-12-12

ODP.NET ラッパー(Facade)クラスの作成

方針

  • コンストラクタで接続(デストラクタで切断)
  • プロバイダファクトリクラス(DbProviderFactory)を利用する。
  • Oracle 固有クラスについてはリフレクションを使用する。
  • Oracle 固有クラスを利用する場合は独立した関数を作成する。
  • パラメータの「名前によるバインド」(BindByName)をデフォルトにする。

コード

ODP.NET管理対象外ドライバ前提です。ODP.NET管理対象ドライバを使用する場合は、Oracle.DataAccess.Client の部分を Oracle.ManagedDataAccess に書き換えてください。(未テスト)

ODP.NET管理対象ドライバについてはこちらに良いまとめがあります。

ODP.NET Managed Driver(管理対象ドライバ)についての私的まとめ - しばたテックブログ

ODP.NET Managed Driverについて自分の欲しい形でまとまった資料がなかったのでここでまとめておきます。 ODP.NET Managed Driver(管理対象ドライバ)とは ODP.NET Managed Driver(管理対象ドライバ)とはOracle 12cから追加された新しい形のODP.NETで…

VB.NET
Imports System
Imports System.Data
Imports System.Data.Common

Public Class DbFacade
    Implements IDisposable

    Private _factory As DbProviderFactory
    Private _cnn As DbConnection
    Private _cmd As DbCommand
    Private _txn As DbTransaction

    Public Sub New()
        Me.New("Data Source=ExampleDb;User ID=SCOTT;Password=TIGER")
    End Sub

    Public Sub New(ByVal connectionString As String)
        _factory = DbProviderFactories.GetFactory("Oracle.DataAccess.Client")
        DbConnect(connectionString)
    End Sub

    Public Function DbConnect(ByVal connectionString As String) As DbConnection
        _cnn = _factory.CreateConnection()
        Me.ConnectionString = connectionString
        Return _cnn
    End Function

    Public Property ConnectionString() As String
        Get
            Return _cnn.ConnectionString
        End Get
        Set(ByVal value As String)
            If _cnn.ConnectionString <> value Then
                _cnn.ConnectionString = value
            End If
        End Set
    End Property

    Public Sub Open()
        If _cnn.State = ConnectionState.Closed Then _cnn.Open()
    End Sub

    Public Sub Close()
        ClearParams()
        If _cnn.State = ConnectionState.Open Then _cnn.Close()
    End Sub

    ''' <summary>
    ''' BindByNameプロパティをセットする
    ''' </summary>
    ''' <param name="isName">True:有効 False:無効</param>
    Private Sub SetBindByName(ByVal cmd As DbCommand, ByVal isName As Boolean)
        cmd.GetType().GetProperty("BindByName").SetValue(cmd, isName, Nothing)
    End Sub

    Public Function CreateCommand() As DbCommand
        Dim cmd As DbCommand = _factory.CreateCommand()
        cmd.Connection = _cnn
        SetBindByName(cmd, True)
        Return cmd
    End Function

    Public Function CreateCommand(ByVal sql As String) As DbCommand
        Dim cmd As DbCommand = CreateCommand()
        cmd.CommandText = sql
        Return cmd
    End Function

    Public Function CreateDataAdapter() As DbDataAdapter
        Return _factory.CreateDataAdapter()
    End Function

    ''' <summary>
    ''' パラメータを追加する。
    ''' </summary>
    ''' <param name="cmd"></param>
    ''' <param name="param"></param>
    ''' <remarks>Removeしなければ、「PLS-00703: リストに名前付き引数のインスタンスが複数あります。」が発生する</remarks>
    Private Sub AddParam(ByVal cmd As DbCommand, ByVal param As DbParameter)
        If cmd.Parameters.Contains(cmd.Parameters(param.ParameterName)) Then
            cmd.Parameters.Remove(cmd.Parameters(param.ParameterName))
        End If
        cmd.Parameters.Add(param)
    End Sub

    ''' <summary>
    ''' パラメータを追加する
    ''' </summary>
    ''' <param name="name">名称</param>
    ''' <param name="value"></param>
    ''' <param name="type">DbType</param>
    Public Function AddParam(ByVal name As String, ByVal value As Object, ByVal type As DbType) As IDataParameter
        If value.GetType().IsArray Then
            Return AddParamPLSQLAssociativeArray(name, value, type)
        End If
        If _cmd Is Nothing Then _cmd = CreateCommand()
        Dim param As DbParameter = _cmd.CreateParameter
        param.ParameterName = name
        param.DbType = type
        param.Value = value
        AddParam(_cmd, param)
        Return param
    End Function

    ''' <summary>
    ''' パラメータを追加する
    ''' </summary>
    ''' <param name="name">名称</param>
    ''' <param name="type">DbType</param>
    ''' <param name="direction">Direction</param>
    ''' <param name="size">Size</param>
    ''' <remarks>ストアド戻り値用</remarks>
    Public Function AddParam(ByVal name As String, ByVal type As DbType, ByVal direction As ParameterDirection, ByVal size As Integer) As IDataParameter
        If _cmd Is Nothing Then _cmd = CreateCommand()
        Dim param As DbParameter = _cmd.CreateParameter()
        param.ParameterName = name
        param.DbType = type
        param.Direction = direction
        param.Size = size
        AddParam(_cmd, param)
        Return param
    End Function

    ''' <summary>
    ''' パラメータを追加する
    ''' </summary>
    ''' <param name="cmd">DbCommand</param>
    ''' <param name="name">名称</param>
    ''' <param name="sourceColumn">列名</param>
    ''' <param name="type">DbType</param>
    ''' <remarks>DataAdapter用</remarks>
    Public Function AddParam(ByVal cmd As DbCommand, ByVal name As String, ByVal sourceColumn As String, ByVal type As DbType) As IDataParameter
        Dim param As DbParameter = cmd.CreateParameter()
        param.ParameterName = name
        param.SourceColumn = sourceColumn
        param.DbType = type
        AddParam(cmd, param)
        Return param
    End Function

    ''' <summary>
    ''' RefCursor型のパラメータを追加する
    ''' </summary>
    ''' <param name="name">名称</param>
    Public Function AddParamRefCursor(ByVal name As String) As IDataParameter
        If _cmd Is Nothing Then _cmd = CreateCommand()

        Dim asm As System.Reflection.Assembly = _factory.GetType().Assembly

        Dim paramType As Type = asm.GetType("Oracle.DataAccess.Client.OracleParameter")
        Dim param As Object = Activator.CreateInstance(paramType)

        paramType.GetProperty("ParameterName").SetValue(param, name, Nothing)
        paramType.GetProperty("Direction").SetValue(param, ParameterDirection.Output, Nothing)

        Dim oraDbType As Type = asm.GetType("Oracle.DataAccess.Client.OracleDbType")
        paramType.GetProperty("OracleDbType").SetValue(param, oraDbType.GetField("RefCursor").GetValue(Nothing), Nothing)
        AddParam(_cmd, DirectCast(param, DbParameter))
        Return DirectCast(param, IDataParameter)
    End Function

    ''' <summary>
    ''' PL/SQL連想配列のパラメータを追加する
    ''' </summary>
    ''' <param name="name">名称</param>
    ''' <param name="value"></param>
    ''' <param name="type">DbType</param>
    Public Function AddParamPLSQLAssociativeArray(ByVal name As String, ByVal value As Object, ByVal type As DbType) As IDataParameter
        If _cmd Is Nothing Then _cmd = CreateCommand()

        Dim asm As System.Reflection.Assembly = _factory.GetType().Assembly

        Dim oraParamType As Type = asm.GetType("Oracle.DataAccess.Client.OracleParameter")
        Dim oraParam As Object = Activator.CreateInstance(oraParamType)

        oraParamType.GetProperty("ParameterName").SetValue(oraParam, name, Nothing)
        oraParamType.GetProperty("Size").SetValue(oraParam, CType(value, Array).Length, Nothing)

        Dim oraCollectionType As Type = asm.GetType("Oracle.DataAccess.Client.OracleCollectionType")
        oraParamType.GetProperty("CollectionType").SetValue(oraParam, oraCollectionType.GetField("PLSQLAssociativeArray").GetValue(Nothing), Nothing)

        Dim param As IDataParameter = DirectCast(oraParam, IDataParameter)
        param.DbType = type
        param.Value = value

        AddParam(_cmd, DirectCast(param, DbParameter))

        Return param
    End Function

    ''' <summary>
    ''' パラメータをクリアする
    ''' </summary>
    Public Sub ClearParams()
        If _cmd Is Nothing Then Exit Sub
        _cmd.Parameters.Clear()
    End Sub

    ''' <summary>
    ''' DataTableを返す
    ''' </summary>
    ''' <param name="sql"></param>
    Public Function GetDataTable(ByVal sql As String) As DataTable
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandText = sql
        Dim da As DbDataAdapter = _factory.CreateDataAdapter()
        da.SelectCommand = _cmd
        Dim dt As DataTable = New DataTable
        da.Fill(dt)
        Return dt
    End Function

    ''' <summary>
    ''' RefCursorをDataSetにセットする
    ''' </summary>
    ''' <param name="dataSet"></param>
    ''' <param name="procedureName"></param>
    Public Sub FillRefCursor(ByRef dataSet As DataSet, ByVal procedureName As String)
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandText = procedureName
        _cmd.CommandType = CommandType.StoredProcedure
        Dim da As DbDataAdapter = _factory.CreateDataAdapter()
        da.SelectCommand = _cmd
        da.Fill(dataSet)
    End Sub

    ''' <summary>
    ''' RefCursorをDataTableにセットする
    ''' </summary>
    ''' <param name="dataTable"></param>
    ''' <param name="procedureName"></param>
    Public Sub FillRefCursor(ByRef dataTable As DataTable, ByVal procedureName As String)
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandText = procedureName
        _cmd.CommandType = CommandType.StoredProcedure
        Dim da As DbDataAdapter = _factory.CreateDataAdapter()
        da.SelectCommand = _cmd
        da.Fill(dataTable)
    End Sub

    ''' <summary>
    ''' SQLを実行する
    ''' </summary>
    ''' <param name="sql"></param>
    Public Function ExecuteSql(ByVal sql As String) As Integer
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandText = sql
        Open()
        Dim result As Integer = _cmd.ExecuteNonQuery()
        If _txn Is Nothing OrElse _txn.Connection Is Nothing Then
            Close()
        End If
        Return result
    End Function

    ''' <summary>
    ''' ストアドプロシージャを実行する
    ''' </summary>
    ''' <param name="procedureName"></param>
    Public Function ExecuteStored(ByVal procedureName As String) As Integer
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandType = CommandType.StoredProcedure
        _cmd.CommandText = procedureName
        Open()
        Dim result As Integer = _cmd.ExecuteNonQuery()
        If _txn Is Nothing OrElse _txn.Connection Is Nothing Then
            Close()
        End If
        Return result
    End Function

    Public Function ExecuteReader(ByVal sql As String) As DbDataReader
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandText = sql
        Open()
        Dim result As DbDataReader = _cmd.ExecuteReader(CommandBehavior.CloseConnection)
        ' Close()
        Return result
    End Function

    Public Function ExecuteScalar(ByVal sql As String) As Object
        If _cmd Is Nothing Then _cmd = CreateCommand()
        _cmd.CommandText = sql
        Open()
        Dim result as Object =_cmd.ExecuteScalar()
        Close()
        Return result
    End Function

    Public Sub BeginTransaction()
        Open()
        _txn = _cnn.BeginTransaction()
    End Sub

    Public Sub Commit()
        _txn.Commit()
        Close()
    End Sub

    Public Sub Rollback()
        _txn.Rollback()
        Close()
    End Sub

    Private disposedValue As Boolean = False        ' 重複する呼び出しを検出するには

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: 明示的に呼び出されたときにマネージ リソースを解放します
                If _txn IsNot Nothing Then _txn.Dispose()
                If _cmd IsNot Nothing Then _cmd.Dispose()
                If _cnn IsNot Nothing Then
                    If _cnn.State = ConnectionState.Open Then
                        _cnn.Close()
                    End If
                    _cnn.Dispose()
                End If
            End If

            ' TODO: 共有のアンマネージ リソースを解放します
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class
C#
using System;
using System.Data;
using System.Data.Common;

public class DbFacade : IDisposable
{
    private DbProviderFactory _factory;
    private DbConnection _cnn;
    private DbCommand _cmd;
    private DbTransaction _txn;

    public DbFacade() : this("Data Source=ExampleDb;User ID=SCOTT;Password=TIGER"){}

    public DbFacade(string connectionString)
    {
        _factory = DbProviderFactories.GetFactory("Oracle.DataAccess.Client");
        DbConnect(connectionString);
    }

    public DbConnection DbConnect(string connectionString)
    {
        _cnn = _factory.CreateConnection();
        ConnectionString = connectionString;
        return _cnn;
    }

    public string ConnectionString
    {
        get
        {
            return _cnn.ConnectionString;
        }
        set
        {
            if ((_cnn.ConnectionString != value))
            {
                _cnn.ConnectionString = value;
            }
        }
    }

    public void Open()
    {
        if ((_cnn.State == ConnectionState.Closed))
        {
            _cnn.Open();
        }
    }

    void Close()
    {
        ClearParams();
        if (_cnn.State == ConnectionState.Open)
        {
            _cnn.Close();
        }
    }

    /// <summary>
    /// BindByNameプロパティをセットする
    /// </summary>
    /// <param name="isName">True:有効 False:無効</param>
    void SetBindByName(DbCommand cmd, bool isName)
    {
        cmd.GetType().GetProperty("BindByName").SetValue(cmd, isName, null);
    }

    public DbCommand CreateCommand()
    {
        DbCommand cmd = _factory.CreateCommand();
        cmd.Connection = _cnn;
        SetBindByName(cmd, true);
        return cmd;
    }

    public DbCommand CreateCommand(string sql)
    {
        DbCommand cmd = CreateCommand();
        cmd.CommandText = sql;
        return cmd;
    }

    public DbDataAdapter CreateDataAdapter()
    {
        return _factory.CreateDataAdapter();
    }

    /// <summary>
    /// パラメータを追加する。
    /// </summary>
    /// <param name="cmd"></param>
    /// <param name="param"></param>
    /// <remarks>Removeしなければ、「PLS-00703: リストに名前付き引数のインスタンスが複数あります。」が発生する</remarks>
    private void AddParam(DbCommand cmd, DbParameter param)
    {
        if (cmd.Parameters.Contains(cmd.Parameters[param.ParameterName]))
        {
            cmd.Parameters.Remove(cmd.Parameters[param.ParameterName]);
        }
        cmd.Parameters.Add(param);
    }

    /// <summary>
    /// パラメータを追加する
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="value">値</param>
    /// <param name="type">DbType</param>
    public IDataParameter AddParam(string name, object value, DbType type)
    {
        if (value.GetType().IsArray)
        {
            return AddParamPLSQLAssociativeArray(name, value, type);
        }
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        DbParameter param = _cmd.CreateParameter();
        param.ParameterName = name;
        param.DbType = type;
        param.Value = value;
        AddParam(_cmd, param);
        return param;
    }

    /// <summary>
    /// パラメータを追加する
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="type">DbType</param>
    /// <param name="direction">Direction</param>
    /// <param name="size">Size</param>
    /// <remarks>ストアド戻り値用</remarks>
    public IDataParameter AddParam(string name, DbType type, ParameterDirection direction, int size)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        DbParameter param = _cmd.CreateParameter();
        param.ParameterName = name;
        param.DbType = type;
        param.Direction = direction;
        param.Size = size;
        AddParam(_cmd, param);
        return param;
    }

    /// <summary>
    /// パラメータを追加する
    /// </summary>
    /// <param name="cmd">DbCommand</param>
    /// <param name="name">名称</param>
    /// <param name="sourceColumn">列名</param>
    /// <param name="type">DbType</param>
    /// <remarks>DataAdapter用</remarks>
    public IDataParameter AddParam(DbCommand cmd, string name, string sourceColumn, DbType type)
    {
        DbParameter param = cmd.CreateParameter();
        param.ParameterName = name;
        param.SourceColumn = sourceColumn;
        param.DbType = type;
        AddParam(cmd, param);
        return param;
    }

    /// <summary>
    /// RefCursor型のパラメータを追加する
    /// </summary>
    /// <param name="name">名称</param>
    public IDataParameter AddParamRefCursor(string name)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        System.Reflection.Assembly asm = _factory.GetType().Assembly;
        Type paramType = asm.GetType("Oracle.DataAccess.Client.OracleParameter");
        object param = Activator.CreateInstance(paramType);
        paramType.GetProperty("ParameterName").SetValue(param, name, null);
        paramType.GetProperty("Direction").SetValue(param, ParameterDirection.Output, null);
        Type oraDbType = asm.GetType("Oracle.DataAccess.Client.OracleDbType");
        paramType.GetProperty("OracleDbType").SetValue(param, oraDbType.GetField("RefCursor").GetValue(null), null);
        AddParam(_cmd, param as DbParameter);
        return param as IDataParameter;
    }

    /// <summary>
    /// PL/SQL連想配列のパラメータを追加する
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="value">値</param>
    /// <param name="type">DbType</param>
    public IDataParameter AddParamPLSQLAssociativeArray(string name, object value, DbType type)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        System.Reflection.Assembly asm = _factory.GetType().Assembly;
        Type oraParamType = asm.GetType("Oracle.DataAccess.Client.OracleParameter");
        object oraParam = Activator.CreateInstance(oraParamType);
        oraParamType.GetProperty("ParameterName").SetValue(oraParam, name, null);
        oraParamType.GetProperty("Size").SetValue(oraParam, ((Array)(value)).Length, null);
        Type oraCollectionType = asm.GetType("Oracle.DataAccess.Client.OracleCollectionType");
        oraParamType.GetProperty("CollectionType").SetValue(oraParam, oraCollectionType.GetField("PLSQLAssociativeArray").GetValue(null), null);
        IDataParameter param = ((IDataParameter)(oraParam));
        param.DbType = type;
        param.Value = value;
        AddParam(_cmd, param as DbParameter);
        return param;
    }

    /// <summary>
    /// パラメータをクリアする
    /// </summary>
    public void ClearParams()
    {
        if (_cmd == null)
        {
            return;
        }
        _cmd.Parameters.Clear();
    }

    /// <summary>
    /// DataTableを返す
    /// </summary>
    /// <param name="sql"></param>
    public DataTable GetDataTable(string sql)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandText = sql;
        DbDataAdapter da = _factory.CreateDataAdapter();
        da.SelectCommand = _cmd;
        DataTable dt = new DataTable();
        da.Fill(dt);
        return dt;
    }

    /// <summary>
    /// RefCursorをDataSetにセットする
    /// </summary>
    /// <param name="dataSet"></param>
    /// <param name="procedureName"></param>
    public void FillRefCursor(ref DataSet dataSet, string procedureName)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandText = procedureName;
        _cmd.CommandType = CommandType.StoredProcedure;
        DbDataAdapter da = _factory.CreateDataAdapter();
        da.SelectCommand = _cmd;
        da.Fill(dataSet);
    }

    /// <summary>
    /// RefCursorをDataTableにセットする
    /// </summary>
    /// <param name="dataTable"></param>
    /// <param name="procedureName"></param>
    public void FillRefCursor(ref DataTable dataTable, string procedureName)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandText = procedureName;
        _cmd.CommandType = CommandType.StoredProcedure;
        DbDataAdapter da = _factory.CreateDataAdapter();
        da.SelectCommand = _cmd;
        da.Fill(dataTable);
    }

    /// <summary>
    /// SQLを実行する
    /// </summary>
    /// <param name="sql"></param>
    public int ExecuteSql(string sql)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandText = sql;
        Open();
        int result = _cmd.ExecuteNonQuery();
        if (_txn == null || _txn.Connection == null)
        {
            Close();
        };
        return result;
    }

    /// <summary>
    /// ストアドプロシージャを実行する
    /// </summary>
    /// <param name="procedureName"></param>
    public int ExecuteStored(string procedureName)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandType = CommandType.StoredProcedure;
        _cmd.CommandText = procedureName;
        Open();
        int result = _cmd.ExecuteNonQuery();
        if (_txn == null || _txn.Connection == null)
        {
            Close();
        };
        return result;
    }

    public DbDataReader ExecuteReader(string sql)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandText = sql;
        Open();
        DbDataReader result = _cmd.ExecuteReader(CommandBehavior.CloseConnection);
        // Close();
        return result;
    }

    public object ExecuteScalar(string sql)
    {
        if (_cmd == null)
        {
            _cmd = CreateCommand();
        }
        _cmd.CommandText = sql;
        Open();
        object result = _cmd.ExecuteScalar();
        Close();
        return result;
    }

    public void BeginTransaction()
    {
        Open();
        _txn = _cnn.BeginTransaction();
    }

    public void Commit()
    {
        _txn.Commit();
        Close();
    }

    public void Rollback()
    {
        _txn.Rollback();
        Close();
    }

    #region IDisposable Support
    private bool disposedValue = false; // 重複する呼び出しを検出するには

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: マネージ状態を破棄します (マネージ オブジェクト)。
                if (_txn != null)
                {
                    _txn.Dispose();
                }
                if (_cmd != null)
                {
                    _cmd.Dispose();
                }
                if (_cnn != null)
                {
                    if (_cnn.State == ConnectionState.Open)
                    {
                        _cnn.Close();
                    }
                    _cmd.Dispose();
                }
            }

            // TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。
            // TODO: 大きなフィールドを null に設定します。

            disposedValue = true;
        }
    }

    // TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。
    // ~DbFacade() {
    //   // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
    //   Dispose(false);
    // }

    // このコードは、破棄可能なパターンを正しく実装できるように追加されました。
    public void Dispose()
    {
        // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
        Dispose(true);
        // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。
        // GC.SuppressFinalize(this);
    }
    #endregion
}

利用方法

とりあえず、

VB.NET
Using db As New DbFacade()
    'Update
    db.ExecuteSql("UPDATE emp SET sal=sal*1.1 WHERE empno=7369")
    'DataTable
    Dim dt As DataTable = db.GetDataTable("select * from EMP")
End Using
C#
using (DbFacade db = new DbFacade())
{
    // Update
    db.ExecuteSql("UPDATE emp SET sal=sal*1.1 WHERE empno=7369");
    // DataTable
    DataTable dt = db.GetDataTable("select * from EMP");
}