在新实体中加载导航属性的最佳方法

时间:2014-07-24 07:52:34

标签: c# linq entity-framework entity-framework-4

我正在尝试使用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();

    }    

统计实体中有导航ApiUser属性,我想将其加载到新的统计实体中。我曾尝试使用下面的代码加载导航属性,但它会产生大量查询并降低性能。有任何建议如何以其他方式加载导航属性?

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

    }

3 个答案:

答案 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();