C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[6]: LINQ to SQL:


(過去ログ 147 を表示中)

[トピック内 21 記事 (1 - 20 表示)]  << 0 | 1 >>

■85784 / inTopicNo.1)  LINQ to SQL:
  
□投稿者/ ぬる (1回)-(2017/11/24(Fri) 11:31:52)

分類:[.NET 全般] 

お世話になっております。
環境:VS2013 VB.NET






Using db = New SomeDataContext(...)

    db.Connection.Open()
    db.Transaction = db.Connection.BeginTransaction()

    ' Insert
    db.Student.InsertOnSubmit(New Student() With {.Id = 1, .Name = "a"})
    db.SubmitChanges()

    ' Delete
    db.ExecuteCommand("delete from Student where Id = 1")
    db.SubmitChanges()

    '' Delete
    'db.Student.DeleteOnSubmit(db.Student.Single(Function(m) m.Id = 1))
    'db.SubmitChanges()

    ' Insert
    db.Student.InsertOnSubmit(New Student() With {.Id = 1, .Name = "a"})
    db.SubmitChanges()
End Using

引用返信 編集キー/
■85785 / inTopicNo.2)  Re[1]: LINQ to SQL:
□投稿者/ ぬる (2回)-(2017/11/24(Fri) 11:36:27)
2017/11/24(Fri) 12:38:39 編集(投稿者)
2017/11/24(Fri) 11:44:58 編集(投稿者)
2017/11/24(Fri) 11:43:36 編集(投稿者)

すみません、途中で送信されてしまいました。
上記の様に、LINQ to SQLの途中で、ExecuteCommandでDeleteしたときに、
最後のSubmitChangesで
「既に使用されているキーを持つエンティティは追加できません。」
という例外になってしまいます。
ExecuteCommandではなく、LINQ to SQLでDeleteすれば問題ありません。
ExecuteCommandのところは既存コードで触れないので、何とか、ExecuteCommandにしておきたいです。
同じキーを追加、削除(SQL)、追加は何故ダメなのでしょうか。
削除自体は、ExecuteCommandでも、LINQ to SQLでも正常にできていますので、主キー違反ではないはずです。
db.Refresh(...)はいろいろやってみてるのですが結果は変わらずです。
お手数をおかけしますが、ご回答、よろしくお願いします。

引用返信 編集キー/
■85789 / inTopicNo.3)  Re[2]: LINQ to SQL:
□投稿者/ furu (136回)-(2017/11/24(Fri) 15:55:02)
No85785 (ぬる さん) に返信

LINQ to SQLは知りませんが
要は、db.Studentの外でdeleteしているので
ExecuteCommandした後に
db.Studentを読込(select?)し直さなければ
いけない気がします。

そうでなければ、全部ExecuteCommandにするとか
引用返信 編集キー/
■85790 / inTopicNo.4)  Re[3]: LINQ to SQL:
□投稿者/ ぬる (3回)-(2017/11/24(Fri) 16:04:30)
furuさん、ありがとうございます。
その読込という行為がdb.Refresh(...)なのかなと思って、いろいろ引数を変えて試しているのですが変わらない状況です。
やっぱり、ExecuteCommandかLINQ to SQLかどちらかに統一するしかないのか、、、と思って悩んでいるところです。
引用返信 編集キー/
■85796 / inTopicNo.5)  Re[2]: LINQ to SQL:
□投稿者/ WebSurfer (1364回)-(2017/11/25(Sat) 11:09:23)
No85785 (ぬる さん) に返信

自分も Linq to SQL はほとんど知らないのですが・・・

> 上記の様に、LINQ to SQLの途中で、ExecuteCommandでDeleteしたときに、
> 最後のSubmitChangesで
> 「既に使用されているキーを持つエンティティは追加できません。」
> という例外になってしまいます。

その結果から想像できるのは、

(1) 「ExecuteCommandでDeleteしたとき」は実際には SQL Server では Id = 1(主キー?)
  のレコードは DELETE されておらず、その状態で Id = 1 のレコードを INSERT しよう
  として失敗した。

(2) 一方、db.Student.DeleteOnSubmit ⇒ db.SubmitChanges を実行すると、その時点で SQL
  Server で Id = 1 のレコードは DELETE され、次に Id = 1 のレコードを INSERT できた。

・・・ということ以外は思い当たりません。

とすると、原因は DELETE クエリが間違っているか、ExecuteCommand が実行されてないとい
うことになると思われます。そのあたりは確認されたでしょうか?

ただ、コードを見た感じ DELETE クエリは間違ってなさそうですし、ExecuteCommand が実行
されない理由も自分は分からないのですが・・・

