【ASP.NET MVC】スクリプトをBundleしたりMinifyしたりする

ASP.NET MVCではwebpack使わなくてもスクリプトをバンドルできます.
各画面にソースのパスを書くのは面倒なのでBundleConfigでまとめてみます.

Microsoft.AspNet.Web.Optimizationの取得

Microsoft.AspNet.Web.OptimizationをNuGetから取得します.

BundleConfigの作成

App_StartフォルダにBundleConfig.csを追加します.

using System.Web.Optimization;

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js"));    
        bundles.Add(new ScriptBundle("~/scripts/hoge").Include(
            "~/Scripts/Sample/hoge.js",
            "~/Scripts/Sample/fuga.js",
            "~/Scripts/Sample/piyo.js"));
    }
}

BundleConfigをGlobal.asaxへ登録する

Global.asax.csでBundleConfigを呼び出すようにします.

using System.Web.Optimization;

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

画面からBundleを呼び出す

あとは画面で呼び出すだけです.

@using System.Web.Optimization

<head>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/hoge")
</head>

結果

<head>
    <script src="/map/scripts/jquery?v=WvOgJns1M1ZZkySVvxnR5qZTgNQ_UWnAsm21lcWbuMs1"></script>
    <script src="/map/scripts/hoge?v=fsiac-cyztW0v5rUgi-a9vvPTsOHXerrhW6x3w1IiHw1"></script>
</head>

ソースのパスをBundleConfigにまとめることが出来ました.
また、BundleConfigで{version}と指定することでライブラリのバージョンを更新しても気にする必要がなくなりました.
更に、結果のソースを見てみるとminifyされていることが分かります.

var hoge=function(){function n(){}return n.prototype.JsonToDocType=function(n){ ...

すてき.

【ASP.NET】面倒なオブジェクトマッピングはAutoMapperにやらせよう

AutoMapperって?

オブジェクトの詰め替えをしてくれる.NETのライブラリです. マッパーを使うことでオブジェクトの詰め替えをその都度記述する必要がなくなるのでコーディング量を減らすことが出来ます. また、オブジェクト間のプロパティの対応関係を局所化することが出来るので、修正箇所が限定されます.

AutoMapperの取得

NuGetからAutoMapperを取得してください.

AutoMapperを使わない場合

MVCではよくEntityをViewModelに詰め替えることがあるかと思います. 例えばDBから取得したユーザをViewModelに詰め替えて返す場合こんな感じになるかと思います.

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = new UserViewModel()
        {
            UserId = user.USER_ID;
            UserName = user.USER_NAME;
            Tel = user.TEL;
            Mail = user.MAIL;
        }
        return View(viewModel);
    }
}

Entity

public class USER
{
    public string USER_ID { get; set; }
    public string USER_NAME { get; set; }
    public string TEL { get; set; }
    public string MAIL { get; set; }
}

ViewModel

public class UserViewModel
{
    public string UserId { get; set; }
    public string UserName { get; set; }
    public string Tel { get; set; }
    public string Mail { get; set; }
}

先ほどのControllerに一覧を返すメソッドを追加した場合はこんな感じに.

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = new UserViewModel()
        {
            UserId = user.USER_ID;
            UserName = user.USER_NAME;
            Tel = user.TEL;
            Mail = user.MAIL;
        }
        return View(viewModel);
    }

    public ActionResult List()
    {
        // DBからUSER一覧を取得したとする
        var users = Service.FindUsers();
        var viewModels = new List<UserViewModel>();
        foreach(var user in users)
        {
            // USER -> UserViewModel
            var viewModel = new UserViewModel()
            {
                UserId = user.USER_ID;
                UserName = user.USER_NAME;
                Tel = user.TEL;
                Mail = user.MAIL;
            };
            viewModels.Add(viewModel);
        }         
        return View(viewModels);
    }    
}

