This is my first time using Entity Framework 6.1 (code first). I keep running into a problem where my navigation properties are null when I don't expect them to be. I've enabled lazy loading.
My entity looks like this:
public class Ask
{
public Ask()
{
this.quantity = -1;
this.price = -1;
}
public int id { get; set; }
public int quantity { get; set; }
public float price { get; set; }
public int sellerId { get; set; }
public virtual User seller { get; set; }
public int itemId { get; set; }
public virtual Item item { get; set; }
}
It has the following mapper:
class AskMapper : EntityTypeConfiguration<Ask>
{
public AskMapper()
{
this.ToTable("Asks");
this.HasKey(a => a.id);
this.Property(a => a.id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(a => a.id).IsRequired();
this.Property(a => a.quantity).IsRequired();
this.Property(a => a.price).IsRequired();
this.Property(a => a.sellerId).IsRequired();
this.HasRequired(a => a.seller).WithMany(u => u.asks).HasForeignKey(a => a.sellerId).WillCascadeOnDelete(true);
this.Property(a => a.itemId).IsRequired();
this.HasRequired(a => a.item).WithMany(i => i.asks).HasForeignKey(a => a.itemId).WillCascadeOnDelete(true);
}
}
Specifically, the problem is that I have an Ask
object with a correctly set itemId
(which does correspond to an Item
in the database), but the navigation property item
is null, and as a result I end up getting a NullReferenceException. The exception is thrown in the code below, when I try to access a.item.name
:
List<Ask> asks = repo.GetAsksBySeller(userId).ToList();
List<ReducedAsk> reducedAsks = new List<ReducedAsk>();
foreach (Ask a in asks)
{
ReducedAsk r = new ReducedAsk() { id = a.id, sellerName = a.seller.username, itemId = a.itemId, itemName = a.item.name, price = a.price, quantity = a.quantity };
reducedAsks.Add(r);
}
Confusingly, the seller
navigation property is working fine there, and I can't find anything I've done differently in the 'User' entity, nor in its mapper.
I have a test which recreates this, but it passes without any problems:
public void canGetAsk()
{
int quantity = 2;
int price = 10;
//add a seller
User seller = new User() { username = "ted" };
Assert.IsNotNull(seller);
int sellerId = repo.InsertUser(seller);
Assert.AreNotEqual(-1, sellerId);
//add an item
Item item = new Item() { name = "fanta" };
Assert.IsNotNull(item);
int itemId = repo.InsertItem(item);
Assert.AreNotEqual(-1, itemId);
bool success = repo.AddInventory(sellerId, itemId, quantity);
Assert.AreNotEqual(-1, success);
//add an ask
int askId = repo.InsertAsk(new Ask() { sellerId = sellerId, itemId = itemId, quantity = quantity, price = price });
Assert.AreNotEqual(-1, askId);
//retrieve the ask
Ask ask = repo.GetAsk(askId);
Assert.IsNotNull(ask);
//check the ask info
Assert.AreEqual(quantity, ask.quantity);
Assert.AreEqual(price, ask.price);
Assert.AreEqual(sellerId, ask.sellerId);
Assert.AreEqual(sellerId, ask.seller.id);
Assert.AreEqual(itemId, ask.itemId);
Assert.AreEqual(itemId, ask.item.id);
Assert.AreEqual("fanta", ask.item.name);
}
Any help would be extremely appreciated; this has been driving me crazy for days.
EDIT:
The database is SQL Server 2014.
At the moment, I have one shared context, instantiated the level above this (my repository layer for the db). Should I be instantiating a new context for each method? Or instantiating one at the lowest possible level (i.e. for every db access)? For example:
public IQueryable<Ask> GetAsksBySeller(int sellerId)
{
using (MarketContext _ctx = new MarketContext())
{
return _ctx.Asks.Where(s => s.seller.id == sellerId).AsQueryable();
}
}
Some of my methods invoke others in the repo layer. Would it better for each method to take a context, which it can then pass to any methods it calls?
public IQueryable<Transaction> GetTransactionsByUser(MarketContext _ctx, int userId)
{
IQueryable<Transaction> buyTransactions = GetTransactionsByBuyer(_ctx, userId);
IQueryable<Transaction> sellTransactions = GetTransactionsBySeller(_ctx, userId);
return buyTransactions.Concat(sellTransactions);
}
Then I could just instantiate a new context whenever I call anything from the repo layer: repo.GetTransactionsByUser(new MarketContext(), userId);
Again, thanks for the help. I'm new to this, and don't know which approach would be best.
Try to add Include call in your repository call:
Also, there is an extension method Include which accepts lambda expression as parameter and provides you type checks on compile time
http://msdn.microsoft.com/en-us/data/jj574232.aspx