13

コンテキストを直接操作するのは良い考えですか?たとえば、顧客のデータベースがあり、ユーザーが名前で顧客を検索し、リストを表示し、リストを選択して、その顧客のプロパティを編集できるとします。

コンテキストを使用して顧客のリスト(POCOまたはにマップされているCustomerViewModels)を取得し、すぐにコンテキストを閉じる必要があるようです。次に、ユーザーがCustomerViewModelsリストから1つを選択すると、UIの顧客プロパティセクションにデータが入力されます。

次に、名前、タイプ、Webサイトのアドレス、会社の規模などを変更できます。保存ボタンを押すと、新しいコンテキストを開き、からのIDを使用しCustomerViewModelてその顧客レコードを取得し、各プロパティを更新します。最後にSaveChanges()、コンテキストを呼び出して閉じます。これはたくさんの仕事です。

私の質問は、コンテキストを直接操作して、コンテキストを開いたままにしないのはなぜですか?ライフタイムスコープが長い同じコンテキストを使用して読んだことがありますが、これは非常に悪いことであり、必然的に問題が発生します。私の想定では、アプリケーションを1人だけが使用する場合は、コンテキストを開いたままにして、すべてを実行できます。ただし、ユーザーが多い場合は、簡潔な作業単位を維持して、リクエストごとにコンテキストを開いたり閉じたりしたいと思います。

助言がありますか?ありがとう。


@PGallagher-徹底的な回答をありがとう。
@Brice-あなたの入力も役に立ちます

ただし、@ManosD.の「冗長コードの縮図」コメントは少し気になります。例を見てみましょう。顧客をデータベースに保存していて、顧客のプロパティの1つがCommunicationMethodであるとします。

[Flags]
public enum CommunicationMethod
{
    None = 0,
    Print = 1,
    Email = 2,
    Fax = 4
}

WPFの顧客管理ページのUIには、顧客コミュニケーション方法(印刷、電子メール、ファックス)の下に3つのチェックボックスが含まれます。各チェックボックスをその列挙型にバインドすることはできません。意味がありません。また、ユーザーがその顧客をクリックし、起きて昼食に行くとどうなりますか...コンテキストは何時間もそこにとどまりますが、これは悪いことです。代わりに、これは私の思考プロセスです。

エンドユーザーはリストから顧客を選択します。コンテキストを新しくし、その顧客を見つけてCustomerViewModelを返すと、コンテキストが閉じられます(ここでは簡単にするためにリポジトリを省略しました)。

using(MyContext ctx = new MyContext())
{
    CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId));
}

これで、ユーザーは、Save()メソッドもあるCustomerViewModelの3つのboolプロパティにバインドされているため、[印刷]、[電子メール]、[ファックス]ボタンをオン/オフにできます。ここに行きます。

public class CustomerViewModel : ViewModelBase
{
    Customer _customer;

    public CustomerViewModel(Customer customer)
    {
        _customer = customer;
    }


    public bool CommunicateViaEmail
    {
        get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email); }
        set
        {
            if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email)) return;

            if (value)
                _customer.CommunicationMethod |= CommunicationMethod.Email;
            else
                _customer.CommunicationMethod &= ~CommunicationMethod.Email;
        }
    }
    public bool CommunicateViaFax
    {
        get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax); }
        set
        {
            if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax)) return;

            if (value)
                _customer.CommunicationMethod |= CommunicationMethod.Fax;
            else
                _customer.CommunicationMethod &= ~CommunicationMethod.Fax;
        }
    }
    public bool CommunicateViaPrint
    {
        get { return _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print); }
        set
        {
            if (value == _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print)) return;

            if (value)
                _customer.CommunicateViaPrint |= CommunicationMethod.Print;
            else
                _customer.CommunicateViaPrint &= ~CommunicationMethod.Print;
        }
    }

    public void Save()
    {
        using (MyContext ctx = new MyContext())
        {
            var toUpdate = ctx.Customers.Find(_customer.Id);
            toUpdate.CommunicateViaEmail = _customer.CommunicateViaEmail;
            toUpdate.CommunicateViaFax = _customer.CommunicateViaFax;
            toUpdate.CommunicateViaPrint = _customer.CommunicateViaPrint;

            ctx.SaveChanges();
        }
    }
}

これに何か問題がありますか?

4

3 に答える 3

17

長期実行コンテキストを使用しても問題ありません。その影響に注意する必要があります。

コンテキストは作業単位を表します。SaveChanges を呼び出すたびに、追跡されているエンティティに対するすべての保留中の変更がデータベースに保存されます。このため、各コンテキストを意味のある範囲に限定する必要があります。たとえば、顧客を管理するタブと製品を管理する別のタブがある場合、それぞれに 1 つのコンテキストを使用して、ユーザーが顧客タブで [保存] をクリックしたときに、製品に加えたすべての変更が保存されないようにすることができます。

コンテキストによって追跡されるエンティティが多数あると、DetectChanges が遅くなる可能性もあります。これを軽減する 1 つの方法は、変更追跡プロキシを使用することです。

エンティティをロードしてからそのエンティティを保存するまでの時間は非常に長くなる可能性があるため、楽観的同時実行例外が発生する可能性は、存続期間が短いコンテキストの場合よりも高くなります。これらの例外は、エンティティの読み込みと保存の間にエンティティが外部から変更された場合に発生します。これらの例外の処理は非常に簡単ですが、それでも注意が必要です。

WPF で有効期間の長いコンテキストを使用して実行できる優れた方法の 1 つは、DbSet.Local プロパティ (context.Customers.Local など) にバインドすることです。これは、削除対象としてマークされていない追跡対象のすべてのエンティティを含む ObservableCollection です。

これにより、どのアプローチを支援するかを決定するのに役立つ情報が得られることを願っています.

于 2013-02-21T23:45:38.947 に答える
3

マイクロソフトのリファレンス:

http://msdn.microsoft.com/en-gb/library/cc853327.aspx

彼らが言うには;

ObjectContext のスコープを制限する

ほとんどの場合、using ステートメント内で ObjectContext インスタンスを作成する必要があります (Visual Basic では Using…End Using)。

これにより、コードがステートメント ブロックを終了するときに、オブジェクト コンテキストに関連付けられたリソースが自動的に破棄されるようになるため、パフォーマンスが向上します。

ただし、オブジェクト コンテキストによって管理されるオブジェクトにコントロールがバインドされている場合は、バインドが必要であり、手動で破棄される限り、ObjectContext インスタンスを維持する必要があります。

詳細については、「オブジェクト サービスでのリソースの管理 (Entity Framework)」を参照してください。http://msdn.microsoft.com/en-gb/library/bb896325.aspx

つまり、

実行時間の長いオブジェクト コンテキストでは、不要になったときにコンテキストを確実に破棄する必要があります。


StackOverflow リファレンス:

この StackOverflow の質問には、いくつかの役立つ回答もあります...

ビジネスロジックにおけるEntity Frameworkのベストプラクティス?

コンテキストをより高いレベルに昇格させ、ここから参照することを提案する人が何人かいます。したがって、単一のコンテキストのみを保持します。


私の 10 ペンスの価値:

コンテキストを Using ステートメントでラップすると、ガベージ コレクターがリソースをクリーンアップできるようになり、メモリ リークが防止されます。

明らかに単純なアプリでは、これは大した問題ではありませんが、複数の画面があり、すべてが大量のデータを使用している場合、Context を正しく Dispose しない限り、問題が発生する可能性があります。

したがって、私はあなたが言及した方法と同様の方法を採用しました。ここではAddOrUpdate、各リポジトリにメソッドを追加し、そこで新しいエンティティまたは変更されたエンティティを渡し、それが存在するかどうかに応じて更新または追加します。


エンティティ プロパティの更新:

ただし、プロパティの更新に関しては、リフレクションを使用してすべてのプロパティをあるエンティティから別のエンティティにコピーする単純な関数を使用しました。

Public Shared Function CopyProperties(Of sourceType As {Class, New}, targetType As {Class, New})(ByVal source As sourceType, ByVal target As targetType) As targetType
    Dim sourceProperties() As PropertyInfo = source.GetType().GetProperties()
    Dim targetProperties() As PropertyInfo = GetType(targetType).GetProperties()

    For Each sourceProp As PropertyInfo In sourceProperties
        For Each targetProp As PropertyInfo In targetProperties
            If sourceProp.Name <> targetProp.Name Then Continue For

            ' Only try to set property when able to read the source and write the target
            '
            ' *** Note: We are checking for Entity Types by Checking for the PropertyType to Start with either a Collection or a Member of the Context Namespace!
            '
            If sourceProp.CanRead And _
                  targetProp.CanWrite Then
                ' We want to leave System types alone
                If sourceProp.PropertyType.FullName.StartsWith("System.Collections") Or (sourceProp.PropertyType.IsClass And _
                       sourceProp.PropertyType.FullName.StartsWith("System.Collections")) Or sourceProp.PropertyType.FullName.StartsWith("MyContextNameSpace.") Then
                    '
                    ' Do Not Store
                    '
                Else

                    Try

                        targetProp.SetValue(target, sourceProp.GetValue(source, Nothing), Nothing)

                    Catch ex As Exception

                    End Try

                End If
            End If

            Exit For
        Next
    Next

    Return target
End Function

私が何かをするところ;

dbColour = Classes.clsHelpers.CopyProperties(Of Colour, Colour)(RecordToSave, dbColour)

もちろん、これにより、リポジトリごとに記述する必要があるコードの量が削減されます。

于 2013-02-22T00:06:01.347 に答える
0

コンテキストはデータベースに永続的に接続されていません。これは基本的に、ディスクからロードしたレコードのメモリ内キャッシュです。以前にロードされていないレコードをリクエストした場合、強制的に更新した場合、または変更をディスクに保存している場合にのみ、データベースからレコードをリクエストします。

コンテキストを開き、レコードを取得し、コンテキストを閉じてから、変更されたプロパティを新しいコンテキストからオブジェクトにコピーすることは、冗長コードの典型です。元のコンテキストをそのままにして、それを使用して SaveChanges() を実行することになっています。

同時実行の問題に対処する場合は、エンティティ フレームワークのバージョンの「同時実行の処理」について Google 検索を行う必要があります。

例として、これを見つけまし

コメントに応じて編集:

私が理解していることから、レコードの列のサブセットを新しい値で上書きし、残りは影響を受けないようにする必要がありますか? そうであれば、「新しい」オブジェクトでこれらのいくつかの列を手動で更新する必要があります。

顧客オブジェクトのすべてのフィールドを反映し、顧客レコード全体への編集アクセスを提供することを意図したフォームについて話しているという印象を受けました。この場合、新しいコンテキストを使用してすべてのプロパティを 1 つずつ慎重にコピーしても意味がありません。これは、最終結果 (年齢に関係なく、フォームの値で上書きされたすべてのデータ) が同じになるためです。

于 2013-02-21T23:50:21.500 に答える