記事の概要:
- 仕事の準備。 コードファーストとデータベースファーストのアプローチ
- データベースクエリ
- JOINおよびナビゲーションプロパティ
- 遅延と非同期
- 取引
- OrmLiteとEntity Frameworkのパフォーマンス比較
- おわりに
キャットの下で私が招待する利害関係者。
仕事の準備。 コードファーストとデータベースファーストのアプローチ
プロジェクトにOrmLiteをインストールします。
インストールパッケージServiceStack.OrmLite.SqlServer
OrmLiteは、主にコード優先ORMです。 ただし、既存のデータベースに基づいてPOCOクラスを生成することは可能です。 この世代では、T4テンプレートを追加インストールすることから始めます。
インストールパッケージServiceStack.OrmLite.T4
すべてがうまくいった場合、3つのファイルがプロジェクトに追加されます。
OrmLite.Core.ttinclude
OrmLite.Poco.tt
OrmLite.SP.tt
接続文字列をapp / web.configに追加し、OrmLite.Poco.ttファイルにConnectionStringNameを入力し(app.configの1行のオプション)、Run Custom Toolファイルをクリックして、生成されたPOCOクラスを取得します。次に例を示します。
[Alias("Order")] [Schema("dbo")] public partial class Order : IHasId<int> { [AutoIncrement] public int Id { get; set; } [Required] public int Number { get; set; } public string Text { get; set; } public int? CustomerId { get; set; } }
OK、モデルの準備ができました。 データベースにテストクエリを作成してみましょう。 OrmLite機能は、IDbConnectionを実装するOrmLiteConnectionクラスのインスタンスを介してアクセスされます。
var dbFactory = new OrmLiteConnectionFactory(ConnectionString, SqlServerDialect.Provider); using (IDbConnection db = dbFactory.Open()) { //db.AnyMethod... }
このパターンを覚えておきましょう。dbオブジェクトを参照する際には、このパターンが暗示されます。
50より大きい数値を持つOrderテーブルからすべてのレコードを選択します。
List<Order> orders = db.Select<Order>(order => order.Number > 50);
シンプル!
OrmLiteConnectionの中身は何ですか?
通常のSqlConnection :
public override IDbConnection CreateConnection(string connectionString, Dictionary<string, string> options) { var isFullConnectionString = connectionString.Contains(";"); if (!isFullConnectionString) { var filePath = connectionString; var filePathWithExt = filePath.ToLower().EndsWith(".mdf") ? filePath : filePath + ".mdf"; var fileName = Path.GetFileName(filePathWithExt); var dbName = fileName.Substring(0, fileName.Length - ".mdf".Length); connectionString = string.Format( @"Data Source=.\SQLEXPRESS;AttachDbFilename={0};Initial Catalog={1};Integrated Security=True;User Instance=True;", filePathWithExt, dbName); } if (options != null) { foreach (var option in options) { if (option.Key.ToLower() == "read only") { if (option.Value.ToLower() == "true") { connectionString += "Mode = Read Only;"; } continue; } connectionString += option.Key + "=" + option.Value + ";"; } } return new SqlConnection(connectionString); }
コードファーストのアプローチに移りましょう。 テーブルのDROPおよびCREATEを順番に実行するには、次のようにします。
db.DropAndCreateTable<Order>();
T4 POCOを使用して以前に生成されたクラスでは、データベーステーブルに関する情報の一部(文字列データの長さ、外部キーなど)が失われていることに注意してください。 OrmLiteは、このような情報をPOCOに追加するために必要なすべてを提供します(コードファースト指向!)。 次の例では、非クラスター化インデックスを作成し、nvarchar型(20)を指定し、それぞれNumber、Text、およびCustomerIdフィールドの外部キーを作成します。
[Schema("dbo")] public partial class Order : IHasId<int> { [AutoIncrement] public int Id { get; set; } [Index(NonClustered = true)] public int Number { get; set; } [CustomField("NVARCHAR(20)")] public string Text { get; set; } [ForeignKey(typeof(Customer))] public int? CustomerId { get; set; } }
その結果、db.CreateTableが呼び出されると、次のSQLクエリが実行されます。
CREATE TABLE "dbo"."Order" ( "Id" INTEGER PRIMARY KEY IDENTITY(1,1), "Number" INTEGER NOT NULL, "Text" NVARCHAR(20) NULL, "CustomerId" INTEGER NULL, CONSTRAINT "FK_dbo_Order_dbo_Customer_CustomerId" FOREIGN KEY ("CustomerId") REFERENCES "dbo"."Customer" ("Id") ); CREATE NONCLUSTERED INDEX idx_order_number ON "dbo"."Order" ("Number" ASC);
データベースクエリ
OrmLiteには、データベースクエリを作成する2つの主な方法があります。ラムダ式とパラメーター化されたSQLです。 以下のコードは、指定されたCustomerIdを持つOrderテーブルからすべてのレコードをさまざまな方法で取得する方法を示しています。
1)ラムダ式とSqlExpression:
List<Order> orders = db.Select<Order>(order => order.CustomerId == customerId);
List<Order> orders = db.Select(db.From<Order>().Where(order => order.CustomerId == customerId));
2)パラメーター化されたSQL:
List<Order> orders = db.SelectFmt<Order>("CustomerId = {0}", customerId);
List<Order> orders = db.SelectFmt<Order>("SELECT * FROM [Order] WHERE CustomerId = {0}", customerId);
単純な挿入/更新/削除クエリの作成も簡単です。 ネタバレの下には、公式文書のいくつかの例があります。
シンプルなCRUD
SQL:
SQL:
SQL:
db.Update(new Person { Id = 1, FirstName = "Jimi", LastName = "Hendrix", Age = 27});
SQL:
UPDATE "Person" SET "FirstName" = 'Jimi',"LastName" = 'Hendrix',"Age" = 27 WHERE "Id" = 1
db.Insert(new Person { Id = 1, FirstName = "Jimi", LastName = "Hendrix", Age = 27 });
SQL:
INSERT INTO "Person" ("Id","FirstName","LastName","Age") VALUES (1,'Jimi','Hendrix',27)
db.Delete<Person>(p => p.Age == 27);
SQL:
DELETE FROM "Person" WHERE ("Age" = 27)
より詳細に、より興味深いケースを検討します。
JOINおよびナビゲーションプロパティ
既知のOrderテーブルに、関連するCustomerテーブルを追加します。
class Customer { [AutoIncrement] public int Id { get; set; } public string Name { get; set; } }
内部接続(INNER JOIN)の場合、コードを実行するだけで十分です。
List<Order> orders = db.Select<Order>(q => q.Join<Customer>());
SQL:
SELECT "Order"."Id", "Order"."Details", "Order"."CustomerId" FROM "Order" INNER JOIN "Customer" ON ("Customer"."Id" = "Order"."CustomerId")
したがって、LEFT JOINには、q.LeftJoinメソッドなどが使用されます。 複数のテーブルから同時にデータを取得するには、方法1で結果の選択を次のOrderInfoクラスにマッピングします。
class OrderInfo { public int OrderId { get; set; } public string OrderDetails { get; set; } public int? CustomerId { get; set; } public string CustomerName { get; set; } }
List<OrderInfo> info = db.Select<OrderInfo>(db.From<Order>().Join<Customer>());
SQL:
SELECT "Order"."Id" as "OrderId", "Order"."Details" as "OrderDetails", "Order"."CustomerId", "Customer"."Name" as "CustomerName" FROM "Order" INNER JOIN "Customer" ON ("Customer"."Id" = "Order"."CustomerId")
OrderInfoクラスの唯一の前提条件は、そのプロパティに{TableName} {FieldName}テンプレートの名前を付ける必要があることです。
EFスタイルのメソッド番号2は、ナビゲーションプロパティを使用することです(OrmLiteの用語では、「参照」と呼ばれます)。
これを行うには、次のプロパティをOrderクラスに追加します。
[Reference] Customer Customer { get; set; }
このプロパティは、非常に便利なdb.Selectなどのクエリでは無視されます。 関連するエンティティをロードするには、db.LoadSelectメソッドを使用する必要があります。
List<Order> orders = db.LoadSelect<Order>(); Assert.True(orders.All(order => order.Customer != null));
SQL:
SELECT "Id", "Details", "CustomerId" FROM "Order" SELECT "Id", "Name" FROM "Customer" WHERE "Id" IN (SELECT "CustomerId" FROM "Order")
同様に、customer.Ordersのセットを初期化できます。
注:上記の例では、リンクされたテーブルの外部キーの名前は{Parent} Idパターンに従いました。これにより、OrmLiteは接続を行う列を自動的に選択し、コードを簡素化しました。 別の方法は、外部キーを属性でマークすることです:
[References(typeof(Parent))] public int? CustomerId { get; set; }
結合のテーブル列を明示的に設定します。
SqlExpression<Order> expression = db .From<Order>() .Join<Order, Customer>((order, customer) => order.CustomerId == customer.Id); List<Order> orders = db.Select(expression);
遅延と非同期
保留中のSELECTクエリは、IEnumerableを介して実装されます。 *遅延メソッドの場合、ラムダ式を使用した簡潔なクエリはサポートされていません。 したがって、SelectLazyは、パラメーター化されたSQLのみを使用することを想定しています。
IEnumerable<Product> lazyQuery = db.SelectLazy<Product>("UnitPrice > @UnitPrice", new { UnitPrice = 10 });
列挙をバイパスすることは、次の呼び出しに似ています。
db.Select<Product>(q => q.UnitPrice > 10);
ColumnLazy(テーブル列の値のリストを返す)の場合、SqlExpressionが追加でサポートされます。
IEnumerable<string> lazyQuery = db.ColumnLazy<string>(db.From<Product>().Where(x => x.UnitPrice > 10));
遅延クエリとは異なり、OrmLite APIのほとんどには非同期バージョンがあります。
List<Employee> employees = await db.SelectAsync<Employee>(employee => employee.City == "London");
取引
以下がサポートされています。
db.DropAndCreateTable<Employee>(); Assert.IsTrue(db.Count<Employee>() == 0); using (IDbTransaction transaction = db.OpenTransaction()) { db.Insert(new Employee {Name = "First"}); transaction.Commit(); } Assert.IsTrue(db.Count<Employee>() == 1); using (IDbTransaction transaction = db.OpenTransaction()) { db.Insert(new Employee { Name = "Second" }); Assert.IsTrue(db.Count<Employee>() == 2); transaction.Rollback(); } Assert.IsTrue(db.Count<Employee>() == 1);
内部では、db.OpenTransactionがSqlConnection.BeginTransactionを呼び出しているため、トランザクションのトピックについては詳しく説明しません。
文字列のグループに対する操作。 OrmLiteとEntity Frameworkのパフォーマンス比較
SELECTクエリの実行のさまざまなバリエーションに加えて、OrmLite APIは、データベース内の行のグループを変更するための3つのメソッドを提供します。
InsertAll(IEnumerable)
UpdateAll(IEnumerable)
DeleteAll(IEnumerable)
この場合のOrmLiteの動作は、「アダルト」ORM、主にEntity Frameworkの動作と変わりません。データベースの行ごとに1つのINSERT / UPDATEステートメントを取得します。 Row Constructorを使用してINSERTのソリューションを調べることは興味深いですが、運命ではありません。 明らかに、実行速度の違いは、主にフレームワーク自体のアーキテクチャ上の特徴のために形成されます。 この違いはとても大きいですか?
以下は、Entity FrameworkとOrmLiteを使用したOrderテーブルからの10 3行の選択、挿入、変更の実行時間の測定値です。 反復は10 3回繰り返され、 合計ランタイム(秒単位)が表に表示されます。 各反復で、ランダムデータの新しいセットが生成され、テーブルがクリアされます。 コードはGitHubで入手できます 。
テスト環境
.NET 4.5
MS SQL Server 2012
Entity Framework 6.1.3(コードファースト)
OrmLite 4.0.38
MS SQL Server 2012
Entity Framework 6.1.3(コードファースト)
OrmLite 4.0.38
コード
エンティティフレームワーク:
OrmLite:
//select context.Orders.AsNoTracking().ToList(); //insert context.Orders.AddRange(orders); context.SaveChanges(); //update context.SaveChanges();
OrmLite:
//select db.Select<Order>(); //insert db.InsertAll(orders); //update db.UpdateAll(orders);
秒単位の実行時間:
選択してください | 挿入 | 更新する | |
EF | 4.0 | 282 | 220 |
オルミテ | 7.3 | 94 | 88 |
OrmLite、本気ですか? Selectの実行はEFよりも遅いですか? これらの結果の後、Idによって1行の読み取り速度を測定する追加のテストを記述することが決定されました。
コード
エンティティフレームワーク:
OrmLite:
context.Orders.AsNoTracking().FirstOrDefault(order => order.Id == id);
OrmLite:
db.SingleById<Order>(id);
秒単位の実行時間:
IDでシングルを選択 | |
EF | 1.9 |
オルミテ | 1,0 |
今回、OrmLiteにはほぼ2つの利点がありますが、それは悪くありません。 これまでのところ、データベースから大量の行をアンロードするときにパフォーマンスが低下する理由について説明することは想定していません。 示されているように、ほとんどのシナリオでは、OrmLiteはEFよりも高速です(2〜3回)。
測定と系統誤差について
クライアントでのコードの合計実行時間が測定されたため、サーバーでのSQLステートメントの実行時間は測定に体系的なエラーをもたらします。 SQL Serverの負荷の高い「戦闘」インスタンスを使用すると、両方のORMに対して「ほぼ同じ」ランタイムが得られます。 明らかに、サーバーは可能な限り生産的であり、より正確な結果を得るためにロードされるべきではありません。
おわりに
記事の最後に、OrmLiteを使用した(確かに主観的な)印象についてお話しし、このmicro-ORMの長所と短所をまとめます。
長所:
- 軽量で、構成と展開が簡単です。
- 単純なCRUDリクエストは非常に簡単に記述できます。
短所:
- さまざまなメソッドのクエリ構築の可変性(ラムダ、パラメーター化されたSQL、SqlExpressionのいずれか)。 APIのメソッドでサポートされる単一の汎用構文はありません。
- 不十分なドキュメント:メソッドに関するXMLコメントはありません。公式のドキュメントは構造が不十分で、単一のWebページにあります。
- あいまいなAPI。 db.Select、db.LoadSelect、db.Whereの呼び出しの違いをすぐに推測してみてください。 またはdb.Insertとdb.Save? ナビゲーションプロパティはdb.Join呼び出しから取得されますか?
これらのアイテムは、テクノロジーの「しきい値」を高めます。 一部、これにより、OrmLiteからmicro-ORMの主な利点の1つである「大人の」ORMと比較した単純さと使いやすさが奪われます。 一般的に、私は中立的な印象を持っていました。 OrmLiteは確かに使用できますが、商用製品にはさらに多くのものが期待されていました。