7

私はEntityFrameworkを初めて使用し、CodeFirstを使用してデータベースからエンティティをロードする方法を学習しようとしています。

私のモデルにはユーザーが含まれています:

public class User
{
    public int UserID { get; set; }

    [Required]
    public string Name { get; set; }

    // Navigation Properties
    public virtual ICollection<AuditEntry> AuditEntries { get; set; }
}

各ユーザーは、それぞれに簡単なメッセージを含む一連の監査エントリを持つことができます。

public class AuditEntry
{
    public int AuditEntryID { get; set; }

    [Required]
    public string Message { get; set; }

    // Navigation Properties
    public int UserID { get; set; }
    public virtual User User { get; set; }
}

2つのテーブルを公開するだけのDBContextがあります。

public DbSet<User> Users { get; set; }
public DbSet<AuditEntry> AuditEntries { get; set; }

私がやりたいのは、メッセージを含むAuditEntryオブジェクトのリストと、UserIDおよびNameプロパティを含む関連するUserオブジェクトをロードすることです。

List<AuditEntry> auditEntries = db.AuditEntries.ToList();

ナビゲーションプロパティを仮想としてマークし、遅延読み込みを無効にしていないため、無限に深いオブジェクトグラフが表示されます(各AuditEntryにはAuditEntriesのリストを含むUserオブジェクトがあり、各オブジェクトにはUserオブジェクトが含まれています。 AuditEntriesなどのリストが含まれています)

オブジェクトをシリアル化する場合(たとえば、結果としてWeb APIで送信する場合)、これは適切ではありません。

遅延読み込みをオフにしてみました(モデルのナビゲーションプロパティから仮想キーワードを削除するか、this.Configuration.LazyLoadingEnabled = false;をDBContextに追加します)。予想どおり、これにより、Userがnullに設定されたAuditEntryオブジェクトのフラットリストが作成されます。

遅延読み込みをオフにして、次のようにユーザーの読み込みを熱心に試みました。

var auditentries = db.AuditEntries.Include(a => a.User);

しかし、これは以前と同じ深い/周期的な結果になります。

後方参照をロードしたり、ナビゲーションプロパティを元のオブジェクトに戻したり、サイクルを作成したりせずに、1レベルの深さ(たとえば、ユーザーのIDと名前を含める)をロードするにはどうすればよいですか?

4

1 に答える 1

3

多くのハッキングの後、Linqクエリで動的リターンタイプとプロジェクションを使用して、次の解決策を考え出しました。

public dynamic GetAuditEntries()
{
    var result = from a in db.AuditEntries
                 select new
                 {
                     a.AuditEntryID,
                     a.Message,
                     User = new
                     {
                         a.User.UserID,
                         a.User.Username
                     }
                 };

    return result;
}

これにより、(内部的に)次のSQLが生成されます。

SELECT 
[Extent1].[AuditEntryID] AS [AuditEntryID], 
[Extent1].[Message] AS [Message], 
[Extent1].[UserID] AS [UserID], 
[Extent2].[Username] AS [Username]
FROM  [dbo].[AuditEntries] AS [Extent1]
INNER JOIN [dbo].[Users] AS [Extent2] ON [Extent1].[UserID] = [Extent2].[UserID]

これにより、私が求めている結果が得られますが、少し時間がかかるようです(特に、私の例よりもはるかに複雑な実際のモデルの場合)。これがパフォーマンスに与える影響については疑問があります。

利点

  • これにより、返されたオブジェクトの正確な内容に対して多くの柔軟性が得られます。私は通常、ほとんどのUIインタラクション/テンプレートをクライアント側で行うため、モデルオブジェクトの複数のバージョンを作成しなければならないことがよくあります。通常、ユーザーがどのプロパティを確認できるかについて、特定の粒度が必要です(たとえば、AJAXリクエストですべてのユーザーの電子メールアドレスを低特権ユーザーのブラウザーに送信したくない場合があります)。

  • これにより、エンティティフレームワークはクエリをインテリジェントに構築し、投影するために選択したフィールドのみを選択できます。たとえば、各トップレベルのAuditEntryオブジェクト内に、User.UserIDとUser.Usernameを表示したいが、User.AuditEntriesは表示したくない。

短所

  • Web APIから返された型は厳密に型指定されなくなったため、このAPIに基づいて厳密に型指定されたMVCビューを作成できませんでした。たまたま、これは私の特定のケースでは問題ではありません。

  • このように大規模で複雑なモデルから手動で投影すると、多くのコードが生成される可能性があり、多くの作業のように見え、APIにエラーが発生する可能性があります。これは注意深くテストする必要があります。

  • APIメソッドはモデルの構造と緊密に結合され、POCOクラスに基づいて完全に自動化されなくなったため、モデルに加えられた変更は、それらをロードするコードに反映する必要があります。

メソッドを含めますか?

.Include()メソッドの使用についてはまだ少し混乱しています。このメソッドは、関連するエンティティが指定されたエンティティとともに「熱心にロード」される必要があることを指定することを理解しています。ただし、ナビゲーションプロパティはリレーションシップの両側に配置し、仮想としてマークする必要があるというガイダンスのように思われるため、Includeメソッドを使用すると、その有用性に重大な悪影響を与えるサイクルが作成されるようです(特にシリアル化の場合)。 。

私の場合、「ツリー」は次のようになります。

AuditEntry
    User
        AuditEntries * n
            User * n
                etc

このアプローチ、この方法でダイナミックを使用することの影響、またはその他の洞察についてのコメントを聞いて非常に興味があります。

于 2012-11-20T13:10:50.443 に答える