AutoQuery load second level nested data

Hi, I am trying to convert simple EF POCOs to ORMLite and AutoQuery and I have a problem with loading second level nested data.

For example I have this POCOs

[Alias("Item")]
public partial class Item: IHasId<int> 
{
    [Alias("ItemID")]
    [Required]
    public int Id { get; set; }
    public string ItemName { get; set; }

    [Reference]
    public List<Item_TagList> Item_TagList { get; set; }

    [Reference]
    public List<Item_Photo> Item_Photo { get; set; }
}

[Alias("Item_Photo")]
public partial class Item_Photo: IHasId<int>
{
    [Alias("ItemPhotoID")]
    [AutoIncrement]
    public int Id { get; set; }

    [References(typeof(Item))]
    [Required]
    public int ItemID { get; set; }
    public string PhotoURL{ get; set; }
}

[Alias("Item_TagList")]
public partial class Item_TagList : IHasId<int> 
{
    [Alias("ItemTagListID")]
    [AutoIncrement]
    public int Id { get; set; }
    

    [References(typeof(Item))]
    [Required]
    public int ItemID { get; set; }

    [References(typeof(Tag))]
    [Required]
    public int TagID { get; set; }

    [Reference]
    public Tag Tag { get; set; }
}

[Alias("Tag")]
public partial class Tag: IHasId<int>
{
    [ForeignKey(typeof(Item))]
    [Alias("TagID")]
    [AutoIncrement]
    public int Id { get; set; }
    public string TagName { get; set; }
}

I am trying to make AutoQuery service as this

[Route("/items")]
public class QueryItems : QueryDb<Item>, ILeftJoin<Item, Item_Photo>, ILeftJoin<Item, Item_TagList>, ILeftJoin<Item_TagList, Tag>
{
    public int? ItemID { get; set; }
}

And I get results for Item, Item_Photo, Item_TagList but there is no Tag data.

What am I doing wrong ?

OrmLite’s POCO’s references that AutoQuery uses only loads related data 1-reference level deep.

One way to populate them is to use a Custom AutoQuery Implementation to stitch the tags used in your AutoQuery response, something like:

public class MyQueryServices : Service
{
    public IAutoQueryDb AutoQuery { get; set; }

    public object Any(QueryItems query)
    {
        using var db = AutoQuery.GetDb(query, base.Request);
        var q = AutoQuery.CreateQuery(query, base.Request, db);
        var response = AutoQuery.Execute(query, q, base.Request, db);
        
        var tagIds = (from x in response.Results 
                      from t in x.Item_TagList select t.TagID).Distinct();
        var tagsMap = db.SelectByIds<Tag>(tagIds).ToDictionary(x => x.Id);
        response.Results.ForEach(x => x.Item_TagList.ForEach(t => 
            t.Tag = tagsMap.TryGetValue(x.Id, out var tag) ? tag : null));
        
        return response;
    }
}
1 Like

Its too complicated for me. Is there a way to use EF Core to get data for tables like this one ?

I tried registering

services.AddDbContext<Baza>(options =>
options.UseSqlServer("someConnString"));

and adding

public Baza Baza { get; set; }

in MyServices.cs

and then using

public object Any(GetItems request)
{
return new GetItemsResponse { Results = Baza.Item.Take(5).ToList()};
}

But i just get error 500 and empty generated page on https://localhost:5001/items

I am using route “/items”

If i create new dummy List in code and populate it with random data it works just fine so i think it is the EF Core integration problem.

You should be able to use EF like any other dependency. What’s the full Exception StackTrace?

I dont see any exception.

Where can I look for the full Exception StackTrace ?

Do you have logging enabled? Can you post the raw HTTP Request/Response? Can use Chrome Web Inspector or Fiddler.

Request

GET https://localhost:5001/artikal HTTP/1.1
Host: localhost:5001
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,sr;q=0.8,hr;q=0.7,bs;q=0.6,sl;q=0.5
Cookie: _ga=GA1.1.372916457.1580622744; .AspNetCore.Culture=c%3Den%7Cuic%3Den; ss-opt=perm; .AspNet.ApplicationCookie=7teDCjd8m0TcUejWshl14hazs4iNYWeAl9YGp-k81BCcI1W9OeKiinfI7vg7M2u3Ecy0mQu0posKhoESk2yAj7AGCcmWIWcXERpOo47cvZR02gpiBUD-40cfwSdT768_NkXwCjjkLKtugum0AF3k11K1rWRW6jcUeTRBuunlNJXm6qWlwH8gMNVWz9ohP2U6JfaQEr0o5eXfr_uXCMbCrVBMYTKKCv4EC-i_ZkGMT6EexGPl4UZxZ2go2okBG9qORtlaXsy3N5u0IENIaPtgacFCUo0QNp5Ge0PYxpUm_G1ctvX4xG5QwHlMt29xLLfLU3b_kK2nn8sZJyFMSdKl3zWOeTTLz3DTumODjfQxwkQzLVC59i7WxmZGsVlRJNXfqx8thb5VaHkO3WcSoujdcGjEtgQIGV91sQTzL4opJ7qMreRZmne0pDzcAKNhPxuArOKQ4BWIZVteXuPxwoF7-En4FDNy93RrkbHchrcTlfUPNXjcmE-q4XXG5n_II0yg; crisp-client%2Fsession%2F8bcd8221-99f1-457a-8f3f-7be5efec8e82=session_1119d1af-9e8e-4816-af62-b8807cf1f97f; ss-id=32XrisvISEJyxZvyAr3a; ss-pid=YgWO03GvIULFMOL3mmd9

