在新实体中加载导航属性的最佳方式
本文关键字:属性 导航 最佳 方式 加载 新实体 实体 | 更新日期: 2023-09-27 18:14:26
我正在尝试使用EF添加新记录到SQL数据库。代码看起来像
public void Add(QueueItem queueItem)
{
var entity = queueItem.ApiEntity;
var statistic = new Statistic
{
Ip = entity.Ip,
Process = entity.ProcessId,
ApiId = entity.ApiId,
Result = entity.Result,
Error = entity.Error,
Source = entity.Source,
DateStamp = DateTime.UtcNow,
UserId = int.Parse(entity.ApiKey),
};
_statisticRepository.Add(statistic);
unitOfWork.Commit();
}
在统计实体中有导航Api
和User
属性,我想加载到新的统计实体中。我曾尝试使用下面的代码加载导航属性,但它会产生大量查询并降低性能。有什么建议如何以其他方式加载导航属性?
public Statistic Add(Statistic statistic)
{
_context.Statistic.Include(p => p.Api).Load();
_context.Statistic.Include(w => w.User).Load();
_context.Statistic.Add(statistic);
return statistic;
}
你们中的一些人可能有问题,为什么我要加载导航属性,同时添加新的实体,这是因为我在移动实体到数据库之前在DbContext.SaveChanges()
中执行一些计算。代码看起来像
public override int SaveChanges()
{
var addedStatistics = ChangeTracker.Entries<Statistic>().Where(e => e.State == EntityState.Added).ToList().Select(p => p.Entity).ToList();
var userCreditsGroup = addedStatistics
.Where(w => w.User != null)
.GroupBy(g => g.User )
.Select(s => new
{
User = s.Key,
Count = s.Sum(p=>p.Api.CreditCost)
})
.ToList();
//Skip code
}
所以上面的Linq不加载导航属性就不能工作,因为它使用了导航属性。
我还添加了统计实体的完整视图
public class Statistic : Entity
{
public Statistic()
{
DateStamp = DateTime.UtcNow;
}
public int Id { get; set; }
public string Process { get; set; }
public bool Result { get; set; }
[Required]
public DateTime DateStamp { get; set; }
[MaxLength(39)]
public string Ip { get; set; }
[MaxLength(2083)]
public string Source { get; set; }
[MaxLength(250)]
public string Error { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
public int ApiId { get; set; }
[ForeignKey("ApiId")]
public virtual Api Api { get; set; }
}
正如您所说,针对上下文的以下操作将生成大型查询:
_context.Statistic.Include(p => p.Api).Load();
_context.Statistic.Include(w => w.User).Load();
将所有统计数据和相关api实体的对象图物化,然后将所有统计数据和相关用户物化到统计上下文
只需将其替换为如下的单个调用,就可以将其减少为单个往返:
_context.Statistic.Include(p => p.Api).Include(w => w.User).Load();
一旦这些被加载,实体框架更改跟踪器将修复新统计实体上的关系,从而一次性为所有新统计填充api和用户的导航属性。
根据一次创建的新统计数据的数量与数据库中现有统计数据的数量,我非常喜欢这种方法。
然而,查看SaveChanges方法,似乎每个新统计数据都发生一次关系修复。例如,每次添加新的统计数据时,您都要查询数据库中的所有统计数据以及相关的api和用户实体,以触发新统计数据的关系修复。
在这种情况下,我更倾向于这样做:
_context.Statistics.Add(statistic);
_context.Entry(statistic).Reference(s => s.Api).Load();
_context.Entry(statistic).Reference(s => s.User).Load();
这将只查询新统计的Api和User,而不是查询所有统计。也就是说,您将为每个新的统计数据生成2个单行数据库查询。
或者,如果你要在一个批处理中添加大量的统计数据,你可以通过预先加载所有用户和api实体来利用上下文的本地缓存。也就是说,把所有用户和api实体预先缓存为2个大查询。
// preload all api and user entities
_context.Apis.Load();
_context.Users.Load();
// batch add new statistics
foreach(new statistic in statisticsToAdd)
{
statistic.User = _context.Users.Local.Single(x => x.Id == statistic.UserId);
statistic.Api = _context.Api.Local.Single(x => x.Id == statistic.ApiId);
_context.Statistics.Add(statistic);
}
有兴趣了解实体框架是否从其本地缓存中进行关系修复。例如,如果以下内容将在所有新统计数据上从本地缓存填充导航属性。待会儿有戏。
_context.ChangeTracker.DetectChanges();
免责声明:所有代码都是直接输入到浏览器中,所以要小心输入错误。
对不起,我没有时间测试,但EF映射实体到对象。因此不应该简单地给对象赋值:
public void Add(QueueItem queueItem)
{
var entity = queueItem.ApiEntity;
var statistic = new Statistic
{
Ip = entity.Ip,
Process = entity.ProcessId,
//ApiId = entity.ApiId,
Api = _context.Apis.Single(a => a.Id == entity.ApiId),
Result = entity.Result,
Error = entity.Error,
Source = entity.Source,
DateStamp = DateTime.UtcNow,
//UserId = int.Parse(entity.ApiKey),
User = _context.Users.Single(u => u.Id == int.Parse(entity.ApiKey)
};
_statisticRepository.Add(statistic);
unitOfWork.Commit();
}
我猜了一下你的名字,你应该在测试前调整
如何查找并只加载必要的列?
private readonly Dictionary<int, UserKeyType> _userKeyLookup = new Dictionary<int, UserKeyType>();
我不确定如何创建存储库,您可能需要在完成保存更改后或在事务开始时清理查找。
_userKeyLookup.Clean();
首先在查找中查找,如果没有找到则从上下文加载。
public Statistic Add(Statistic statistic)
{
// _context.Statistic.Include(w => w.User).Load();
UserKeyType key;
if (_userKeyLookup.Contains(statistic.UserId))
{
key = _userKeyLookup[statistic.UserId];
}
else
{
key = _context.Users.Where(u => u.Id == statistic.UserId).Select(u => u.Key).FirstOrDefault();
_userKeyLookup.Add(statistic.UserId, key);
}
statistic.User = new User { Id = statistic.UserId, Key = key };
// similar code for api..
// _context.Statistic.Include(p => p.Api).Load();
_context.Statistic.Add(statistic);
return statistic;
}
然后稍微改变一下分组。
var userCreditsGroup = addedStatistics
.Where(w => w.User != null)
.GroupBy(g => g.User.Id)
.Select(s => new
{
User = s.Value.First().User,
Count = s.Sum(p=>p.Api.CreditCost)
})
.ToList();