without with framework example c# entity-framework asp.net-web-api odata

c# - with - OData $ expand, DTOs, y Entity Framework



odata web api with entity framework (4)

Debe usar una propiedad de navegación de ICollection lugar de un IQueryable . Estos tipos son muy diferentes. No estoy seguro de que sea tu problema, pero vale la pena arreglarlo.

Tengo una configuración básica del servicio WebApi con una primera base de datos configurada con EF DataModel. Estoy ejecutando las compilaciones nocturnas de los paquetes WebApi, EF6 y WebApi OData. (WebApi: 5.1.0-alpha1, EF: 6.1.0-alpha1, WebApi OData: 5.1.0-alpha1)

La base de datos tiene dos tablas: Producto y Proveedor. Un Producto puede tener un Proveedor. Un proveedor puede tener múltiples productos.

También he creado dos clases de 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; } }

He configurado mi WebApiConfig de la siguiente manera:

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()); }

He configurado mis dos controladores de la siguiente manera:

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>; } }

Aquí están los metadatos que se devuelven. Como puede ver, las propiedades de navegación están configuradas correctamente:

<?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>

Por lo tanto, el conjunto normal de consultas de odata funciona bien: / odata / product? $ Filter = Name + eq + ''Product1'' y / odata / supplier? $ Select = Id, por ejemplo, todos funcionan bien.

El problema es cuando intento trabajar con $ expand. Si tuviera que hacer / odata / supplier? $ Expand = Productos, por supuesto recibo un error:

"El miembro de tipo especificado ''Productos'' no es compatible con LINQ para entidades. Solo se admiten inicializadores, miembros de entidad y propiedades de navegación de entidad".

Actualización: Sigo recibiendo las mismas preguntas, así que estoy agregando más información. Sí, las propiedades de navegación están configuradas correctamente como se puede ver en la información de metadatos que publiqué anteriormente.

Esto no está relacionado con los métodos que faltan en el controlador. Si tuviera que crear una clase que implemente IODataRoutingConvention, / odata / supplier (1) / product se analizaría como "~ / entityset / key / navigation" muy bien.

Si tuviera que pasar por alto mis DTO por completo y simplemente devolver las clases generadas por EF, $ expand funciona de manera inmediata.

Actualización 2: Si cambio mi clase de producto a lo siguiente:

public class Product { [Key] public int Id { get; set; } public string Name { get; set; } public virtual Supplier Supplier { get; set; } }

y luego cambie el controlador de producto a esto:

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 } }); } }

Si tuviera que llamar / odata / producto, obtendría lo que esperaba. Una matriz de Productos con el campo Proveedor no se devuelve en la respuesta. La consulta SQL generó uniones y selecciones de la tabla Proveedores, lo que tendría sentido para mí si no fuera por los resultados de la siguiente consulta.

Si tuviera que llamar a / odata / product? $ Select = Id, obtendría lo que esperaba. Pero $ select se traduce en una consulta de SQL que no se une a la tabla de proveedores.

/ odata / product? $ expand = El producto falla con un error diferente:

"El argumento de DbIsNullExpression debe referirse a un tipo primitivo, de enumeración o de referencia".

Si cambio mi Controlador de Producto a lo siguiente:

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 / producto, / odata / producto? $ select = Id, y / odata / producto? $ expand = Proveedor devuelve los resultados correctos, pero obviamente .ToList () anula un poco el propósito.

Puedo intentar modificar el Controlador del producto para que solo llame a .ToList () cuando se pasa una consulta $ expand, así:

[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>; } } }

Desafortunadamente, cuando llamo / odata / product? $ Select = Id u / odata / product? $ Expand = Supplier produce un error de serialización porque returnValue no se puede convertir a IQueryable. Puedo ser lanzado sin embargo si llamo / odata / producto.

¿Cuál es el trabajo por aquí? ¿Debo omitir el intento de usar mis propios DTO o puedo / debo lanzar mi propia implementación de $ expand y $ select?


El comando $ expand solo funciona si la acción del controlador tiene un argumento MaxExpansionDepth agregado al atributo Queryable que es mayor que 0.

[Queryable(MaxExpansionDepth = 1)]



No has configurado las relaciones de entidad en tu web-api. Necesitas agregar más métodos a tus controladores.

Supongo que las siguientes /odata/product(1)/Supplier URL no funcionan tan bien: /odata/product(1)/Supplier Esto se debe a que la relación no está establecida.

Agregue el siguiente método a su controlador y creo que debería resolver el problema:

// GET /Products(1)/Supplier public Supplier GetSupplier([FromODataUri] int key) { var context = new ExampleContext(); Product product = context.EF_Products.FirstOrDefault(p => p.ID == key); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product.Supplier; }

Creo que coincidió con su nombre. Solucionarlos según sea necesario. Para obtener más información, http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/working-with-entity-relations . La estructura de tu modelo es muy similar.