詰め替えまくってますね(笑). 例えば「USERにFAXも追加しよう」となったとき,詰め替えている箇所全て修正する必要がありますね.

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = new UserViewModel()
        {
            UserId = user.USER_ID;
            UserName = user.USER_NAME;
            Tel = user.TEL;
            // 追加
            Fax = user.FAX;
            Mail = user.MAIL;
        }
        return View(viewModel);
    }

    public ActionResult List()
    {
        // DBからUSER一覧を取得したとする
        var users = Service.FindUsers();
        var viewModels = new List<UserViewModel>();
        foreach(var user in users)
        {
            // USER -> UserViewModel
            var viewModel = new UserViewModel()
            {
                UserId = user.USER_ID;
                UserName = user.USER_NAME;
                Tel = user.TEL;
                // 追加
                Fax = user.FAX;
                Mail = user.MAIL;
            };
            viewModels.Add(viewModel);
        }         
        return View(viewModels);
    }    
}

いちいち該当箇所を探すのは面倒ですよね. そこでAutoMapperです.

AutoMapperConfigの作成

AutoMapperConfig.csにマッピング情報を追加します. App_Start配下に用意してくださいね.

using AutoMapper;

public class AutoMapperConfig
{
    public static void RegisterAutoMappings()
    {
        Mapper.Initialize(cfg =>
        {
            // USER -> UserViewModel
            cfg.CreateMap<USER, UserViewModel>()
                .ForMember(d => d.UserId, o => o.MapFrom(s => s.USER_ID))
                .ForMember(d => d.UserName, o => o.MapFrom(s => s.USER_NAME))
                .ForMember(d => d.Tel, o => o.MapFrom(s => s.TEL))
                .ForMember(d => d.Mail, o => o.MapFrom(s => s.MAIL));
        });
    }
}

Global.asaxへの登録

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        AutoMapperConfig.RegisterAutoMappings();
    }
}

AutoMapperの利用

あとはControllerを修正します.

using AutoMapper;

public class UserController
{
    public ActionResult Detail(string name)
    {
        // DBからUSERを取得したとする
        var user = Service.FindUserByName(name);
        // USER -> UserViewModel
        var viewModel = Mapper.Map<UserViewModel>(user);
        return View(viewModel);
    }

    public ActionResult List()
    {
        // DBからUSER一覧を取得したとする
        var users = Service.FindUsers();
        var viewModels = new List<UserViewModel>();
        foreach(var user in users)
        {
            // USER -> UserViewModel
            var viewModel = Mapper.Map<UserViewModel>(user);
            viewModels.Add(viewModel);
        }         
        return View(viewModels);
    }    
}

超すっきりしましたね! これで「USERにFAXも追加しよう」となったときはAutoMapperConfigを修正するだけで済みます.

using AutoMapper;

public class AutoMapperConfig
{
    public static void RegisterAutoMappings()
    {
        Mapper.Initialize(cfg =>
        {
            // USER -> UserViewModel
            cfg.CreateMap<USER, UserViewModel>()
                .ForMember(d => d.UserId, o => o.MapFrom(s => s.USER_ID))
                .ForMember(d => d.UserName, o => o.MapFrom(s => s.USER_NAME))
                .ForMember(d => d.Tel, o => o.MapFrom(s => s.TEL))
                .ForMember(d => d.Fax, o => o.MapFrom(s => s.FAX))
                .ForMember(d => d.Mail, o => o.MapFrom(s => s.MAIL));
        });
    }
}

最後に

AutoMapperConfigにMapping情報を書けばMapperを呼び出すだけで簡単にマッピングされるようになります. マッピング情報がAutoMapperConfig一箇所にまとまるのでコードの見通しも良くなりメンテも捗るはずです.

【SQL Server】移行後のDBと移行前のDBのデータが同じかどうかを確認してみた

新旧DBのなかみが同じことを確認してみた

DBを移行してデータが本当に同じかどうかを検証してみた.
SQL Server 2008 -> SQL Server 2016.

方針

全件データをファイルに掃き出してハッシュ値を比べることにした.
もっとスマートなやり方知ってる人いたら誰か教えて!

  1. DBのテーブル一覧を取得する
  2. 各テーブルのデータを全件取得するSQLを作成する
  3. 新旧DBの各テーブルのデータをファイルに掃き出す
  4. ファイルのハッシュ値を比べて等しいことを確認する

スクリプト

1.DBのテーブル一覧を取得する

SQLはこんなかんじ.
SQL Serverではsys.objectからテーブル情報を取得出来る.
type = 'U'でユーザーテーブルを指定した.

