データベースを最初に EF DataModel に設定して、基本的な WebApi サービスを設定しています。WebApi、EF6、および WebApi OData パッケージのナイトリー ビルドを実行しています。(WebApi: 5.1.0-alpha1、EF: 6.1.0-alpha1、WebApi OData: 5.1.0-alpha1)
データベースには、Product と Supplier の 2 つのテーブルがあります。製品は 1 つのサプライヤーを持つことができます。サプライヤーは複数の製品を持つことができます。
2 つの DTO クラスも作成しました。
public class Supplier
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual IQueryable<Product> Products { get; set; }
}
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
次のように WebApiConfig を設定しました。
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder oDataModelBuilder = new ODataConventionModelBuilder();
oDataModelBuilder.EntitySet<Product>("product");
oDataModelBuilder.EntitySet<Supplier>("supplier");
config.Routes.MapODataRoute(routeName: "oData",
routePrefix: "odata",
model: oDataModelBuilder.GetEdmModel());
}
次のように2つのコントローラーをセットアップしました。
public class ProductController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Product> Get()
{
var context = new ExampleContext();
var results = context.EF_Products
.Select(x => new Product() { Id = x.ProductId, Name = x.ProductName});
return results as IQueryable<Product>;
}
}
public class SupplierController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Supplier> Get()
{
var context = new ExampleContext();
var results = context.EF_Suppliers
.Select(x => new Supplier() { Id = x.SupplierId, Name = x.SupplierName });
return results as IQueryable<Supplier>;
}
}
返されるメタデータは次のとおりです。ご覧のとおり、ナビゲーション プロパティは正しく設定されています。
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<Schema Namespace="StackOverflowExample.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="Product">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
</EntityType>
<EntityType Name="Supplier">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
<NavigationProperty Name="Products" Relationship="StackOverflowExample.Models.StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner" ToRole="Products" FromRole="ProductsPartner" />
</EntityType>
<Association Name="StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner">
<End Type="StackOverflowExample.Models.Product" Role="Products" Multiplicity="*" />
<End Type="StackOverflowExample.Models.Supplier" Role="ProductsPartner" Multiplicity="0..1" />
</Association>
</Schema>
<Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
<EntitySet Name="product" EntityType="StackOverflowExample.Models.Product" />
<EntitySet Name="supplier" EntityType="StackOverflowExample.Models.Supplier" />
<AssociationSet Name="StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartnerSet" Association="StackOverflowExample.Models.StackOverflowExample_Models_Supplier_Products_StackOverflowExample_Models_Product_ProductsPartner">
<End Role="ProductsPartner" EntitySet="supplier" />
<End Role="Products" EntitySet="product" />
</AssociationSet>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
したがって、odata クエリの通常の配列は正常に機能します。たとえば、/odata/product?$filter=Name+eq+'Product1' および /odata/supplier?$select=Id はすべて正常に機能します。
問題は、$expand を使用しようとしたときです。/odata/supplier?$expand=Products を実行すると、もちろんエラーが発生します。
「指定された型メンバー 'Products' は、LINQ to Entities ではサポートされていません。初期化子、エンティティ メンバー、およびエンティティ ナビゲーション プロパティのみがサポートされています。」
更新: 同じ質問が引き続き寄せられるため、情報を追加します。はい、上で投稿したメタデータ情報に見られるように、ナビゲーション プロパティは正しく設定されています。
これは、コントローラーでメソッドが欠落していることとは関係ありません。IODataRoutingConvention を実装するクラスを作成する場合、/odata/supplier(1)/product は "~/entityset/key/navigation" として正しく解析されます。
DTO を完全にバイパスして、EF で生成されたクラスを返すだけであれば、$expand はすぐに使用できます。
更新 2: Product クラスを次のように変更した場合:
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual Supplier Supplier { get; set; }
}
次に、ProductController を次のように変更します。
public class ProductController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Product> Get()
{
var context = new ExampleContext();
return context.EF_Products
.Select(x => new Product()
{
Id = x.ProductId,
Name = x.ProductName,
Supplier = new Supplier()
{
Id = x.EF_Supplier.SupplierId,
Name = x.EF_Supplier.SupplierName
}
});
}
}
/odata/product を呼び出すと、期待どおりの結果が得られます。応答で返されないサプライヤ フィールドを持つ製品の配列。SQL クエリは、Suppliers テーブルからの結合と選択を生成しました。これは、次のクエリ結果がない場合でも意味があります。
/odata/product?$select=Id を呼び出すと、期待どおりの結果が返されます。しかし $select は、サプライヤー テーブルに結合しない SQL クエリに変換されます。
/odata/product?$expand=製品は別のエラーで失敗します:
「DbIsNullExpression への引数は、プリミティブ、列挙型、または参照型を参照する必要があります。」
製品コントローラを次のように変更すると:
public class ProductController : ODataController
{
[HttpGet]
[Queryable]
public IQueryable<Product> Get()
{
var context = new ExampleContext();
return context.EF_Products
.Select(x => new Product()
{
Id = x.ProductId,
Name = x.ProductName,
Supplier = new Supplier()
{
Id = x.EF_Supplier.SupplierId,
Name = x.EF_Supplier.SupplierName
}
})
.ToList()
.AsQueryable();
}
}
/odata/product、/odata/product?$select=Id、および /odata/product?$expand=Supplier は正しい結果を返しますが、明らかに .ToList() は目的を少し無効にします。
次のように、$expand クエリが渡されたときにのみ .ToList() を呼び出すように Product Controller を変更することができます。
[HttpGet]
public IQueryable<Product> Get(ODataQueryOptions queryOptions)
{
var context = new ExampleContext();
if (queryOptions.SelectExpand == null)
{
var results = context.EF_Products
.Select(x => new Product()
{
Id = x.ProductId,
Name = x.ProductName,
Supplier = new Supplier()
{
Id = x.EF_Supplier.SupplierId,
Name = x.EF_Supplier.SupplierName
}
});
IQueryable returnValue = queryOptions.ApplyTo(results);
return returnValue as IQueryable<Product>;
}
else
{
var results = context.EF_Products
.Select(x => new Product()
{
Id = x.ProductId,
Name = x.ProductName,
Supplier = new Supplier()
{
Id = x.EF_Supplier.SupplierId,
Name = x.EF_Supplier.SupplierName
}
})
.ToList()
.AsQueryable();
IQueryable returnValue = queryOptions.ApplyTo(results);
return returnValue as IQueryable<Product>;
}
}
}
残念ながら、/odata/product?$select=Id または /odata/product?$expand=Supplier を呼び出すと、returnValue を IQueryable にキャストできないため、シリアル化エラーがスローされます。/odata/product を呼び出すと、キャストできます。
このあたりの仕事は何ですか?独自の DTO の使用をスキップする必要がありますか、または $expand と $select の独自の実装をロールすることができますか?