#想像を膨らませると、ExecuteCommand に db.SubmitChanges は働かない(DeleteOnSubmit
 で削除マークを付けたエンティティにしか働かない)、そして遅延実行されるとか?
引用返信 編集キー/
■85824 / inTopicNo.6)  Re[3]: LINQ to SQL:
□投稿者/ ぬる (4回)-(2017/11/28(Tue) 09:12:42)
2017/11/28(Tue) 09:13:40 編集(投稿者)

ありがとうございます。
ExecuteCommandのDeleteは間にSelectを入れて正常に削除されていることを確認しています。
Linqでも生SQLでも、Delete→Selectは問題ないです。
データが削除されていないとすればDB側で主キー違反になりますが、
「既に使用されているキーを持つエンティティは追加できません。」というエラーはDB側の主キー違反とは別物だと思います。
引用返信 編集キー/
■85825 / inTopicNo.7)  Re[4]: LINQ to SQL:
□投稿者/ furu (137回)-(2017/11/28(Tue) 09:38:27)
No85824 (ぬる さん) に返信
> 「既に使用されているキーを持つエンティティは追加できません。」というエラーはDB側の主キー違反とは別物だと思います。

DBが主キーを持っていることを忖度し、
db.Studentがdeleteされたことを知らずにエラーを
出しているんだと思います。

例えば、not null制約がある列にDB側のトリガーで
値を設定しているのにDB側に行く前にnot nullで
エラーになったりします。

db.Studentを再読込のような最新にする方法があると思うのですが…
引用返信 編集キー/
■85827 / inTopicNo.8)  Re[4]: LINQ to SQL:
□投稿者/ WebSurfer (1366回)-(2017/11/28(Tue) 12:26:48)
No85824 (ぬる さん) に返信

> ExecuteCommandのDeleteは間にSelectを入れて正常に削除されていることを確認しています。
> Linqでも生SQLでも、Delete→Selectは問題ないです。
> データが削除されていないとすればDB側で主キー違反になりますが、
> 「既に使用されているキーを持つエンティティは追加できません。」というエラーはDB側の主キー違反とは別物だと思います。

ということは、また想像が入ってますが、

(1) Linq to Entity の場合、編集・更新操作を行うには対象となるレコードのエンティティオブジェクトの
  状態を、登録なら Added、編集なら Modified、削除なら Deleted としてマークし、DbContext.SaveChanges
  メソッドを適用します。(これは想像ではありません)

(2) ここからは想像が入っていますが、Linq to SQL でも (1) と同様であろうと思われます。

(3) ところが、ExecuteCommand("delete ...") では当該エンティティに Deleted マークは付かない。なので、
  db.SubmitChanges メソッドは意味がない。でも、SQL Server の当該レコードは削除される。

(4) コンテキスト db は上記 (3) の操作は知らない。当該エンティティの状態は Deleted にはなっていない。
  Unchanged のまま。

(5) その状態で、db.Student.InsertOnSubmit で新たに Id = 1 の Student エンティティをコンテキスト db
  に追加(そのエンティティの状態は Added になる)。

(6) 結果、Id = 1 で Unchanged マークがついたエンティティと、同じく Id = 1 で Added マークがついた
  エンティティの両方がコンテキストに存在することになる。

(7) db.SubmitChangesで「既に使用されているキーを持つエンティティは追加できません」という結果になる。

・・・ということであろうと思われます。(furu さんの No85825 もそのことを言われていると思います)

上記の想像が当たっていれば、以下のようにすればよさそうです。

(a) 上記 (5) で InsertOnSubmit に代えて ExecuteCommand("insert ...") を使う、or

(b) furu さんの言われるようにコンテキストにアタッチされるエンティティを最新のものにしてから
  db.Student.InsertOnSubmit ⇒ db.SubmitChanges を行う。

引用返信 編集キー/
■85828 / inTopicNo.9)  Re[2]: LINQ to SQL:
□投稿者/ 魔界の仮面弁士 (1482回)-(2017/11/28(Tue) 12:28:50)
2017/11/28(Tue) 12:49:13 編集(投稿者)

No85785 (ぬる さん) に返信
> 最後のSubmitChangesで
> 「既に使用されているキーを持つエンティティは追加できません。」
> という例外になってしまいます。

このあたりの処置と調査が手間だったので、個人的には
LINQ to SQL の採用を早々に中止しました…どうするべきなんでしょうね。


> db.Refresh(...)はいろいろやってみてるのですが結果は変わらずです。