USE [TEST_TABLE]
SELECT name FROM sys.objects WHERE type = 'U' AND name LIKE 'TEST_%' ORDER BY name;

あとはsqlcmdでSQLを叩く.
-oで結果を出力する.

$hostName = [System.Environment]::MachineName
$userName = "sa"
$password = "password"
$sql = "getTableNames.sql"
$csv = "tableNames.csv"
sqlcmd -S $hostName -U $userName -P $password -i $sql -o $csv

結果はこんなかんじ.

データベース コンテキストが 'TEST' に変更されました。
name                                                   
-------------------------------------------------------
TEST_USER
...
TEST_ROLE

(xxx 行処理されました)

2.各テーブルのデータを全件取得するSQLを作成する

1.でテーブル名一覧を取得したので各テーブルのデータを全件取得するSQLを作成する.
テンプレを用意してテーブル名をつっこんでSQLを発行するようにした.

USE [TEST]

DECLARE @sql nvarchar(max);
DECLARE @tblName nvarchar(max);

SET @tblName = N'{TABLE_NAME}';
SET @sql = N'SELECT * FROM ' + @tblName;

exec sp_executesql @sql

テーブル名一覧はコピペで用意しちゃった.

$template = "D:\Sample\SQL\template.sql"
$sqlOutDir = "D:\Sample\SQL\Test"
$tableNames = (
    "TEST_USER",
    ...
    "TEST_ROLE"
)
$tableNames | % { (Get-Content -Path $template) -replace "{TABLE_NAME}", $_ | Out-File -Encoding String -FilePath ("{0}\{1}.sql" -f $sqlOutDir, $_) }

3.新旧DBの各テーブルのデータをファイルに掃き出す

新旧DBそれぞれで2.で作成したSQLを実行する.

# 2.で作成したSQLが置いてあるとこ
$sqlDir = "D:\Sample\SQL\Test"
# 全件データの出力先
$outDir = "D:\Sample\Result\New"
# $outDir = "D:\Sample\Result\Old"
Get-ChildItem -Path $sqlDir -Filter *.sql | % { Execute-Sql -Sql ("{0}\{1}" -f $sqlDir, $_.Name) -FileName ($_.Name -split ".")[0] -OutDir $outDir }

function Execute-Sql(){
    Param(
        $Sql, 
        $FileName,
        $OutDir
    )
    $hostName = [System.Environment]::MachineName
    $userName = "sa"
    $password = "password"
    $csvName = ("{0}\{1}.csv" -f $OutDir, $FileName)
    sqlcmd -S $hostName -U $userName -P $password -i $Sql -o $csvName -s ","
}

4.ファイルのハッシュ値を比べて等しいことを確認する

$str = "ファイル,比較結果,新DB,旧DB`n"
Get-ChildItem -Path $RelResultDir -Filter *.csv | ForEach-Object { $str += Get-ComparedResultStr -FileName ($_.Name -split "\.")[0] -NewFileDir ("{0}\{1}" -f $RelResultDir, $_.Name) }
$str | Out-File "CompareResult.csv" -Encoding default

funciton Get-ComparedResultStr(){
    Param(
        $NewDataDir,
        $FileName
    )

    # 新DBのハッシュ値
    $NewHash = (Get-FileHash -Path $NewDataDir).Hash
    # 旧DBのデータを格納しているディレクトリに切替
    $OldDataDir = $NewDataDir -replace "New", "Old"
    # 旧DBのハッシュ値
    $OldHash = (Get-FileHash -Path $OldDataDir).Hash
    # CSVのデータ行
    return ("{0},{1},{2},{3}`n" -f $FileName, ($NewHash -eq $OldHash), $NewHash, $OldHash)
}

比較結果はこんなかんじ.

ファイル,比較結果,新DB,旧DB
TEST_USER,True,A8BD1EB1209E732645D9DB396A7EA969DB26EF105090FC8C2FB2C4E9E0A28A7E,A8BD1EB1209E732645D9DB396A7EA969DB26EF105090FC8C2FB2C4E9E0A28A7E
...
TEST_ROLE,True,DFE6BB21EEE7ED304D72F02B77974ADA00F8E8958A477045CAE0461B3A4EFF11,DFE6BB21EEE7ED304D72F02B77974ADA00F8E8958A477045CAE0461B3A4EFF11

