繁体   English   中英

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

[英]Best way to load navigation properties in new entity

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

    }

如您所说,针对您的上下文执行以下操作将生成大型查询:

_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”和“用户”,而不是所有统计信息。 即,您将为每个新统计信息生成2个单行数据库查询。

或者,如果要在一批中添加大量统计信息,则可以通过预先预载所有用户和api实体来利用上下文的本地缓存。 即,先将热门内容预先缓存为2个大型查询,然后将所有用户和api实体预先缓存。

// 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();

免责声明:所有直接输入到浏览器中的代码都请注意输入错误。

抱歉,我没有时间进行测试,但是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();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM