我正在尝试使用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; }
}
答案 0 :(得分:4)
正如您所说,针对您的上下文的以下操作将生成大型查询:
_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和user的导航属性。
根据一次创建的新统计信息数量与数据库中现有统计信息的数量,我非常喜欢这种方法。
然而,看看SaveChanges方法,看起来每个新统计数据都会发生一次关系修正。即每次添加新统计信息时,您都在查询数据库中的所有统计信息以及相关的api和用户实体,以触发新统计信息的关系修复。
在这种情况下,我会更倾向于以下内容:
_context.Statistics.Add(statistic);
_context.Entry(statistic).Reference(s => s.Api).Load();
_context.Entry(statistic).Reference(s => s.User).Load();
这将仅查询新统计信息的Api和用户,而不是查询所有统计信息。即,您将为每个新统计信息生成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);
}
有兴趣了解Entity Framework是否从其本地缓存执行关系修正。 即如果以下内容将在所有新统计信息上填充本地缓存中的导航属性。稍后会玩。
_context.ChangeTracker.DetectChanges();
免责声明:所有代码都直接输入浏览器,因此请注意拼写错误。
答案 1 :(得分:0)
抱歉,我没有时间对其进行测试,但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();
}
我对你的命名做了一些猜测,你应该在测试前调整它
答案 2 :(得分:0)
如何进行查找并仅加载必要的列。
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();