これでスクリプトで正しくデータ移行できたか検証できた.

matplotlibの日本語表示問題を解決する

matplotlibのとうふを撲滅する

f:id:mt9116:20180719150741p:plain

にほんごがとうふになっちゃう...

日本語に対応したフォントを用意する

https://ipafont.ipa.go.jp/ からIPAexゴシックフォントをダウンロードする.

matplotlibの設定ファイルのパスを調べる

import matplotlib
matplotlib.matplotlib_fname()

設定ファイルを編集する

matplotlibrcファイルを適当なエディタで開いて下の行を追加する.

font.family : IPAexGothic

フォントファイルを配置する

設定ファイルのパス/fonts/tffにダウンロードしたipaexg.tffを置く.

キャッシュを再構築する

import matplotlib
matplotlib.font_manager._rebuild()

以上でとうふは消えます.

f:id:mt9116:20180719150754p:plain

tesseractでOCR@Windows7

OCRに興味があったので試してみた.

tesseract

OCRエンジンのオープンソース. https://github.com/tesseract-ocr/tesseract

tesseractのインストール

Windows7でtesseractを使えるようにした. まず, https://github.com/parrot-office/tesseract/releases/tag/3.5.1 からtesseract-Win64.zipをダウンロードした. ダウンロードしたらディレクトリ(C:\Program Files\Tesseract-OCR)を作成して, zipファイルをC:\Program Files\Tesseract-OCRに展開した.

辞書データの準備

https://github.com/tesseract-ocr/tesseract/wiki/Data-Files のeng.traineddata, jpn.traineddataをダウンロードする. ディレクトリ(C:\Program Files\Tesseract-OCR\tessdata)を作成して, eng.traineddata, jpn.traineddataを置く.

環境変数の設定

変数
Path C:\Program Files\Tesseract-OCR
TESSDATA_PREFIX C:\Program Files\Tesseract-OCR\tessdata

tesseractを試す

コマンドプロンプトでtesseractを実行する. sample.pngをtesseractでテキストにしてみる.

tesseract sample.png result -l jpn

画像

sample.png

結果 result.txt

何言つてんだよ! !
その崖っぷちが最髙のチヤンスなんだぜ ! !

まつおかしゅうぞう

ちゃんとテキスト化できた(笑)

pyocr

tesseractをpythonから使えるようにしたラッパー. https://gitlab.gnome.org/World/OpenPaperwork/pyocr

pyocrのインストール

環境はanaconda@windows7. pyocr用に仮想環境を作ってpyocrをインストールする.

> conda create -n ocr python=3.6
> conda info -e
# conda environments:
#
base                  *  c:\tools\Anaconda3
ocr                      c:\tools\Anaconda3\envs\ocr
> activate ocr
(ocr)> pip install pyocr

pyocrからtesseractを呼び出せるか確認

https://gitlab.gnome.org/World/OpenPaperwork/pyocr のUsage Initializationで検証する.

test.py

from PIL import Image
import sys

import pyocr
import pyocr.builders

tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
# The tools are returned in the recommended order of usage
tool = tools[0]
print("Will use tool '%s'" % (tool.get_name()))
# Ex: Will use tool 'libtesseract'

langs = tool.get_available_languages()
print("Available languages: %s" % ", ".join(langs))
lang = langs[0]
print("Will use lang '%s'" % (lang))
# Ex: Will use lang 'fra'
# Note that languages are NOT sorted in any way. Please refer
# to the system locale settings for the default language
# to use.
(ocr)> python test.py
Will use tool 'Tesseract (sh)'
Available languages: eng, jpn
Will use lang 'eng'

tesseractが正しく認識されているようだ.

pyocrを使ってみる

img_to_txt.py

from PIL import Image
import sys
sys.path.append('/path/to/dir')

import codecs
import pyocr
import pyocr.builders

tool = pyocr.get_available_tools()[0]
builder = pyocr.builders.TextBuilder()

# 画像から文字列を取得する
txt = tool.image_to_string(
    Image.open('sample2.png'),
    lang='jpn',
    builder=builder
)

# 文字列を書き出す
with codecs.open("result2.txt", 'w', encoding='utf-8') as file_descriptor:
    builder.write_file(file_descriptor, txt)
(ocr)> python img_to_txt.py

画像

sample2.PNG

結果 restult2.txt

ー番になるつていつたよな?
日本ーなるつつつたよな!
ぬるま湯なんかつかつてんじゃねぇよお前! !

まつおかしゆうぞう

アツイ言葉が画像から読み取れた(笑)

今後

とりあえず軽く試してみて思った以上に使えそうなことが分かった. 次は手書きの文字でもちゃんと認識できるのか試したい.

GACからDLLをエクスポートする

GACからDLLをエクスポートする

GACからDLLをエクスポートする必要があったため普通無い調べた.

GACって?

  • Global Assembly Cacheのこと.
  • マシンでグローバルにアセンブリを参照出来る.
  • 規定のパスは「%windir%\assembly」.

GACをのぞく

エクスプローラーで「C:\Windows\assembly」を開いてみる.

f:id:mt9116:20180628102651p:plain

通常の表示とは若干異なる. これは「アセンブリキャッシュビューア」によるものらしい.

f:id:mt9116:20180628102659p:plain

アセンブリキャッシュビューアが有効だとDLLがコピーできないみたい. これじゃ困るのでこれを無効にする.

アセンブリキャッシュビューアを無効にする

regedit.exeを管理者権限で起動する. 「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion」で右クリックし「新規」→「DWORD(32ビット)」をクリックする.

f:id:mt9116:20180628102708p:plain

名前を「DiableCacheViewer」とする. 「DisableCacheViewer」を右クリックし, 修正をクリックする.

f:id:mt9116:20180628102716p:plain

「値のデータ」を「1」とし, OKをクリックする.

f:id:mt9116:20180628102725p:plain

これでアセンブリキャッシュビューアが無効になる.

f:id:mt9116:20180628102733p:plain

エクスプローラーで「C:\Windows\assembly」を開いてみるとアセンブリキャッシュビューアが無効になったことが分かる.

f:id:mt9116:20180628102741p:plain

GUIでやるのがめんどくさい人はPowerShelでどうぞー

 New-ItemProperty -path "HKLM:\Software\Microsoft\Fusion" -name "DisableCacheViewer" -value 1 –PropertyType DWORD -Force

GACからDLLをエクスポートする

アセンブリキャッシュビューアを無効にすればあとは好きなだけDLLをコピーできる.

f:id:mt9116:20180628102749p:plain

Copy-Item -Path C:\Windows\assembly\GAC_32\ESRI.ArcGIS.ADF -Destination D:\Somewhere -Force -Recurse

裏話

ArcGIS 10.1から10.6にアップグレードした際にジオコーディングバッチが動かなくなった. ログを見るとDLLの参照がありません的なメッセージがあった. そこでバッチで参照しているDLLを見てみるとArcGIS Web ADFなるものを発見した. ArcGIS Web ADFについて調査すると...

次期バージョンの ArcGIS 10.2 がいよいよ来月末リリースする予定です。前回のバージョン 10.1 のリリースが 2012 年 10 月 1 日でしたのでちょうど 1 年の歳月を経てのバージョンアップとなります。 この ArcGIS 10.2 のリリースに伴い、ArcGIS 10.1 までサポートされていたいくつかのコンポーネントや環境などがサポート終了となります。 目立ったところでは、ArcGIS for Server に付属していた Web アプリケーション開発キットの Web ADF(Application Developer Framework)および ArcGIS Server Manager のウィザードを使って作成できる Web マッピング アプリケーションが提供されなくなります。これらについてはすでに JavaScriptFlex、そして Silverlight といった各種 Web API を提供しており、それらを使用することをお勧めしています。

そりゃあ10.6で提供されているわけないよなー ということで旧サーバーからDLLを取り出す必要があったためこの記事を書くに至った. バッチをビルドするときにDLLも含めておいてくれれば済んだ話なのに

差分バックアップのリストアにはまった話

差分バックアップのリストアにはまった

DBサーバーリプレースの話. 本番環境が稼働している裏でDBサーバーを構築し, 本番環境に切り替えるタイミングで差分バックアップを取得して構築したDBサーバーにリストアしようと画策したらちょっとはまった.