Response

HTTP/1.1 500 NullReferenceException
Date: Mon, 27 Jul 2020 00:34:58 GMT
Content-Type: text/html
Server: Kestrel
Transfer-Encoding: chunked
Vary: Accept
X-Powered-By: ServiceStack/5.91 NetCore/Windows

Says you have a NullReferenceException, does it happen when you debug the Service?

Do any of your models your Service is returning have any cyclical dependencies? As they’re not serializable.

I changed the code to return simple Customer POCO with 5 fields and enabled logging and the error is happening at

db.Customer.Take(5).ToList()

For what I see the db - Baza DBContext is null.

I tried many variations to initialize it…

Last was this

    private Baza db;

    public MyServices(Baza context)
    {
        db = context;
    }

But I still get the error

Your EF dependencies should work like any other registered IOC dependency, you’ll need to refer to Microsoft docs for how to configure your EF DataContext properly - we can provide support for any ServiceStack libraries, not 3rd party ones.

But I’ve quickly test that this works as expected by creating a new empty Web Template:

 $ x new web EFTest

Then add the EF dependency in both your EFTest.csproj & EFTest.ServiceInterface.csproj:

<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.6" />
  <PackageReference Include="ServiceStack" Version="5.*" />
</ItemGroup>

I then registered a basic BloggingContext as they refer to in their docs in ConfigureServices() which registers the IOC dependency. To create the tables I used the RelationalDatabaseCreator as mentioned in this StackOverflow answer which should only be run once to create the table and insert a row.

public class Startup : ModularStartup
{
    public new void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<BloggingContext>(opt => opt.UseSqlServer(str));
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        SeedBlogs(app); // run once to create Blogs table and insert row
        //..
    }

    private static void SeedBlogs(IApplicationBuilder app)
    {
        using var scope = app.ApplicationServices.CreateScope();
        using var context = scope.ServiceProvider.GetService<BloggingContext>();
        var databaseCreator = (RelationalDatabaseCreator) 
            context.Database.GetService<IDatabaseCreator>();
        databaseCreator.CreateTables();
        context.Blogs.Add(new Blog { Title = "Title", Content = "Content" });
        context.SaveChanges();
    }
}

The BloggingContext and all DB Models should be defined in your ServiceInterface project

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

    public DbSet<Blog> Blogs { get; set; }
}

public class Blog
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
}

[Route("/blogs")]
public class GetBlogs : IReturn<GetBlogsResponse> {}

public class GetBlogsResponse
{
    public List<Blog> Results { get; set; }
    public ResponseStatus ResponseStatus { get; set; }
}

The BloggingContext EF Dependency should be injected in your Services as normal:

public class MyServices : Service
{
    private BloggingContext db;
    public MyServices(BloggingContext db) => this.db = db;

    public object Any(GetBlogs request)
    {
        return new GetBlogsResponse { Results = db.Blogs.ToList() };
    }
}

And this should work as expected, with the BloggingContext being injected with the Service being able to use it in Services as above.

Another way to access scoped dependencies is using the ResolveScoped API, which resolves the dependency from .NET Core Request Scope:

public class MyServices : Service
{
    public object Any(GetBlogs request)
    {
        using (var db = Request.ResolveScoped<BloggingContext>())
        {
            return new GetBlogsResponse { Results = db.Blogs.ToList() };
        }
    }
}

Not sure what issue you’re having but I was able to use EF Core by following the Microsoft docs for configuring the DB Context,

If your Response DTO has a ResponseStatus property it should get injected with the Exception Info.

A quick way to inspect the Exception thrown in a Service is to override OnExceptionAsync() in your Service:

public class MyServices : Service
{
    public override Task<object> OnExceptionAsync(object request, Exception ex)
    {
        var s = ex.ToString();
        return base.OnExceptionAsync(request, ex);
    }
}

Whilst you can inspect every Service Exception by Overriding OnExceptionTypeFilter in your AppHost:

public class AppHost : AppHostBase
{
   public override void OnExceptionTypeFilter(Exception ex,ResponseStatus status)
   {
       var s = ex.ToString();
       base.OnExceptionTypeFilter(ex, status);
   }
}

This was all I needed. I searched for a way to see exceptions but couldn’t find it myself

I followed all the instructions from your previous post but it still didn’t work, but at the end I found this override to show exceptions

I lost almost 2 days for this

“Entity type ‘vwTExport’ has composite primary key defined with data annotations. To set composite primary key, use fluent API.”

But still I learned a lot.

Thanks a lot for your help

1 Like