2016-07-16 4 views
3

のそれぞれをソートする:私はすべてのコンピュータを問い合わせる場合NHibernateはQueryOverはこの不自然なドメインを検討一対多のコレクション

namespace TryHibernate.Example { 

public class Computer 
{ 
    public int Id { get; set; } 
    public IList<Device> Devices { get; set; } 
} 

public class Device 
{ 
    public int Id { get; set; } 
    public Maker Maker { get; set; } 
} 

public class Maker 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

} // namespace 

を、そのデバイスがランダムに並べられます。私はそれらをメーカーの名前で注文したいと思います。私が実際にHasMany().OrderBy()とすることはできません。OrderByはローカル列しか使用できないので(例えば、Device.Idでソートすることができます)。さらに、クエリごとに順序を制御するのがいいですから、QueryOverソリューションを探しています。

私が得ることができる最も遠いが、このでした:

using (ISessionFactory sessionFactory = Fluently.Configure() 
    .Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql()) 
    .Mappings(m => m.AutoMappings.Add(
     AutoMap.AssemblyOf<Computer>(new ExampleConfig()) 
      .Conventions.Add(DefaultLazy.Never()) 
      .Conventions.Add(DefaultCascade.All()))) 
    .ExposeConfiguration(c => new SchemaExport(c).Create(true, true)) 
    .BuildSessionFactory()) 
{ 
    using (ISession db = sessionFactory.OpenSession()) 
    { 
     Computer comp = new Computer(); 
     comp.Devices = new List<Device>(); 
     Device dev1 = new Device(); 
     comp.Devices.Add(dev1); 
     dev1.Maker = new Maker() { Name = "IBM"}; 
     Device dev2 = new Device(); 
     comp.Devices.Add(dev2); 
     dev2.Maker = new Maker() { Name = "Acer"}; 
     db.Persist(comp); 
     db.Flush(); 
    } 
    using (ISession db = sessionFactory.OpenSession()) 
    { // This is the part I'm having trouble with: 
     Device devAlias = null; 
     Maker makerAlias = null; 
     IList<Computer> comps = db.QueryOver<Computer>() 
      .JoinAlias(c => c.Devices,() => devAlias) 
      .JoinAlias(() => devAlias.Maker,() => makerAlias) 
      .OrderBy(() => makerAlias.Name).Asc 
      .List(); 
     Console.WriteLine(comps.Count); 
     foreach (Device dev in comps[0].Devices) 
     { 
      Console.WriteLine(dev.Maker.Name); 
     } 
    } 
} 

しかし、もちろん、それは私がやりたいことはありません。メーカーの名前でリスト全体をソートしようとします。 SQLからもわかるように、それは成功しています。実際には、デバイスメーカによってソートされたデバイスを持つコンピュータの無駄なデカルト製品を取得しています。

しかし、今回はソートせずにデバイスをフェッチするために別のSQLクエリを発行します。私はNHibernateが私の結合が子供をフェッチすることを意図していたとは思わないと思う。

問題は、どうすれば2番目のクエリを制御できますか?たとえば、メーカーの名前でデバイスを注文する場合、または各Computer.Devicesのリストに、IBMが作成したデバイス(存在する場合)のみが含まれるようにします。私はそのためにサブクエリが必要だと思いますが、どこにプラグインしますか?

ただ、完全を期すために、ここに私の設定です:

class ExampleConfig : DefaultAutomappingConfiguration 
{ 
    public override bool ShouldMap(Type type) 
    { 
     return type.Namespace == "TryHibernate.Example"; 
    } 
} 

答えて

1

Bozhidarの答えでしばらくの間苦労した後、私はそれをやや働かせました。私は、コンピュータのために、このようなを手動でマッピングをしなければならなかった:

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="all" default-lazy="false"> 
    <class name="TryHibernate.Example.Computer, TryHibernate" table="Computer"> 
    <id name="Id" type="System.Int32, mscorlib" column="Id" generator="identity" /> 
    <bag name="Devices"> 
     <key column="ComputerId" /> 
     <one-to-many class="TryHibernate.Example.Device, TryHibernate" /> 
     <loader query-ref="DeviceLoader" /> 
    </bag> 
    </class> 

    <sql-query name="DeviceLoader"> 
    <load-collection alias="Device" role="TryHibernate.Example.Computer.Devices" /> 
    SELECT Device.Id, Device.Maker_Id, Device.ComputerId, Maker.Name 
    FROM Device JOIN Maker ON (Maker.Id = Device.Maker_Id) 
    WHERE Device.ComputerId=? 
    ORDER BY Maker.Name 
    </sql-query> 