Using db As New SomeDataContext(something)
  db.Log = Console.Out

  db.Connection.Open()
  db.Transaction = db.Connection.BeginTransaction() 'TransactionScope なし

  db.Student.InsertOnSubmit(New Student() With {.Id = 1, .Name = "a"})
  db.SubmitChanges() '★1

  ' Delete
  Dim r = db.ExecuteCommand("delete from Student where Id = 1") '戻り値 1
  db.SubmitChanges() '★2

  'db.ClearCache() '☆

  Dim d1 = db.Student.FirstOrDefault(Function(x) x.Id = 1) '★3
  Dim d2 = db.Student.FirstOrDefault(Function(x) x.Id = 2) '★4
  
  ' Insert
  db.Student.InsertOnSubmit(New Student() With {.Id = 1, .Name = "a"})
  db.SubmitChanges() '★5
  
  db.Transaction.Commit()
End Using

この場合、★1、★2、★4 のタイミングで SQL が実行されていることが伺えますが、
★3 では SQL が実行されておらず、d1 IsNot Nothing となってしまいます。

そのため、★5 の段階で DuplicateKeyException が投げ飛ばされています。全力で。


> お手数をおかけしますが、ご回答、よろしくお願いします。

コンテキストを再生成するか、キャッシュをクリアすれば ★5 も通りますが、
私はキャッシュクリアのための正攻法を見つけることができませんでした。


一応、リフレクションを用いて、上記 ☆ を強制的に呼び出すことで
対処できることは経験的に分かっていますが、副作用については
調査しきれていないので、動作保証はできません。

https://referencesource.microsoft.com/#System.Data.Linq/DataContext.cs#194


Imports System.Reflection
Friend Module ContextExtensions
 <System.Runtime.CompilerServices.Extension()>
 Friend Sub ClearCache(ByVal this As System.Data.Linq.DataContext)
  Dim f = BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic
  this.GetType().GetMethod("ClearCache", f).Invoke(this, Nothing)
 End Sub
End Module
引用返信 編集キー/
■85831 / inTopicNo.10)  Re[4]: LINQ to SQL:
□投稿者/ WebSurfer (1368回)-(2017/11/28(Tue) 14:36:01)
No85824 (ぬる さん) に返信

想像するばかりでは何なので No85827 で書いたことが正しいかどうか実際にコードを
書いて検証してみました。

DataContext にアタッチされたエンティティの状態を調べる方法が分からなかったので、それは調べ
ていませんが、 No85827 で書いた、

(a) 上記 (5) で InsertOnSubmit に代えて ExecuteCommand("insert ...") を使う、or

(b) furu さんの言われるようにコンテキストにアタッチされるエンティティを最新のものにしてから
  db.Student.InsertOnSubmit ⇒ db.SubmitChanges を行う。

・・・で期待通りの結果になることは確認できました。なので、No85827 で書いた想像は当たっている
のではないかと思います。

ちなみに検証に使ったコードは以下の通りです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleAppLinqToSQL
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new DataClasses1DataContext())
            {
                var query = from item in db.Table
                            select item;

                foreach (Table table in query)
                {
                    Console.WriteLine("Id={0}, Name={1}, Price={2}", table.Id, table.Name, table.Price);
                }

                db.Table.InsertOnSubmit(new Table() { Id = 15, Name = "ABCDEF", Price = 100 });
                db.SubmitChanges();

                db.ExecuteCommand("delete from [Table] where Id = 15");
                // 以下のコードは意味なし ⇒ コメントアウト
                //db.SubmitChanges();

                // これは SubmitChanges でエラー
                //db.Table.InsertOnSubmit(new Table() { Id = 15, Name = "ACB", Price = 100 });
                //db.SubmitChanges();

                // これは OK
                db.ExecuteCommand("insert into [Table] (Id,Name,Price) values (15,'newABC',200)");
            }

            // 上の db.ExecuteCommand("insert ..."); に代えて以下のようにしても OK 
            //using (var db = new DataClasses1DataContext())
            //{
            //    db.Table.InsertOnSubmit(new Table() { Id = 15, Name = "newABC", Price = 200 });
            //    db.SubmitChanges();
            //}
        }
    }
}

引用返信 編集キー/
■85905 / inTopicNo.11)  Re[3]: LINQ to SQL:
□投稿者/ ぬる (5回)-(2017/11/30(Thu) 21:46:09)
魔界の仮面弁士さん
返信が遅れまして申し訳ありませんでした。
ご回答ありがとうございました。
ClearCacheはInternalなんですね。
Refreshと似たことを一部やっているようですが。
確かにLINQ to SQLの採用自体を考えるようになってきました。
Entity Frameworkも多分一緒ですよね。。。。
詳細な調査をしていただき、本当にありがとうございました。
引用返信 編集キー/
■85906 / inTopicNo.12)  Re[5]: LINQ to SQL:
□投稿者/ ぬる (6回)-(2017/11/30(Thu) 21:46:51)
WebSurferさん
返信が遅れまして申し訳ありませんでした。
ご回答ありがとうございました。

