SB2055
SB2055

Reputation: 12852

EF Navigation Properties not working?

I have a Card class:

public class Card
{
    public Card(){}

    [Key]
    [Required]
    public virtual int CardId { get; set; }

    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }
    }
}

And a Stage class:

public class Stage
{
    [Key]
    public virtual int StageId { get; set; }

    [DataMember(EmitDefaultValue = true)]
    public string Name { get; set; }

    public long Ticks { get; set; }

    [NotMapped]
    public TimeSpan Span
    {
        get { return TimeSpan.FromTicks(Ticks); }
    }

    // Make sure that these stages are generated accordingly
    public static class Ids
    {
        // Zero
        public const int One = 1;
        // Ten Seconds
        public const int Two = 2;
        // One Minute
    }

}

In my repository service I have an AddCard method:

public Card AddCard(Card card)
    {
        int parentId = 0;
        Set parentSet = null;
        if (card.ParentSetId.HasValue)
        {
            parentId = card.ParentSetId.Value;
            parentSet = GetSet(parentId);
        }

        card.ParentSet = parentSet;
        card.ParentSetId = parentSet.SetId;

        card.StageId = Stage.Ids.One;  // Set Id here with hopes of getting card.Stage to resolve as a nav property

        _db.Cards.Add(card);

        SaveChanges();

        return card;
    }

But for some reason, all of my Cards have a null value for Card.Stage - where Card.StageId is always some integer. I'm trying to get the navigation property working so I can access properties of the Stage through a Card.

What have I done wrong here?

Update:

The property is null when I try to access them via my test. First I create the card (in my test code):

 var cardDto = new CardDto  // Works!
            {
                Details = "Test Card",
            };

Then I send it to my Dto Service (also in test code):

 var fullCardDto = _service.AddCard(cardDto); // breaks because Stage is null when stageId is not

CardDto Class:

public class CardDto
{
    public CardDto(){}

    public CardDto(Card card)
    {
        CardId = card.CardId;
        Stage = card.Stage.Name;  // Fails here on its way back - creating the initial DTO works
        Details = card.Details;
    }

    [Key]
    [DataMember(EmitDefaultValue = true)]
    public int CardId { get; set; }


    [DataMember(EmitDefaultValue = true)]
    public string Stage { get; set; }

    [DataMember(IsRequired = false)]
    public string Details { get; set; }

    public Card ToEntity()
    {
        var newCard = new Card
            {
             CardId = CardId,
             Details = Details,
            };
        return newCard;
}

_service is DTO service:

public CardDto AddCard(CardDto card)
    {
        return new CardDto(_repository.AddCard(card.ToEntity()));
    }

_repository code is above.

This works all the way through the _repository call, but if I debug the _repository.AddCard method, StageId has a value after I call SaveChanges(), but Stage does not.

The Card is passed back up the chain until it's supposed to be converted to a Dto again, where it fails due to the null Stage.

What's odd is that this code works outside of the test - if I use a client to hit AddCard, I get a card back without any errors.

Upvotes: 0

Views: 698

Answers (1)

Jayantha Lal Sirisena
Jayantha Lal Sirisena

Reputation: 21366

To make Lazy loading to work you need to have a proxy created. If you are loading Card though the EF you will automatically received a object wrapped in a proxy object. You can do two things here
1. To you use lazy loading you can create a proxy like this,

public Card AddCard(Card card)
    {
        var cardProxy= _db.Cards.Create(); 
        //and copy all values from card to cardProxy here..
        int parentId = 0;
        Set parentSet = null;
        if (cardProxy.ParentSetId.HasValue)
        {
            parentId = cardProxy.ParentSetId.Value;
            parentSet = GetSet(parentId);
        }
        cardProxy.ParentSet = parentSet;
        cardProxy.ParentSetId = parentSet.SetId;

        cardProxy.StageId = Stage.Ids.One;  // Set Id here with hopes of getting card.Stage to resolve as a nav property

          SaveChanges();

        return cardProxy;
}
  1. Or you can use explicit loading,

    public Card AddCard(Card card) { int parentId = 0; Set parentSet = null; if (card.ParentSetId.HasValue) { parentId = card.ParentSetId.Value; parentSet = GetSet(parentId); }

            card.ParentSet = parentSet;
            card.ParentSetId = parentSet.SetId;
    
            card.StageId = Stage.Ids.One;  // Set Id here with hopes of getting card.Stage to resolve as a nav property
    
            _db.Cards.Add(card);
    
            SaveChanges();
    
            context.Entry(card).Reference(p => p.Stage).Load();//load stage here
    
           return card;
        }
    

Upvotes: 1

Related Questions