简体   繁体   English

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

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

I am trying to add new record into SQL database using EF. 我正在尝试使用EF将新记录添加到SQL数据库中。 The code looks like 代码看起来像

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

    }    

There is navigation Api and User properties in Statistic entity which I want to load into new Statistic entity. 我想加载到新的统计实体中的统计实体中有导航ApiUser属性。 I have tried to load navigation properties using code below but it produce large queries and decrease performance. 我试图使用下面的代码加载导航属性,但它会产生大量查询并降低性能。 Any suggestion how to load navigation properties in other way? 有什么建议如何以其他方式加载导航属性?

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

Some of you may have question why I want to load navigation properties while adding new entity, it's because I perform some calculations in DbContext.SaveChanges() before moving entity to database. 某些人可能会问为什么我要在添加新实体时加载导航属性,这是因为我在将实体移至数据库之前在DbContext.SaveChanges()执行了一些计算。 The code looks like 代码看起来像

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

}

So the Linq above will not work without loading navigation properties because it use them. 因此,上面的Linq在不加载导航属性的情况下将无法工作,因为它使用了它们。

I am also adding Statistic entity for full view 我还添加了统计实体以进行全视图

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

    }

As you say, the following operations against your context will generate large queries: 如您所说,针对您的上下文执行以下操作将生成大型查询:

_context.Statistic.Include(p => p.Api).Load();
_context.Statistic.Include(w => w.User).Load();

These are materialising the object graphs for all statistics and associated api entities and then all statistics and associated users into the statistics context 这些正在为所有统计信息和关联的api实体具体化对象图,然后将所有统计信息和关联的用户纳入统计信息上下文中

Just replacing this with a single call as follows will reduce this to a single round trip: 只需按以下方式将其替换为一个电话,即可将其减少为一次往返:

_context.Statistic.Include(p => p.Api).Include(w => w.User).Load();

Once these have been loaded, the entity framework change tracker will fixup the relationships on the new statistics entities, and hence populate the navigation properties for api and user for all new statistics in one go. 加载完这些后,实体框架变更跟踪器将修正新统计实体上的关系,从而一次性填充所有新统计的api和用户的导航属性。

Depending on how many new statistics are being created in one go versus the number of existing statistics in the database I quite like this approach. 我非常喜欢这种方法,具体取决于一次要创建多少新统计信息与数据库中现有统计信息的数量。

However, looking at the SaveChanges method it looks like the relationship fixup is happening once per new statistic. 但是,查看SaveChanges方法,似乎每个新统计信息都会发生一次关系修正。 Ie each time a new statistic is added you are querying the database for all statistics and associated api and user entities to trigger a relationship fixup for the new statistic. 即,每次添加新统计信息时,您都在数据库中查询所有统计信息以及关联的api和用户实体,以触发新统计信息的关系修正。

In which case I would be more inclined todo the following: 在这种情况下,我将更倾向于执行以下操作:

_context.Statistics.Add(statistic);
_context.Entry(statistic).Reference(s => s.Api).Load();
_context.Entry(statistic).Reference(s => s.User).Load();

This will only query for the Api and User of the new statistic rather than for all statistics. 这只会查询新统计信息的“ Api”和“用户”,而不是所有统计信息。 Ie you will generate 2 single row database queries for each new statistic. 即,您将为每个新统计信息生成2个单行数据库查询。

Alternatively, if you are adding a large number of statistics in one batch, you could make use of the Local cache on the context by preloading all users and api entities upfront. 或者,如果要在一批中添加大量统计信息,则可以通过预先预载所有用户和api实体来利用上下文的本地缓存。 Ie take the hit upfront to pre cache all user and api entities as 2 large queries. 即,先将热门内容预先缓存为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);
}

Would be interested to find out if Entity Framework does relationship fixup from its local cache. 有兴趣找出Entity Framework是否从其本地缓存进行关系修复。 Ie if the following would populate the navigation properties from the local cache on all the new statistics. 也就是说,以下内容是否将填充所有新统计信息中来自本地缓存的导航属性。 Will have a play later. 以后会玩。

_context.ChangeTracker.DetectChanges();

Disclaimer: all code entered directly into browser so beware of the typos. 免责声明:所有直接输入到浏览器中的代码都请注意输入错误。

Sorry I dont have the time to test that, but EF maps entities to objects. 抱歉,我没有时间进行测试,但是EF将实体映射到对象。 Therefore shouldnt simply assigning the object work: 因此,不应简单地分配对象工作:

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

}    

I did a little guessing of your namings, you should adjust it before testing 我对您的命名有一些猜测,请在测试之前进行调整

How about make a lookup and load only necessary columns. 如何进行查找并仅加载必要的列。

private readonly Dictionary<int, UserKeyType> _userKeyLookup = new Dictionary<int, UserKeyType>();

I'm not sure how you create a repository, you might need to clean up the lookup once the saving changes is completed or in the beginning of the transaction. 我不确定如何创建存储库,一旦保存更改完成或在事务开始时,您可能需要清理查找。

_userKeyLookup.Clean();

First find in the lookup, if not found then load from context. 首先在查找中找到,如果找不到,则从上下文中加载。

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

Then change the grouping a little. 然后稍微更改分组。

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.

相关问题 在 EF 4.1 中加载所有导航属性及其子项的最佳方式是什么 - What is the best way to load all navigation properties and their children in EF 4.1 在Entity Framework上预先加载导航属性 - Eager load navigation properties on Entity Framework 如何在实体框架中加载嵌套导航属性? - How to Load Nested navigation properties in Entity Framework? 实体框架不会加载导航属性 - Entity framework does not load navigation properties 有关“实体数据库优先”的导航属性的最佳实践 - Best practices about navigation properties with Entity Database First 如何为实体 object 加载导航集合的属性 - How to load a navigation collection's properties for an entity object 实体框架导航属性未加载直到新的DbContext已建立 - Entity Framework Navigation Properties not loading til new DbContext is established 实体框架从导航属性创建新条目 - Entity framework creating new entries from navigation properties 实体框架将仅保存新实体的导航属性,而不更新 - Entity Framework will only save navigation properties for new entities, not updates 有没有填充实体而不将实体保存到数据库的方法? - Is there a way to fill navigation properties without saving an entity to the database?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM