0

How to load a OnetoMany Field when loading through APIController.

I have an Article model

public class Article : BaseEntity
{

    public string Title
    {
        get; set;
    }

    public Edition Edition
    {
        get; set;
    }

}

And an Edition model

public class Edition : BaseEntity
{
    public string Title
    {
        get; set;
    }

    public int Position { get; set; }

}

The BaseEntity model looks like that:

public class BaseEntity
{
    public Guid ID { get; set; }

    [Timestamp]
    public byte[] Timestamp { get; set; }

    public BaseEntity()
    {
        ID = Guid.NewGuid();
    }
}

I defined an HttpGet function in my Articles Controller, where I want to load all my articles.

[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesDefaultResponseType]
public ActionResult<IEnumerable<Article>> GetArticles([FromRoute] Guid editionId)
{
    return Ok(_context.Articles);
}

Unfortunately the EditionId is not loaded with the other fields. Here is what the JSON looks like:

[
    {
        "title": "Article Postman",
        "edition": null,
        "id": "74b53ba7-75a4-46c6-a70f-470e73c83ee5",
        "timestamp": "AAAAAAAAB+M="
    },
    {
        "title": "Title",
        "edition": null,
        "id": "d74b4ac3-7ddc-4c89-bd74-4fbe3fbe0cd8",
        "timestamp": "AAAAAAAAB+E="
    },
    {
        "title": "Article Postman 2",
        "edition": null,
        "id": "5dddd99f-151a-4325-91f7-c8295b872410",
        "timestamp": "AAAAAAAAB+U="
    }
]
Kr1
  • 1,269
  • 2
  • 24
  • 56
  • @Chayim Friedman, why did you remove your solution. Is it working well for me. Thank you – Kr1 Feb 26 '19 at 09:24

2 Answers2

2

Just make your property virtual for Lazy Loading:

public virtual Edition Edition
{
    get; set;
}

However, take a look at Lazy Loading vs Eager Loading., to see which one fits your case.

Update:

If you are using Lazy Loading, check to have the following statements in the Constructor of your DbContext:

Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;

Lazy Loading is a nice option in EF as far as it's used in the right place, because for each object, in order to fetch its relation, EF creates a new connection to database.
On the other hand, Eager Loading (Include()) loads all the related objects of your list in the first connection, many of which you will not probably use.
Depending on the number of objects to be fetched, you should choose between Lazy Loading and Eager Loading.

Community
  • 1
  • 1
Amir Molaei
  • 3,700
  • 1
  • 17
  • 20
2

I would suggest going for the simple solution that EF Core offers that is every time you call you data access layer for a specif data you add the .Include(x=>x.ReferenceIWantToLoad). This avoids any restructure in the project models to add the keyword 'virtual' to every Foreign Reference, example:

var allArticlesWithEditions = await _dbContext.Articles.Include(x=>x.Edition).ToListAsync();

PS: to be able to ignore infinite loop of references you want to add this to your startup file configuration :

services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );
Tiago Silva
  • 2,299
  • 14
  • 18
  • Thanks, this is what I do with Spring Framework. This approach works like a charm. – Kr1 Feb 26 '19 at 09:33
  • How to do the same when loading only one Article -> [var article = await _dbContext.Articles.FindAsync(id);]? – Kr1 Mar 01 '19 at 16:47
  • exactly the same but doesnt work with Find method there is a workaround but using firstordefaultasync its more easy and elegant _dbContext.Articles.Include(articles=>article.Edition).FirstOrDefaultAsync(x=>x.Id == id); by the way a week as passed you should accept either of the mentioned answers as the accepted one – Tiago Silva Mar 01 '19 at 16:55
  • I tried but didn't work "var article = await _dbContext.Articles.FindAsync(id); _dbContext.Articles.Include(articles => article.Edition).FirstOrDefaultAsync(x => x.ID == id);" – Kr1 Mar 04 '19 at 09:20
  • no I meant using .FirstOrDefault instead of Find there you can check how to include with Find but you wont be able to do it in 1 line ... var article = _dbContext.Articles.Include(articles => article.Edition).FirstOrDefaultAsync(x => x.ID == id); – Tiago Silva Mar 04 '19 at 09:21
  • I also tried that, but can't because there are some errors with 'Include' and 'articles => article.Edition' - > 'cannot use local variable article before it is declared – Kr1 Mar 04 '19 at 09:30
  • 1
    oh there was a typo ... "var article = _dbContext.Articles.Include(articles => articles.Edition).FirstOrDefaultAsync(x => x.ID == id);" on articles.Edition it was missing an 's' – Tiago Silva Mar 04 '19 at 10:03
  • Thanks, while waiting for your answer I was able to do it in another way: ---> Article article = await _dbContext.Articles.Include(a => a.Edition).SingleOrDefaultAsync(a => a.ID == id); – Kr1 Mar 04 '19 at 10:42
  • 1
    Yes that's exactly the same only with the withdraw that 'single' method throws an exception if there is more than one element in the sequence but for your scenario your ID will always be unique so its no problem. – Tiago Silva Mar 04 '19 at 10:47