这是我第一次使用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);
}
再次感谢您的帮助。我是新手,不知道哪种方法最好。
答案 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表达式作为参数,并为您提供编译时的类型检查
答案 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
所需的数据和它才会阻止您不使用的实体的实现以及相对昂贵的流程,如更改跟踪和关系修正。