院長のメモ帖
2015年10月 1日 木曜日
FileInfoのIsReadOnlyはRefresh()とともに
ちょっとしたファイルバックアップコードを書いていてハマりました。
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
f.CopyTo(bak.FullName);
bak.IsReadOnly = true;
}
bakファイルが存在しなければfファイルをコピーしてbakを作成し、読み出し専用フラグを設定する、というだけの簡単なコードですが、出来上がったファイルは、readOnlyどころか、Hidden, Systemその他確認できる限りのフラグが立ってしまっていました。確認のため次のコードを実行した所、
private static void check(FileInfo bak)
{
bak.IsReadOnly = true;
}
問題なく、ReadOnly属性のみついていました。
少しデバッグして気が付いたのですが、まず、bak.Existを呼び出した時点で、bakにはファイルが存在していないことが
キャッシュされます。これは、次行のファイルコピーされた後も変化しません。その次にbakに属性をセットるわけですが、この時点でbakのAttribiutesプロパティーは-1になっており、IsReadOnlyは以下のコードのようにファイルAttribiutesのReadOnlyビットを操作しているのでキャッシュされた-1に対する演算になってしまします。そしてこの結果が実ファイルに書き込まれるので、あらゆるファイル属性がonになって隠しシステムファイルが誕生してしまったわけです。
public bool IsReadOnly {
get {
return (Attributes & FileAttributes.ReadOnly) != 0;
}
set {
if (value)
Attributes |= FileAttributes.ReadOnly;
else
Attributes &= ~FileAttributes.ReadOnly;
}
}
ですから、最初のコードは
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
{
f.CopyTo(bak.FullName);
bak.Refresh();
}
bak.IsReadOnly = true;
}
と、RefreshしてからIsReadOnlyする必要がありました。
FileInfoやDirectoryInfoでIO操作するときはキャッシュの存在を頭に入れておかないと、時としてとんでもないことが起こりますね(-_-;)
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
f.CopyTo(bak.FullName);
bak.IsReadOnly = true;
}
bakファイルが存在しなければfファイルをコピーしてbakを作成し、読み出し専用フラグを設定する、というだけの簡単なコードですが、出来上がったファイルは、readOnlyどころか、Hidden, Systemその他確認できる限りのフラグが立ってしまっていました。確認のため次のコードを実行した所、
private static void check(FileInfo bak)
{
bak.IsReadOnly = true;
}
問題なく、ReadOnly属性のみついていました。
少しデバッグして気が付いたのですが、まず、bak.Existを呼び出した時点で、bakにはファイルが存在していないことが
キャッシュされます。これは、次行のファイルコピーされた後も変化しません。その次にbakに属性をセットるわけですが、この時点でbakのAttribiutesプロパティーは-1になっており、IsReadOnlyは以下のコードのようにファイルAttribiutesのReadOnlyビットを操作しているのでキャッシュされた-1に対する演算になってしまします。そしてこの結果が実ファイルに書き込まれるので、あらゆるファイル属性がonになって隠しシステムファイルが誕生してしまったわけです。
public bool IsReadOnly {
get {
return (Attributes & FileAttributes.ReadOnly) != 0;
}
set {
if (value)
Attributes |= FileAttributes.ReadOnly;
else
Attributes &= ~FileAttributes.ReadOnly;
}
}
ですから、最初のコードは
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
{
f.CopyTo(bak.FullName);
bak.Refresh();
}
bak.IsReadOnly = true;
}
と、RefreshしてからIsReadOnlyする必要がありました。
FileInfoやDirectoryInfoでIO操作するときはキャッシュの存在を頭に入れておかないと、時としてとんでもないことが起こりますね(-_-;)
2015年7月22日 水曜日
Visual Studio 2015 のC#6.0で・がエラーになった
先日Visual Studio 2015のRTMがMSDNでダウンロード可能になったので早速インストールしました。
世間的にはXamarinなどを使ってのクロスプラットフォーム開発が目玉かもしれませんが、売り物のアプリを作るほどではないものには、Xamarinは高価すぎるのでとりあえずスルー。
個人的にはC#6.0の細かい文法上のリファインが楽しみです。自動実装プロパティーの初期化子が一番ありがたいですね。
でとりあず、VS2013で開発しているプロジェクトを一つ読み込んでリビルドかけたら、いきなりエラーが発生。よく見てみると
世間的にはXamarinなどを使ってのクロスプラットフォーム開発が目玉かもしれませんが、売り物のアプリを作るほどではないものには、Xamarinは高価すぎるのでとりあえずスルー。
個人的にはC#6.0の細かい文法上のリファインが楽しみです。自動実装プロパティーの初期化子が一番ありがたいですね。
でとりあず、VS2013で開発しているプロジェクトを一つ読み込んでリビルドかけたら、いきなりエラーが発生。よく見てみると
エラー CS1056 予期しない文字 '・'
今まで識別子に使えた'・'や'・'は使えなくなったようです。C#コンパイラを一から作り直したことで互換性はさがったようですね。手持ちのコードを全部調べても、このprivateの変数一か所にしか使ってなかったのでほぼ影響はなかったですが、なぜ・を使ったのか謎です(笑)
もう一つ影響を受けたのが、ソース管理の挙動です。ついにオプション-ソース管理-環境から「ソリューションまたはプロジェクトを開いたときにすべて取得」がなくなってしまいました...VS2013のときには自動チェックインオプションである「ソリューションまたはプロジェクトを閉じるときにすべてをチェックイン」がオプションとしては残ったまま機能しなくなったのに続いて、自動取得までできなくなるとは...
大企業では使わないオプションなのかもしれませんが、両方をオンにすると自動バックアップと移動プロファイルを合わせたような機能だったので、複数のPCで一人で開発しているプログラマーには非常にありがたい機能だったのですが...使わない人や企業はオフにすればいいだけやん。グループポリシーか何かで上書き禁止で集中管理できるようにしてオプションとしては残しておけばいいやん...ほんと、個人開発者をないがしろにしてるとC#コーダーのすそ野が減るでよ...
もう一つ影響を受けたのが、ソース管理の挙動です。ついにオプション-ソース管理-環境から「ソリューションまたはプロジェクトを開いたときにすべて取得」がなくなってしまいました...VS2013のときには自動チェックインオプションである「ソリューションまたはプロジェクトを閉じるときにすべてをチェックイン」がオプションとしては残ったまま機能しなくなったのに続いて、自動取得までできなくなるとは...
大企業では使わないオプションなのかもしれませんが、両方をオンにすると自動バックアップと移動プロファイルを合わせたような機能だったので、複数のPCで一人で開発しているプログラマーには非常にありがたい機能だったのですが...使わない人や企業はオフにすればいいだけやん。グループポリシーか何かで上書き禁止で集中管理できるようにしてオプションとしては残しておけばいいやん...ほんと、個人開発者をないがしろにしてるとC#コーダーのすそ野が減るでよ...
2015年6月 2日 火曜日
Enumerable.Cast<TResult>メソッドの罠
前回、最近データー解析にはまっている話を書きましたが、その続きです。
自前で相関係数を計算するライブラリを作りました。
/blog/SourceCode/CorrelationCoefficient.cs.txt
/// <summary>
/// 二つの集合の相関係数を求める。二つのシーケンスの先頭から順番にペアとして要素を取り出し、要素数の多いシーケンスの残りの要素は無視される。
/// 計算式にはピアソンの積率相関係数を用いている。
/// 例外: ArgumentNullException 引数がnullシーケンス。
/// DivideByZeroException 引数が空のシーケンス。
/// </summary>
/// <param name="List1">集合1</param>
/// <param name="List2">集合2</param>
/// <returns>相関係数</returns>
public static double CorrelationCoefficient(this IEnumerable<double> List1, IEnumerable<double> List2)
{
if (List1 == null)
throw new ArgumentNullException("引数List1がnullです。");
if (List2 == null)
throw new ArgumentNullException("引数List2がnullです。");
var calcMember = List1.Zip(List2, (x, y) => new { x, y }).ToArray();
//入力された二つのシーケンスを合流して、(x, y)の一対のデーターになった配列に変換する。
//多い方の余った要素は無視される。
if (calcMember.Length == 0)
{
//要素数がゼロなら例外発生。
StringBuilder sb = new StringBuilder("計算する要素数がゼロです。");
if (List1.Count() == 0)
sb.Append("引数List1が空のシーケンスです。");
if (List1.Count() == 0)
sb.Append("引数List2が空のシーケンスです。");
throw new DivideByZeroException(sb.ToString());
}
///ピアソンの積率相関係数を計算
/// SIGMA((xi-ax)(yi-ay)) / SQRT(SIGMA((xi-ax)^2) / SQRT(SIGMA((yi-ya)^2))
var ax = calcMember.Select(r => r.x).Average();
var ay = calcMember.Select(r => r.y).Average();
var result = calcMember
.Select(r => new { xi = r.x - ax, yi = r.y - ay })
.Select(r => new { a = r.xi * r.yi, b = r.xi * r.xi, c = r.yi * r.yi })
.Aggregate((t, i) => new { a = t.a + i.a, b = t.b + i.b, c = t.c + i.c });
return result.a / Math.Sqrt(result.b) / Math.Sqrt(result.c);
}
まあ、このメソッドは別に難しくもなんともなく普通に動くのですが、実際のデーターはint型なことが圧倒的に多いので、int型を受け取るオーバーロードを加えてみました。
public static double CorrelationCoefficient(this IEnumerable<int> List1, IEnumerable<int> List2)
{
return CorrelationCoefficient(List1.Cast<double>(), List2.Cast<double>());
}
intからdoubleにキャストして型合わせをしただけのオーバーロードですが、実行時におもいっきりInvalidCastExceptionなぞ出てしまいました。Cast<T>メソッドは今までほぼ使ったことなかったのですが、前回の記事を書くときにLINQのおさらいをして存在を思い出したので、今回使ってみたところ思いっきりエラー出ました。
MSDNを調べた限り、OfType<T>メソッドとの違いはキャストできないときにnullを返すのか例外を出すのかだけの違いのようで、OfType<T>はいつも常用していて特に不都合を感じたことはありません。しかし、このソースでOfType<T>にしたところ、要素数がゼロになってDivideByZeroExceptionが出るようになっただけでした。
検証した限りではintからintの変換はobject型に格納していようがint型変数に格納していようが問題ないですが、違う値型同士では一切変換できないようです。クラス型の変換では全く問題が起こりませんでした。いやこれができないようではOfType使った今までのプログラム大半が動かないはずですから(笑)
違う値型への変換が全くできないと分かったので同じこと見つけた人いないかと思って検索した所、ありました。ここの解説によると、いったん入力値をobjectに変換してから目的の型に変換する仕様だから値型で機能しないそうです。このページにならってILSpyでCast<T>のコードを覗いてみますと
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
if (enumerable != null)
{
return enumerable;
}
if (source == null)
{
throw Error.ArgumentNull("source");
}
return Enumerable.CastIterator<TResult>(source);
}
最初の太字のところが肝心の変換ですが、なんとas演算子でキャストしてるじゃないですか。as演算子のヘルプには思いっきり、「as の演算子は参照の変換、null 許容変換とボックス化変換だけ実行します。 as の演算子は他の変換を使用して、キャスト式を使用して実行する必要があるユーザー定義の変換など実行できません。」とかかれているわけで、当然ここはnullが帰ります。そこで最終行のCastIterator<T>メソッドへ飛ぶわけですが、
private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
foreach (object current in source)
{
yield return (TResult)((object)current);
}
yield break;
}
というわけでintをobjectにボックス化してそれをdoubleにアンボクシング化を試みてますが、それは無理というもので、あえて書くとしたら太字のところは
(double)((int)((object)current))
ですよね...
例のコードは
public static double CorrelationCoefficient(this IEnumerable<int> List1, IEnumerable<int> List2)
とすることで決着がつきました。MSDNのページに違う値型への変換は例外発生するって一言書いてほしいな。キャストって名前も誤解招きやすいから、内部ではas演算子つかってますよってこともね。
自前で相関係数を計算するライブラリを作りました。
/blog/SourceCode/CorrelationCoefficient.cs.txt
/// <summary>
/// 二つの集合の相関係数を求める。二つのシーケンスの先頭から順番にペアとして要素を取り出し、要素数の多いシーケンスの残りの要素は無視される。
/// 計算式にはピアソンの積率相関係数を用いている。
/// 例外: ArgumentNullException 引数がnullシーケンス。
/// DivideByZeroException 引数が空のシーケンス。
/// </summary>
/// <param name="List1">集合1</param>
/// <param name="List2">集合2</param>
/// <returns>相関係数</returns>
public static double CorrelationCoefficient(this IEnumerable<double> List1, IEnumerable<double> List2)
{
if (List1 == null)
throw new ArgumentNullException("引数List1がnullです。");
if (List2 == null)
throw new ArgumentNullException("引数List2がnullです。");
var calcMember = List1.Zip(List2, (x, y) => new { x, y }).ToArray();
//入力された二つのシーケンスを合流して、(x, y)の一対のデーターになった配列に変換する。
//多い方の余った要素は無視される。
if (calcMember.Length == 0)
{
//要素数がゼロなら例外発生。
StringBuilder sb = new StringBuilder("計算する要素数がゼロです。");
if (List1.Count() == 0)
sb.Append("引数List1が空のシーケンスです。");
if (List1.Count() == 0)
sb.Append("引数List2が空のシーケンスです。");
throw new DivideByZeroException(sb.ToString());
}
///ピアソンの積率相関係数を計算
/// SIGMA((xi-ax)(yi-ay)) / SQRT(SIGMA((xi-ax)^2) / SQRT(SIGMA((yi-ya)^2))
var ax = calcMember.Select(r => r.x).Average();
var ay = calcMember.Select(r => r.y).Average();
var result = calcMember
.Select(r => new { xi = r.x - ax, yi = r.y - ay })
.Select(r => new { a = r.xi * r.yi, b = r.xi * r.xi, c = r.yi * r.yi })
.Aggregate((t, i) => new { a = t.a + i.a, b = t.b + i.b, c = t.c + i.c });
return result.a / Math.Sqrt(result.b) / Math.Sqrt(result.c);
}
まあ、このメソッドは別に難しくもなんともなく普通に動くのですが、実際のデーターはint型なことが圧倒的に多いので、int型を受け取るオーバーロードを加えてみました。
public static double CorrelationCoefficient(this IEnumerable<int> List1, IEnumerable<int> List2)
{
return CorrelationCoefficient(List1.Cast<double>(), List2.Cast<double>());
}
intからdoubleにキャストして型合わせをしただけのオーバーロードですが、実行時におもいっきりInvalidCastExceptionなぞ出てしまいました。Cast<T>メソッドは今までほぼ使ったことなかったのですが、前回の記事を書くときにLINQのおさらいをして存在を思い出したので、今回使ってみたところ思いっきりエラー出ました。
MSDNを調べた限り、OfType<T>メソッドとの違いはキャストできないときにnullを返すのか例外を出すのかだけの違いのようで、OfType<T>はいつも常用していて特に不都合を感じたことはありません。しかし、このソースでOfType<T>にしたところ、要素数がゼロになってDivideByZeroExceptionが出るようになっただけでした。
検証した限りではintからintの変換はobject型に格納していようがint型変数に格納していようが問題ないですが、違う値型同士では一切変換できないようです。クラス型の変換では全く問題が起こりませんでした。いやこれができないようではOfType使った今までのプログラム大半が動かないはずですから(笑)
違う値型への変換が全くできないと分かったので同じこと見つけた人いないかと思って検索した所、ありました。ここの解説によると、いったん入力値をobjectに変換してから目的の型に変換する仕様だから値型で機能しないそうです。このページにならってILSpyでCast<T>のコードを覗いてみますと
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
if (enumerable != null)
{
return enumerable;
}
if (source == null)
{
throw Error.ArgumentNull("source");
}
return Enumerable.CastIterator<TResult>(source);
}
最初の太字のところが肝心の変換ですが、なんとas演算子でキャストしてるじゃないですか。as演算子のヘルプには思いっきり、「as の演算子は参照の変換、null 許容変換とボックス化変換だけ実行します。 as の演算子は他の変換を使用して、キャスト式を使用して実行する必要があるユーザー定義の変換など実行できません。」とかかれているわけで、当然ここはnullが帰ります。そこで最終行のCastIterator<T>メソッドへ飛ぶわけですが、
private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
foreach (object current in source)
{
yield return (TResult)((object)current);
}
yield break;
}
というわけでintをobjectにボックス化してそれをdoubleにアンボクシング化を試みてますが、それは無理というもので、あえて書くとしたら太字のところは
(double)((int)((object)current))
ですよね...
例のコードは
public static double CorrelationCoefficient(this IEnumerable<int> List1, IEnumerable<int> List2)
{
return CorrelationCoefficient(List1.Select(r=>(double)r), List2.Select(r=>(double)r));
}
とすることで決着がつきました。MSDNのページに違う値型への変換は例外発生するって一言書いてほしいな。キャストって名前も誤解招きやすいから、内部ではas演算子つかってますよってこともね。
2015年5月31日 日曜日
LINQのJoinメソッド
最近、データーベースに集まった数値データーの解析にはまっています。なんのデーターかはあれですから秘密ですが、平均とったり、順位付けしたり、ある条件下のデーターと違う条件下のデータを比較したり、偏差値換算したり、相関関係とったり、ソートしたりと、まああらゆることをして解析しております。
私はSQL文は触りしか理解してないので、これらは全部C#からLINQ To SQL経由で処理してます。Viewやらストアドプロシージャーをうまく使えばもっと幅が広がるのでしょうが、日曜プログラマーにはいくつもの言語に精通する時間と余裕がないので、せめてC#の変化にだけはついていこうと思っています。
それで本題なのですが、一対他のリレーションシップがあるEntity AとEntity Bのデーターがあるとします。
class A
{
int ID;
string Data;
EntitySet<B> ListOfB;
}
class B
{
int ID:
int A_ID;
A EntityA;
string Data;
}
Aの集合からそれらの各要素と結びついているすべてのBの集合を作ろうとする場合、どうすればよいか実はわかっていませんでした。力技で
IQuerable<A> AList = db.A.Where(r=> 条件式);
List<B> BList = new List<B>();
foreach (A a in AList)
BList.AddRage(a.ListOfB)
なんてことしてました。これ、AListがせっかくIQueryableなのに、BはLinqToObjectに成り下がってしまっています。Concatすれば一応IQueryableのままですが、コードがややこやしいし、多分かなり非効率なクエリーになってしまうので、AListもToListしちゃった方が速いでしょうね。
で、もしかしていつも無視しているメソッドになんかあるんじゃないかと思って調べてみたらまさしく、Joinを使えばよかったんですね。愛読している究極のC#プログラミングとかC#プログラムの効率的な書き方といった川俣晶さんの本を読み返したらちゃんと説明してあるんだもんな...
言い訳ですが、これらを読んでLINQガンガンに使い始めたんですが、何故かこのころJoinを使わなくてはいけないケースが存在しなくて頭の中に入らなかったようです...
で、Joinをつかえば
IQuerable<A> AList = db.A.Where(r=> 条件式);
IQuerable<B> BList = AList.Join(db.B, x=>x, y=>y.EntityA, (x,y)=> y);
でめでたしめでたしかと思いきや、このクエリーはリレーションしているエンティティそのもので比較していますが、試しに二行目を外部キーで比較したら、
IQuerable<B> BList = AList.Join(db.B, x=>x.ID, y=>y.A_ID, (x,y)=> y);
めちゃくちゃ早くなりました。こういうのってSQL Server をわかってない人間には盲点ですね。型の一致している者同士で引数渡すのが当たり前なC#プログラマーってつい前者の書き方のほうがバグが起こりにくいし参照の比較ならそれほど非効率じゃないじゃんって思いますが、SQL Server の内部ではおそらくそういうリンクの仕方しておらず、外部キー/主キーで比較するのが当たり前の世界なんでしょうね。
まあ、おかけで1件出力するのに1秒以上かかっていたクエリが0.5秒で済むようになりました。今まで書いたプログラムの中にも最初の例のような非効率な書き方をしているところがありそうなので、見直さなきゃならないでしょうね。
私はSQL文は触りしか理解してないので、これらは全部C#からLINQ To SQL経由で処理してます。Viewやらストアドプロシージャーをうまく使えばもっと幅が広がるのでしょうが、日曜プログラマーにはいくつもの言語に精通する時間と余裕がないので、せめてC#の変化にだけはついていこうと思っています。
それで本題なのですが、一対他のリレーションシップがあるEntity AとEntity Bのデーターがあるとします。
class A
{
int ID;
string Data;
EntitySet<B> ListOfB;
}
class B
{
int ID:
int A_ID;
A EntityA;
string Data;
}
Aの集合からそれらの各要素と結びついているすべてのBの集合を作ろうとする場合、どうすればよいか実はわかっていませんでした。力技で
IQuerable<A> AList = db.A.Where(r=> 条件式);
List<B> BList = new List<B>();
foreach (A a in AList)
BList.AddRage(a.ListOfB)
なんてことしてました。これ、AListがせっかくIQueryableなのに、BはLinqToObjectに成り下がってしまっています。Concatすれば一応IQueryableのままですが、コードがややこやしいし、多分かなり非効率なクエリーになってしまうので、AListもToListしちゃった方が速いでしょうね。
で、もしかしていつも無視しているメソッドになんかあるんじゃないかと思って調べてみたらまさしく、Joinを使えばよかったんですね。愛読している究極のC#プログラミングとかC#プログラムの効率的な書き方といった川俣晶さんの本を読み返したらちゃんと説明してあるんだもんな...
言い訳ですが、これらを読んでLINQガンガンに使い始めたんですが、何故かこのころJoinを使わなくてはいけないケースが存在しなくて頭の中に入らなかったようです...
で、Joinをつかえば
IQuerable<A> AList = db.A.Where(r=> 条件式);
IQuerable<B> BList = AList.Join(db.B, x=>x, y=>y.EntityA, (x,y)=> y);
でめでたしめでたしかと思いきや、このクエリーはリレーションしているエンティティそのもので比較していますが、試しに二行目を外部キーで比較したら、
IQuerable<B> BList = AList.Join(db.B, x=>x.ID, y=>y.A_ID, (x,y)=> y);
めちゃくちゃ早くなりました。こういうのってSQL Server をわかってない人間には盲点ですね。型の一致している者同士で引数渡すのが当たり前なC#プログラマーってつい前者の書き方のほうがバグが起こりにくいし参照の比較ならそれほど非効率じゃないじゃんって思いますが、SQL Server の内部ではおそらくそういうリンクの仕方しておらず、外部キー/主キーで比較するのが当たり前の世界なんでしょうね。
まあ、おかけで1件出力するのに1秒以上かかっていたクエリが0.5秒で済むようになりました。今まで書いたプログラムの中にも最初の例のような非効率な書き方をしているところがありそうなので、見直さなきゃならないでしょうね。
2015年4月 8日 水曜日
DC強制降格後のメタデータークリーンアップ
サーバーのふたを閉めたとき、何かが起こったようでOSが吹っ飛びました。起動ドライブのSSDに何かが起こり、ウインドーログマーク以降に進めなくなったので、バックアップからリストアしました。
まず、システムイメージのみの回復を試みましたがうまくいかなかったので、ドライブフォーマットと全ドライブリストアした所、めでたく復旧しました。
しかしこのサーバー上のVMのDCが、サポートされないリストア云々、というエラーでレプリケーションから外されてしまったので、DCから強制降格して、ntdsutil.exeにてメタデーターのクリーンアップをする羽目になりました。そんなに難しくないですが、ごくまれにしか行わないので次におこなうにはやり方を忘れているという感じ(今回4年ぶりぐらいかな)なので、忘備録としてここにメモって置きます。
ntdsutil.exe
ntdsutil: metadata cleanup
metadata cleanup: connections
server connections: connect to server dc1
server connections: quit
metadata cleanup: select operation target
select operation target: list domains
select operation target: select domain 0
select operation target: list site
select operation target: select site 0
select operation target: list servers in site
select operation target: select server 2
select operation target: quit
metadata cleanup: remove selected server
まず、システムイメージのみの回復を試みましたがうまくいかなかったので、ドライブフォーマットと全ドライブリストアした所、めでたく復旧しました。
しかしこのサーバー上のVMのDCが、サポートされないリストア云々、というエラーでレプリケーションから外されてしまったので、DCから強制降格して、ntdsutil.exeにてメタデーターのクリーンアップをする羽目になりました。そんなに難しくないですが、ごくまれにしか行わないので次におこなうにはやり方を忘れているという感じ(今回4年ぶりぐらいかな)なので、忘備録としてここにメモって置きます。
ntdsutil.exe
ntdsutil: metadata cleanup
metadata cleanup: connections
server connections: connect to server dc1
server connections: quit
metadata cleanup: select operation target
select operation target: list domains
select operation target: select domain 0
select operation target: list site
select operation target: select site 0
select operation target: list servers in site
select operation target: select server 2
select operation target: quit
metadata cleanup: remove selected server