</hibernate-mapping> 

これはComputer.hbm.xmlという名前の埋め込みリソースに入ります。は、その後のコードは、ちょうどこのように動作します:

using (ISessionFactory sessionFactory = Fluently.Configure() 
    .Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql()) 
    .Mappings(m => m.AutoMappings.Add(
     AutoMap.AssemblyOf<Employee>(new ExampleConfig()) 
      .Conventions.Add(DefaultLazy.Never()) 
      .Conventions.Add(DefaultCascade.All()))) 
    .Mappings(m => m.HbmMappings.AddFromAssembly(Assembly.GetExecutingAssembly())) 
    .ExposeConfiguration(c => new SchemaExport(c).Create(true, true)) 
    .BuildSessionFactory()) 
{ 
    using (ISession db = sessionFactory.OpenSession()) 
    { 
     Computer comp = new Computer(); 
     comp.Devices = new List<Device>(); 
     Device dev1 = new Device(); 
     comp.Devices.Add(dev1); 
     dev1.Maker = new Maker() { Name = "IBM" }; 
     Device dev2 = new Device(); 
     comp.Devices.Add(dev2); 
     dev2.Maker = new Maker() { Name = "Acer" }; 
     db.Transaction.Begin(); 
     db.Persist(comp); 
     db.Transaction.Commit(); 
    } 
    using (ISession db = sessionFactory.OpenSession()) 
    { 
     IList<Computer> comps = db.QueryOver<Computer>().List(); 
     Console.WriteLine(comps.Count); 
     foreach (Device dev in comps[0].Devices) 
     { 
      Console.WriteLine(dev.Maker.Name); 
     } 
    } 
} 

しかし、私はこれで満足していると言うことはできません。まず、何かを注文するのはあまりにも多くの作業で、SQLはすぐに使えるものです。また、クエリレベルでカスタマイズすることはできません。これは、私の質問の目的を破るものです。それで、バッグのタグは、私にはむしろ不安を感じさせます。これは、の順序付けされていないコレクションを意味します。確かに、と思われますが、保証はありますか?そして、愚かなNHibernateはリストを使用することを許可していません。なぜなら、それは "真の"マッピングではないからです。あなたは本当のリストを得るために愚かな "インデックス"列が必要です!

これらはすべて、単純なものを並べ替えるだけです。 jOOQのようなものが生まれる理由を私に考えさせます。 .Netには、必死にそのようなものが必要です(nOOQの誰ですか?)。コードジェネレータとPOCOとの自動変換で、タイプセーフな組み込みSQLを使用するだけです。

+0

"何かを注文するにはあまりにも多くの仕事があるようですが、SQLはすぐに使えるものです" - 全く同意します。それは簡単ではありませんが、そうではありません。 NHibernateは、しばしば、成熟していると同時に愚かな能力を持っています。ドキュメンテーションの質はもちろんですが... NHではLINQのサポートがあります。あなたはそれを見たことがありますか? –

+0

@Bozhidar、右、私はLinqを見たことがありますが、この場合に使用できるかどうかは不明ですし、SQLにマップするのか、メモリ内での並べ替えなのか分かりません。フェッチされたコレクションではいつでも自分自身で行うことができるからです。 –

1

これは本当にNHibernateのではサポートされていません。あなたが結合して、おそらくそれに近づくことができるたくさんの機能があります。これらのソリューションは、迅速に破損する傾向があり、維持することは困難です。

QueryOver:NHibernateに子供を参加させて同時にコレクションに入れることができます。

db.QueryOver<Computer>() 
    .JoinAlias(c => c.Devices,() => devAlias) 
    .JoinAlias(() => devAlias.Maker,() => makerAlias) 
    .Fetch(x => x.Devices).Eager 
    .OrderBy(() => makerAlias.Name).Asc 
    .List(); 

デカルト積の問題のため、これは1つのコレクションで多少はうまくいきます。あなたのケースはすでに複雑すぎるでしょう。

