实体框架6.1:导航属性未加载

时间:2014-11-29 22:25:33

标签: c# entity-framework-6 nullreferenceexception navigation-properties

这是我第一次使用Entity Framework 6.1(代码优先)。我一直遇到一个问题,我的导航属性是空的,当我不期望它们。我启用了延迟加载。

我的实体看起来像这样:

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; }
}

它有以下映射器:

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);
    }
}

具体来说,问题是我有一个Ask对象,其中正确设置了itemId(它对应于数据库中的Item),但是导航属性{{1为null,结果我得到一个NullReferenceException。当我尝试访问item

时,下面的代码中会抛出异常
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); } 导航属性在那里工作得很好,我找不到任何我在“用户”实体和其映射器中做过不同的事情。

我有一个重新创建的测试,但它没有任何问题:

seller

非常感谢任何帮助;这让我疯狂了几天。


编辑:

数据库是SQL Server 2014.

目前,我有一个共享上下文,实例化了上面的级别(我的数据库的存储库层)。我应该为每个方法实例化一个新的上下文吗?或者以尽可能低的级别(即每次数据库访问)实例化一个?例如:

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);
}

我的一些方法在repo层中调用其他方法。为每个方法获取一个上下文会更好吗,然后它可以传递给它调用的任何方法吗?

public IQueryable<Ask> GetAsksBySeller(int sellerId)
{
    using (MarketContext _ctx = new MarketContext())
    {
        return _ctx.Asks.Where(s => s.seller.id == sellerId).AsQueryable();
    }
}

然后,只要我从repo层调用任何内容,我就可以实例化一个新的上下文:public IQueryable<Transaction> GetTransactionsByUser(MarketContext _ctx, int userId) { IQueryable<Transaction> buyTransactions = GetTransactionsByBuyer(_ctx, userId); IQueryable<Transaction> sellTransactions = GetTransactionsBySeller(_ctx, userId); return buyTransactions.Concat(sellTransactions); }

再次感谢您的帮助。我是新手,不知道哪种方法最好。

2 个答案:

答案 0 :(得分:1)

尝试添加 Include调用您的存储库调用:

public IQueryable<Ask> GetAsksBySeller(int sellerId)
{
    using (MarketContext _ctx = new MarketContext())
    {
        return _ctx.Asks
               .Include("seller")
               .Include("item")
               .Where(s => s.seller.id == sellerId).AsQueryable();
    }
}

此外,还有一个扩展方法Include,它接受​​lambda表达式作为参数,并为您提供编译时的类型检查

http://msdn.microsoft.com/en-us/data/jj574232.aspx

答案 1 :(得分:0)

对于上下文生命周期,如果这是一个Web应用程序,则每个请求应该共享一个上下文。否则它会更随意,但它应该类似于每个用例或服务调用的上下文。

因此模式将是:创建上下文,将其传递给调用中涉及的存储库,执行任务并处置上下文。上下文可以看作是您的工作单元,因此无论涉及多少个存储库,最后一个SaveChanges()通常应该足以提交所有更改。

我无法判断这是否会解决延迟加载问题,因为从我看到的情况来看,我无法解释为什么它不会发生。

但是,如果我在你的鞋子里,我想要深究它,懒惰的装载是不应该依赖的东西。看看你的(删节)代码:

foreach (Ask a in asks)
{
    ReducedAsk r = new ReducedAsk()
                   { 
                       sellerName = a.seller.username,
                       itemName = a.item.name
                   };

如果延迟加载会按预期工作,那么对于循环的每次迭代,这将对数据库执行两次查询。当然,这是非常低效的。这就是为什么使用Include(如在安东的回答中)更好的方式,不仅是为了规避你的问题。

进一步优化是在查询本身中进行投影(即new {):

var reducedAsks = repo.GetAsksBySeller(userId)
                      .Select(a => new ReducedAsk() { ... })
                      .ToList();

(假设 - 并要求 - repo.GetAsksBySeller返回IQueryable)。

现在,只有从数据库中获取创建ReducedAsk所需的数据它才会阻止您不使用的实体的实现以及相对昂贵的流程,如更改跟踪和关系修正。