【问题标题】:Entity Framework 6.1: navigation properties not loadingEntity Framework 6.1:导航属性未加载
【发布时间】:2014-11-29 22:25:33
【问题描述】:

这是我第一次使用 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),但导航属性 item 为空,并且作为结果我最终得到一个 NullReferenceException。当我尝试访问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 导航属性在那里工作正常,我在“用户”实体及其映射器中找不到任何不同的做法。

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

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

任何帮助将不胜感激;这几天让我发疯。


编辑:

数据库是 SQL Server 2014。

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

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

然后,每当我从 repo 层调用任何内容时,我都可以实例化一个新的上下文:repo.GetTransactionsByUser(new MarketContext(), userId);

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

【问题讨论】:

  • 上下文的生命周期是多少?这是什么数据库?
  • 感谢您的浏览。我编辑了问题以提供更多信息。

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


【解决方案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

【讨论】:

  • 非常感谢,这似乎有效!我的印象是延迟加载意味着不需要包含调用?作为参考,我在MarketContext 构造函数中有Configuration.LazyLoadingEnabled = true;
【解决方案2】:

至于上下文生命周期,如果这是一个 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 所需的数据并且它可以防止实体化您无论如何都不会使用的实体以及相对昂贵的流程,例如更改跟踪和关系修复。

【讨论】:

    猜你喜欢
    • 2017-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多