マッピング:もう1つの解決策は、マッピングファイルで順序付けされています。おそらく、SQLを「数式」に追加するときには、メーカーの名前で注文するように管理します。私はそれをしないだろう。アプリケーションのパフォーマンスは破壊されます。なぜなら、これらのことをデータベースから取り出すためには、これらすべての結合と順序付けが必要になるからです。

書き込み時にソート:正しい順序でデータベースに配置することを検討する必要があります。 Bagではなく、Listとしてマップします。正直言って、メーカーが注文したデバイスは、データ管理のものよりも表示されるように聞こえるので、読んだときに実行する必要があります。例えば、次のような場合に正しい順序で保持することも難しいだろう。メーカーが名前を変更します。しかし、私はこのオプションを完全性のために追加したいと思っていました。これはしばしば意味があります。

KISS:これで最も簡単な解決方法が得られます。

- ドラムロール -

Linqを使用してメモリで注文します。

foreach(var computer in computers) 
{ 
    foreach(var device in computer.Devices.OrderBy(x => x.Maker.Name)) 
    { 

    } 
} 

完了。

+0

'Fetch'ウェイは私にとってはうまくいかず、結果リストにあるコンピュータが所有するコレクションをまだ注文していません。 –

+0

@ SergeyTachenov:うまくいかないかもしれません。それがリストであり、例えばバッグではない場合、注文を変更することさえ許されない。私が言ったように、それは簡単に破ることができます。 –

1

私は自分に1時間与えることをお勧めします。その時に、この特定のクエリのエンティティ・フレームワーク処理を記述します。私はNHibernateをEntityフレームワークに変換しました。そしておそらく、問題のテーブル間でランダムなxmlリンクをしている可能性があります。エンティティフレームワーク内で、これらの「ナビゲーション」/「仮想コレクション」はORM内で明示的に実装されます。

NHibernate内のオブジェクトに移動できない場合は、Entity Frameworkの小さなスライスを別のクラスライブラリに追加してORMに慣れて、多対多の関係が機能しない理由を説明してください。あなたが1対1の関係を持っているなら、これはこれほど簡単なはずです。

+0

この例の全体的なポイントは、NHibernateを学ぶことです、EFは明らかにここで私を助けません。そしてあなたは別の言葉を忘れてしまいました。それはFluent NHibernateなので、XMLではなく、愚かな属性ではなく、コード内でのマニュアルマッピングはほとんどありません。質問に表示されたコードは実際にすべてのマッピングを行います。 EFでもそうすることができますか? –

+0

EFはあなたのために物事を描くことができます。別のナビゲーションプロパティを作成することによって、1対多のものを多数のものに作成することさえできます。私はNHibernateの設定全体をEntityフレームワークに移行しなければならず、今でも覚えている痛みのポイントはNHの暗黙的な結合でした。 – Programmer

+0

私はEF依存関係がどのようにしてドメインに入り込むのか好きではありません。 JPAを思い出させる。私はそれが好きではない。しかし、再び、私はORMsが嫌いです。そして、JPAはそれほど悪くはないので、EFを試してみてください。 –

2

これは、Device es retrievalのSQL select文に干渉することで実現できます。私にとって最もエレガントな解決策は、カスタムローダーを使用することです。しかし、これは
not supported through Fluent NHibernateです。幸いにも、あなたがどこかにプロジェクト内の単一のhbm.xmlを書いて、そのような流暢を通してそれを追加することができます。

Device.hbm.xml

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true"> 
    <class name="TryHibernate.Example.Device, TryHibernate" table="Device"> 
     <id name="Id" type="System.Int32, mscorlib" column="Id" generator="identity" /> 
     <property name="Name" /> 
     <many-to-one name="Maker" column="MakerId" /> 
     <many-to-one name="Computer" column="ComputerId" /> 
     <loader query-ref="DeviceLoader" /> 
    </class> 

    <sql-query name="DeviceLoader"> 
     <return alias="dev" class="TryHibernate.Example.Device, TryHibernate" lock-mode="read"> 
      <return-property name="Computer" column="ComputerId" /> 
      <return-property name="Maker" column="MakerId" /> 
     </return> 

     SELECT Device.Id AS {dev.Id}, Device.Name As {dev.Name}, Device.MakerId AS {dev.MakerId}, Device.ComputerId AS {dev.ComputerId} 
     FROM Device INNER JOIN Maker ON Maker.Id = Device.MakerId 
     WHERE Device.Id=? 
     ORDER BY Device.ComputerId, Maker.Name 
    </sql-query> 
