I'm developing Asp .Net Core 2.2 application with ODATA. I have test application to reproduce the problem: I can't request linked entities with link table via ODATA request.
Request: /odata/Books?$expand=BookCategories
response error:
"Message": "The query specified in the URI is not valid. Property 'BookCategories' on type 'GameStorageView.Data.Book' is not a navigation property or complex property. Only navigation properties can be expanded.",
"ExceptionMessage": "Property 'BookCategories' on type 'GameStorageView.Data.Book' is not a navigation property or complex property. Only navigation properties can be expanded.",
"ExceptionType": "Microsoft.OData.ODataException",
model:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<BookCategory> BookCategories { get; set; }
}
public class BookCategory
{
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Model creating:
modelBuilder.Entity<BookCategory>()
.HasKey(bc => new {bc.BookId, bc.CategoryId});
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Book)
.WithMany(b => b.BookCategories)
.HasForeignKey(bc => bc.BookId);
modelBuilder.Entity<BookCategory>()
.HasOne(bc => bc.Category)
.WithMany(c => c.BookCategories)
.HasForeignKey(bc => bc.CategoryId);
How to make it work? How to request many-to-many entities via odata?
UPD:
ODATA configured:
.EntityType.Count().Filter().OrderBy().Expand(SelectExpandType.Allowed,10).Select().Page().Count();
linq query works fine:
context.Book.Include(c=>c.BookCategories).ToArray()
Ok, my solution is not best, but it works: we need to add field Id and unique constraint to it:
public class BookCategory
{
public int Id { get; set; }
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
and
modelBuilder.Entity<BookCategory>().HasAlternateKey(bc => bc.Id);
and then if we want to load linked entities via odata, we also need to add link entity to odata routes. I use extension method so it looks like this:
public static void RegisterOdataRoute<TEntity>(this ODataConventionModelBuilder builder, string name = null, bool pluralize = true) where TEntity : class
{
name = name ?? $"{typeof(TEntity).Name}";
var names = pluralize ? $"{name}s" : name;
OdataRoutes.Add(name, $"~/odata/{names}");
builder.EntitySet<TEntity>(names).EntityType.Count().Filter().OrderBy().Expand(SelectExpandType.Allowed,10).Select().Page().Count();
}
}
and
app.UseOData(builder =>
{
builder.RegisterOdataRoute<BookCategory>();
...
If someone found a better solution, please let me know!