首页 > 解决方案 > Ef 返回 null,但实体存在

问题描述

我正在尝试在代码中加载一些用户信息,但 EF 返回 null。

foreach (var user in allAutoUsers)
{
    Wallet wallet = db.CabinetUsers
                      .Find(user.IdCabinetUser)?
                      .Wallets
                      .FirstOrDefault(x => x.TypeCurrency == currency);
}

可变用户报告 1 个钱包,但是当我尝试在上面的代码中获取它时,它返回 null。

有一些方法可以解决这个问题吗?

标签: c#.netdatabaseentity-frameworklinq

解决方案


更多地阅读 Linq 表达式,而不是依赖Find. 如果您的实体之间的关系未定义为virtual阻止 EF 延迟加载它们,您可能会遇到问题。

using 的问题.Find()是,如果实体存在,它将返回实体,但是,尝试访问 DbContext 尚未意识到的任何相关属性将需要延迟加载调用。如果延迟加载被禁用,或者成员不是,这很容易被忽略virtual,并且在启用它时可能是性能问题。

相反,Linq 可以让您直接通过对象图进行查询以获得您想要的:

foreach (var user in allAutoUsers)
{
    Wallet wallet = db.CabinetUsers
       .Where(x => x.IdCabinetUser == user.IdCabinetUser)
       .SelectMany(x => x.Wallets)
       .SingleOrDefault(x => x.TypeCurrency == currency);

   // do stuff with wallet...
}

这假设每个用户只有 1 个指定货币的钱包。当期望 0 或 1 时,使用SingleOrDefault. FirstOrDefault仅在您期望 0 或多个、想要“第一个”并指定一个OrderBy子句以确保第一个项目是可预测的时使用。

这将导致每个用户的查询。为所有用户完成 1 个查询:

var userIds = allAutoUsers.Select(x => x.IdCabinetUser).ToList();
var userWallets = db.CabinetUsers
       .Where(x => userIds.Contains(x.IdCabinetUser))
       .Select(x => new 
       {
          x.IdCabinetUser,
          Wallet = x.SelectMany(x => x.Wallets)
            .SingleOrDefault(x => x.TypeCurrency == currency);
       }).ToList();

从这里我会考虑用你真正关心的钱包中的细节来扩展钱包SelectManySelect而不是引用整个钱包实体。这样做的好处是可以加快查询速度,减少内存使用,并避免在 Wallet 引用稍后涉及的任何其他实体时延迟加载调用导致问题发生的可能性。

例如,如果您只需要 IdWallet、WalletName、TypeCurrency 和 Balance:

// replace this line from above...
          Wallet = x.SelectMany(x => x.Wallets)

// with ...
          Wallet = x.SelectMany(x => x.Wallets.Select(w => new 
          { 
              w.IdWallet,
              w.WalletName,
              w.TypeCurrency,
              w.Ballance
          }) // ...

从那里您foreach无需额外查询即可满足您的需求:

foreach ( var userWallet in userWallets)
{
   // do stuff with userWallet.Wallet and userWallet.IdCabinetUser.
}

如果您想将钱包详细信息返回给调用方法或视图等,那么您不能为此使用匿名类型(new { })。相反,您需要为要返回和使用的数据定义一个简单的类Select。即new WalletDTO { IdWallet = w.IdWallet, //... }即使您使用实体,也建议将它们减少为 DTO 或 ViewModel,而不是返回实体。实体不应该比产生它们的 DbContext “存活”更长的时间,否则你会得到各种讨厌的行为,比如ObjectDisposedException和序列化异常。


推荐阅读