</hibernate-mapping> 

そして、セッションファクトリを構築しながら行います。

 
    using (ISessionFactory sessionFactory = Fluently.Configure() 
     .Database(SQLiteConfiguration.Standard.UsingFile("temp.sqlite").ShowSql()) 
     .Mappings(m => m.AutoMappings.Add(
      AutoMap.AssemblyOf(new ExampleConfig()) 
       .Conventions.Add(DefaultLazy.Never()) 
       .Conventions.Add(DefaultCascade.All()))) 
     .Mappings(m => m.HbmMappings.AddFromAssembly(Assembly.GetExecutingAssembly())) 
     .ExposeConfiguration(c => new SchemaExport(c).Create(true, true)) 
     .BuildSessionFactory()) 

私にヒットしたもう一つの考えは、あなたのマッピングにSubselect()を指定できるということです。

public class DeviceMap : ClassMap<Device> 
{ 
    public DeviceMap() 
    { 
     Table("Device"); 
     Subselect(@" 
    SELECT TOP 100 Device.Id , Device.Name, Device.MakerId , Device.ComputerId 
    FROM Device INNER JOIN Maker ON Maker.Id = Device.MakerId 
    ORDER BY Device.ComputerId, Maker.Name 
"); 

     Id(x => x.Id); 
     Map(x => x.Name); 

     References(x => x.Computer).Column("ComputerId"); 
     References(x => x.Maker).Column("MakerId"); 
    } 

通知したとおり、私はTOP句を使用しました。サブselect文がサブクエリになるためには必要であり、あなたが

を知っているかもしれないとORDER BY句はビュー、インライン関数、派生テーブル、サブクエリ、および共通テーブル式では無効で、TOPは、OFFSETない限り、またはFOR XMLも指定されています。


さらに別しかしはIInterceptorインターフェースを実装し、必要に応じてSQLを変更することです。同様に:

public class SqlStatementInterceptor : EmptyInterceptor 
{ 
    public override SqlString OnPrepareStatement(SqlString sql) 
    { 
     var result = sql; 

     if (sql.IndexOfCaseInsensitive("FROM Device") != -1) 
     { 
      var sqlText = string.Format("WITH cteDev AS ({0}{1}{0}){0}SELECT cteDev.* FROM cteDev INNER JOIN Maker ON cteDev.MakerId1_0_ = Maker.Id ORDER BY Maker.Name", Environment.NewLine, sql.ToString()); 

      result = sql 
       .Insert(0, "WITH cteDev AS (") 
       .Append(")SELECT cteDev.* FROM cteDev INNER JOIN Maker ON cteDev.MakerId1_0_ = Maker.Id ORDER BY Maker.Name"); 
      ; 

     } 

     Trace.WriteLine(result.ToString()); 

     return result; 
    } 
} 

***これで、NHibernateが表と列の名前を生成する方法を説明する必要があります。

+0

うわー、それは...予期せぬものでした。賞金満期から1日も経たないうちに、私はそれがただ消えていくと思った。私はあなたの最初の解決策を試しただけです。他のものはもっと複雑で、交換で何も与えていないようです。最初のものはうまく動作せず、クエリごとに順序を制御する能力はありませんが、少なくとも何らかの形で動作するように指示する方向に私を指摘しました。詳細は私の答えを見てください。より良い答えがない場合、あなたは確かに賞金を得るでしょう! –

+0

2番目のオプション、 'Subselect(string)'を試してください。唯一のトレードオフは、トップストップではないような「TOP」条項です。私は、あなたの状況レコード数に十分であると信じています。それは私にとって最速のアプローチでした。 –

+0

'Subselect'を実行すると、デバイスを追加しようとしたときに、絶対にばかげた' INSERT INTO(SELECT ...) 'を実行しようとし続けます。それは、ちょうどそれがそれのように感じるところで、テーブル名を私のsubselectに置き換えたのとほぼ同じです。 –

関連する問題