(b)はトランザクションが切れてしまいますので、仕様的にNGとなります。
なんとかトランザクションを切らない方法がないかどうかを探しております。

(a)の解決法では魔界の仮面弁士さんのご意見のように、
結局LINQ to SQLの採用自体を今後は見合わせることになりそうです。

詳細な調査をしていただき、本当にありがとうございました。
引用返信 編集キー/
■85908 / inTopicNo.13)  Re[6]: LINQ to SQL:
□投稿者/ WebSurfer (1387回)-(2017/11/30(Thu) 22:01:44)
No85906 (ぬる さん) に返信

> (b)はトランザクションが切れてしまいますので、仕様的にNGとなります。

あれ? 最初の質問にあった、

> ' Delete
> db.ExecuteCommand("delete from Student where Id = 1")

はトランザクションに束ねられてないような気がしますけど、どうでしょう?


引用返信 編集キー/
■85910 / inTopicNo.14)  Re[7]: LINQ to SQL:
□投稿者/ ぬる (7回)-(2017/11/30(Thu) 23:56:17)
No85908 (WebSurfer さん) に返信
> ■No85906 (ぬる さん) に返信
>
>>(b)はトランザクションが切れてしまいますので、仕様的にNGとなります。
>
> あれ? 最初の質問にあった、
>
>>' Delete
>>db.ExecuteCommand("delete from Student where Id = 1")
>
> はトランザクションに束ねられてないような気がしますけど、どうでしょう?
>
>

そそ、そうなんでしょうか汗・・・
最初の質問に書いたコードは上から下まで1トランの認識でした。
ExecuteCommandは別トランになるんでしょうか。。。

引用返信 編集キー/
■85911 / inTopicNo.15)  Re[8]: LINQ to SQL:
□投稿者/ WebSurfer (1389回)-(2017/12/01(Fri) 00:17:02)
No85910 (ぬる さん) に返信

> ExecuteCommandは別トランになるんでしょうか。。。

そのメソッドで即クエリが実行されて DB に結果が反映されるということから考えて
そういう気がするだけです。確証はないですが、もし質問者さんの方でも曖昧という
ことでしたら、検証して結果を教えていただけると幸いです。

引用返信 編集キー/
■85913 / inTopicNo.16)  Re[9]: LINQ to SQL:
□投稿者/ ぬる (8回)-(2017/12/01(Fri) 09:21:06)
No85911 (WebSurfer さん) に返信
> ■No85910 (ぬる さん) に返信
>
>>ExecuteCommandは別トランになるんでしょうか。。。
>
> そのメソッドで即クエリが実行されて DB に結果が反映されるということから考えて
> そういう気がするだけです。確証はないですが、もし質問者さんの方でも曖昧という
> ことでしたら、検証して結果を教えていただけると幸いです。
>

以下の通り、ExecuteCommandの戻り値で確認しました。
一回目のDeleteの戻り値はゼロ、二回目は1でした。
Delete → Insert → Delete は1トランでできていると思います。

Using db = New SomeDataContext()

db.Connection.Open()
db.Transaction = db.Connection.BeginTransaction()

' 0
Dim count = db.ExecuteCommand("delete from Student where Id = 1")

' Insert
db.Student.InsertOnSubmit(New Student() With {.Id = 1, .Name = "a"})
db.SubmitChanges()

' 1
count = db.ExecuteCommand("delete from Student where Id = 1")

End Using

引用返信 編集キー/
■85914 / inTopicNo.17)  Re[10]: LINQ to SQL:
□投稿者/ 魔界の仮面弁士 (1488回)-(2017/12/01(Fri) 10:04:29)
No85913 (ぬる さん) に返信
> 以下の通り、ExecuteCommandの戻り値で確認しました。

別のアプリなどから Id = 1 の行を取得してみて、
  取得できる or 何も返されない or ロック状態になる
で判断する方法もありますね。

最初のコードは、Transaction オブジェクトを
Commit していないので、サーバーには反映されていないはず。


でもって、分散トランザクション コーディネータ (MS DTC と OraMTS)が
インストール済みであれば、System.Transactions.dll を参照追加して、
TransactionScope で囲む手も使えるかと思います。
手元の環境には DTC を入れてないので未確認ですが。

