3


2

単一のクエリでのLinq-to-SQLロード1:1リレーション

迅速な結合のために複数の1:1関係を持つ複数のクラスがあり、これは表形式表示の匿名型ではうまく機能しますが、単一のlinqクエリで型を完全に設定する方法がわかりません。

これらのプロパティは、オフ1:1であるか、子コレクションを介してクエリを実行してディスプレイごとに「プライマリ」を見つける必要がないため、保存時にこれらのプライマリIDを設定することでコストが発生します。

この投稿のコンテキストの簡略化された例:

public class Contact
{
  public long Id { get; set; }

  public EntitySet Addresses { get; set; }
  public EntityRef PrimaryAddress { get; set; }
  public long? PrimaryAddressId { get; set; }

  public EntitySet Emails { get; set; }
  public EntityRef PrimaryEmail { get; set; }
  public long? PrimaryEmailId { get; set; }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

public class Address
{
  public long Id { get; set; }
  public EntitySet Contacts { get; set; }

  public bool IsPrimary { get; set; }
  public string Street1 { get; set; }
  public string Street2 { get; set; }
  public string City { get; set; }
  public string State { get; set; }
  public string Country { get; set; }
}

public class Email
{
  public long Id { get; set; }
  public EntitySet Contacts { get; set; }

  public bool IsPrimary { get; set; }
  public string Address { get; set; }
}

問題は、連絡先のリストを表示するときに、「PrimaryAddress」と「PrimaryEmail」を遅延ロードする必要があることです。 `DataLoadOptions`を実行すると、1:1の例なので望ましい効果が得られません。例:

var DB = new DataContext();
var dlo = new DataLoadOptions();
dlo.LoadWith(c => c.PrimaryAddress);
dlo.LoadWith(c => c.PrimaryEmail);
DB.LoadOptions = dlo;

var result = from c in DB.Contacts select c;
result.ToList();

上記のコードは* INNER JOIN *になります。これは、親関係のようにそれを扱い、null許容のFK関係を尊重せず、1:1プロパティに左結合します。 目的のクエリは次のようになります。

Select t1.*, t.2*, t3.*
From Contact t1
Left Join Address t2 On t1.PrimayAddressId = t2.Id
Left Join Email On t1.PrimaryEmailId = t3.Id

これを行い、これらのnull可能な1:1プロパティが設定されたIQueryable、またはリストを取得する方法はありますか? 他の制約のため、型は「Contact」にする必要があります。そのため、匿名型は機能しません。 オプションにはかなりオープンです。表示する行の数に対してn *(number of 1:1s)+1クエリを遅延ロードするよりも優れているでしょう。

4 Answer


2


_Update:_最後にこれを更新しました。devartの人たちは後のバージョンの動作を修正して完全に動作するようにしました。 テーブルのフィールドを使用するだけで、 `DataLoadOptions`はまったく必要ありません。例えば:

var DB = new DataContext();
var result = from c in DB.Contacts
             select new {
               c.Id
               c.FirstName,
               c.LastName,
               Address = c.PrimaryAddress.Street1 + " " + c.PrimaryAddress.Street2 //...
               Email = c.PrimaryEmail.Address
             };

これにより、関連する「Address」テーブルと「Email」テーブルへの単一の左外部結合が正しく実行されます。 修正は、この匿名型を取得するという状況に固有のものです…​しかし、それらは、必要な「DataLoadOptions」の動作も修正し、外部キー型を正しくキーオフします。 このアップデートが古いバージョンの他のユーザーに役立つことを願っています…​アップグレードすることを強くお勧めします。5.35以降のバージョンには多くの新しい拡張機能があります(多くの人が楽になります)。

'' '' '

Original: +私たちがやったことは別のアプローチでした。 これは* http://www.devart.com/dotconnect/oracle/ [devart:dotConnect for Oracle] *プロバイダーに固有の動作である可能性があります(バージョン5.35.62の時点で、この動作が変更された場合、これを試して更新します)質問)。

var DB = new DataContext();
var result = from c in DB.Contacts
             select new {
               c.Id
               c.FirstName,
               c.LastName,
               Address = new AddressLite {
                               Street1 = c.PrimaryAddress.Street1,
                               Street2 = c.PrimaryAddress.Street2,
                               City = c.PrimaryAddress.City,
                               State = go.PrimaryAddress.State,
                               Country = go.PrimaryAddress.Country },
               Email = c.PrimaryEmail.Address
             };
result.ToList();

これにより、単一のクエリが生成されます。 selectで子オブジェクトを呼び出している間、例えば 「c.PrimaryAddress」は、結合を発生させません(多くの「select …​ id = n`のアドレスからの遅延読み込み、表示している表形式データの行ごとに1つ)、ただし、そのプロパティを呼び出します。 c.PrimaryAddress.Street1 * DOES *は、クエリクエリのアドレステーブルに正しい左結合を引き起こします。 上記のlinqはlinq-to-sqlでのみ機能します。linq-to-entitiesのnull参照で失敗しますが、…​私たちが扱っている場合は問題ありません。

'' '' '

いいもの:

  • アドレスと電子メールへの左結合を生成する単一クエリ

  • アドレス用の軽量オブジェクトとメール用の単なる文字列 (両方とも実際のプロジェクトでEntiySetの後方参照を持っているため、単純な表形式表示のニーズに必要なものよりも高価になります)

  • 高速/クリーン、上記は手動で参加するよりもはるかに簡単なクエリです 実行していたすべての子テーブル、よりクリーンなコード。

  • パフォーマンス、より重いオブジェクトの作成は大ヒットでしたが、 電子メールから文字列、住所からAddressLite、および(プロジェクト全体で)電話からPhoneLiteに変更すると、300〜500ミリ秒から50〜100ミリ秒の表形式のデータがページに表示されます。

悪い人:

  • 匿名型、強い型が必要な場合があります。 それらを作成すると(ReSharperがこのタスクを実行するのと同じくらい迅速に)多くの混乱が生じます。

  • 匿名型または任意の型を変更して保存することはできないため、 アノテーション作業をあまり行わずに作成します。モデルがその周りで何かを変更した場合は更新する必要があります。 (これらのクラスは生成されないため)


1


DataLoadOptions、遅延読み込み、およびプライマリレコードで同じ問題に遭遇しました。

正直に言うと、私が思いついた解決策に完全に満足しているわけではありません。正確ではないので、作成するSQLクエリは複雑になる可能性がありますが、基本的には、サブクエリを使用してレコードをロードしました。 上記の例では:

public class ContactWithPrimary
{
    public Contact Contact { get; set; }
    public Email PrimaryEmail { get; set; }
    public Address PrimaryAddress { get; set; }
}

LINQクエリの例は次のとおりです。

List Contacts = DataContext.Contacts
    .Select(con => new ContactWithPrimary
    {
        Contact = con,
        PrimaryEmail = con.PrimaryEmail,
        PrimaryAddress = con.PrimaryAddress
    }).ToList();

ただし、1つのクエリでそれを引き出します。


1


EntityRef型のプロパティの関連付け属性でIsForeignKeyがfalseに設定されている場合、左結合が生成されます。


0


Rob ConeryのLazy Listの実装をご覧ください。

基本的に、遅延読み込みの実装全体がユーザーから見えなくなり、読み込みオプションを指定する必要はありません。

唯一の欠点は、リストに対してのみ機能することです。 ただし、プロパティの実装を記述することもできます。 これが私の努力です。

public class LazyProperty where TEntityType : class
{
    private readonly IQueryable source;
    private bool loaded;
    private TEntityType entity;

    public LazyProperty()
    {
        loaded = true;
    }

    public LazyProperty(IQueryable source)
    {
        this.source = source;
    }

    public TEntityType Entity
    {
        get
        {
            if (!loaded)
            {
                entity = source.SingleOrDefault();
                loaded = true;
            }
            return entity;
        }
        set
        {
            entity = value;
            loaded = true;
        }
    }
}