本番環境で稼働しているDBサーバーを「旧DBサーバー」, 裏で構築しているDBサーバーを「新DBサーバー」とする. 使っているDBは旧DBサーバーが「SQL Server 2008 R2 Standard」, 新DBサーバーが「SQL Server 2016 Enterprise」.

事前準備

新DBサーバーを構築してフルバックアップを適用する.

旧DBサーバーでフルバックアップを取得する.

BACKUP DATABASE [SAMPLE] TO  DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak';

新DBサーバーにフルバックアップをリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak';

差分バックアップを取得してリストアする

挑戦その1

旧DBサーバーで差分バックアップを取得する.

BACKUP DATABASE [SAMPLE] TO  DISK = N'E:\SAMPLE\Backup\SAMPLE_DIFFBACKUP.bak' WITH DIFFERENTIAL;

新DBサーバーに差分バックアップをリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_DIFFBACKUP.bak';

するとこんなエラーが

メッセージ 3117、レベル 16、状態 1、行 1
ロールフォワードできる状態のファイルがないので、ログまたは差分バックアップは復元できません。
メッセージ 3013、レベル 16、状態 1、行 1
RESTORE DATABASE が異常終了しています。

なんだと...

原因調査

データベースの状態を見てみる.

SELECT DATABASEPROPERTYEX('SAMPLE', 'Status') AS DB_STATUS;
DB_STATUS
---------
ONLINE

デフォルトでリストアするとステータスがONLINEになり, 差分バックアップのリストアを受け付けなくなってしまうようだ. まあ, ONLINEだとデータベース操作できるからデータに不整合生じて差分バックアップをリストア出来なくなるのも当然か.

解決策

リストアのオプションを調べた.

オプション 内容
RECOVERY デフォルトオプション. リストア後データベースは「完全復旧」状態となる. 以降バックアップのリストアを受け付けない.
NORECOVERY リストア後データベースは「復元中」状態となる. データベースの操作を一切受け付けない.
STANDBY リストア後データベースは「スタンバイ」状態となる. 読み取りのみ可能にするためデータの不整合が生じない.

新DBサーバー構築後, 各アプリケーション, 地図サーバーから接続出来る状態にしておきたいので出来ればSTANDBY状態でフルバックアップをリストアしておきたい. というわけで, フルバックアップをSTANDBYでリストアして差分バックアップをリストア出来るか試してみる.

挑戦その2

一旦新DBサーバーのデータベースを消す.

DROP DATABASE [SAMPLE];

新DBサーバーにフルバックアップをSTANDBYでリストアするぞ!!

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak'
WITH STANDBY = N'E:\SAMPLE\Backup\SAMPLE_ROLLBACKUNDO.bak';

結果だめだった...

メッセージ 3180、レベル 16、状態 1、行 1
このバックアップは WITH STANDBY では復元できません。データベースのアップグレードが必要です。WITH STANDBY を指定しないで RESTORE を再実行してください。
メッセージ 3013、レベル 16、状態 1、行 1
RESTORE DATABASE が異常終了しています。

SQL Server 2008 R2 Standard」で取得したフルバックアップを「SQL Server 2016 Enterprise」にSTANDBYでリストア出来ないみたいっす. ちなみに「SQL Server 2016 Enterprise」で取得したフルバックアップを「SQL Server 2016 Enterprise」にSTANDBYでリストアすることは出来た. 闇が深そう(そもそもだめなのかな...)なので別の策を考えることに.

結局

残りはNORECOVERYしかないんでフルバックアップをNORECOVERYでリストアして差分バックアップをリストア出来るか試す.

最後の挑戦

新DBサーバーにフルバックアップをNORECOVERYでリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_FULLBACKUP.bak'
WITH NORECOVERY;

新DBサーバーに差分バックアップをリストアする.

RESTORE DATABASE [SAMPLE] FROM DISK = N'E:\SAMPLE\Backup\SAMPLE_DIFFBACKUP.bak';

正常終了した. データ見ても差分バックアップの内容が反映されていた.

まとめ

データベースをリストアする際はオプションに気をつけよう.