(TransactionScope を使う場合、Using/using ブロックの外側で
 Connection を開いた場合、トランザクションには参加されず
 即時コミットされることになるので注意)


'VB
Using tx As New TransactionScope()
 Using db1 As New SomeDataContext(何某)
 End Using
 Using db2 As New SomeDataContext(何某)
 End Using
 tx.Complete()
End Using


// C#
using (var tx = new TransactionScope())
{
 using (var db1 = new SomeDataContext(何某))
 {
 }
 using (var db1 = new SomeDataContext(何某))
 {
 }
 tx.Complete();
}
引用返信 編集キー/
■85916 / inTopicNo.18)  Re[10]: LINQ to SQL:
□投稿者/ WebSurfer (1390回)-(2017/12/01(Fri) 11:35:20)
No85913 (ぬる さん) に返信

検証結果の連絡をありがとうございました。

自分でもちょっと調べてみましたが、以下の記事に書いてあるように Linq to SQL は ADO.NET 上で動いていて、
その記事の「接続」のセクションに書いてあるように、ADO.NET コマンドと DataContext の間で接続を共有でき
るようです。

ADO.NET and LINQ to SQLADO.NET および LINQ to SQL
https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/sql/linq/ado-net-and-linq-to-sql

なので、最初の質問にあった、

> ' Delete
> db.ExecuteCommand("delete from Student where Id = 1")

もトランザクションに束ねることはできるということのようですね。

ADO.NET コマンドと DataContext の間で接続を共有できるのであれば、異なる DataContext の間でも接続を
共有できるのではないかと思って調べてみました。

一応以下のコードで可能でした。(ホントにこれでいいのか自信はありません。どこかに見落としがあるかも。
分散トランザクションが使えるならそちらの方が良いと思います)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;

namespace ConsoleAppLinqToSQL
{
    class Program
    {
        static void Main(string[] args)
        {
            string connString = ConsoleAppLinqToSQL.Properties.Settings.Default.TestDatabaseConnectionString;
            SqlConnection connection = new SqlConnection(connString);

            try
            {
                connection.Open();
                SqlTransaction sqltx = connection.BeginTransaction();

                using (var db = new DataClasses1DataContext(connection))
                {
                    db.Transaction = sqltx;

                    db.Table.InsertOnSubmit(new Table() { Id = 15, Name = "ABCDEF", Price = 100 });
                    db.SubmitChanges();

                    db.ExecuteCommand("delete from [Table] where Id = 15");
                }

                using (var db = new DataClasses1DataContext(connection))
                {
                    db.Transaction = sqltx;

                    db.Table.InsertOnSubmit(new Table() { Id = 15, Name = "newABC", Price = 200 });
                    db.SubmitChanges();
                }

                // Commit するまでは SSMS から Table を読めない(ロックされている?)
                // Commit されると SSMS から Table を読める。上の最後の InsertOnSubmit が反映されている
                // コメントアウトすると Close で Rollback され、上記の結果は SQL Server には反映されない
                sqltx.Commit();
            }
            finally
            {
                // 未コミット状態のトランザクションは Close でロールバックされるので
                // 明示的に Rollback をかける必要はない
                connection.Close();
            }
        }
    }
}

引用返信 編集キー/
■85936 / inTopicNo.19)  Re[4]: LINQ to SQL:
□投稿者/ ぬる (9回)-(2017/12/04(Mon) 13:46:34)
魔界の仮面弁士さん, WebSurferさん
ご回答ありがとうございました。
異なるコンテキストでトランザクションの共有が可能なんですね。
これについてじっくり調査させていただいて、うまくいけばこれで行きたいと思います。
本当にありがとうございました!
解決済み
引用返信 編集キー/
■85952 / inTopicNo.20)  Re[5]: LINQ to SQL:
 
□投稿者/ WebSurfer (1395回)-(2017/12/05(Tue) 13:12:49)
No85936 (ぬる さん) に返信

> 異なるコンテキストでトランザクションの共有が可能なんですね。
> これについてじっくり調査させていただいて、うまくいけばこれで行きたいと思います。

私が No85916 で書いたコードは「マニュアルトランザクション」です。(トランザクションをデー
タベース接続の内側に作成し、各 SqlCommand オブジェクトをそのトランザクションに参加させる)

お勧めは、 No85916 で紹介した記事や魔界の仮面弁士さんの No85914 にある「分散(自動)トラ
ンザクション」です。(データベース接続の外側からトランザクションを定義できるようにする機
能)

そのあたり理解されているでしょうか?

SQL Server なら管理ツールの「サービス」で「Distributed Transaction Coordinator」を「開始」
にすれば使えるようになると思いますのでお試しください。
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -