实体框架6更新图

本文关键字:更新 框架 实体 | 更新日期: 2023-09-27 18:06:12

保存不知道状态的对象的图形的正确方法是什么?我所说的状态是指它们是新的还是现有的正在更新的数据库条目。

例如,如果我有:

public class Person
{
     public int Id { get; set; }
     public int Name { get; set; }
     public virtual ICollection<Automobile> Automobiles { get; set; }
}
public class Automobile
{
     public int Id { get; set; }
     public int Name { get; set; }
     public short Seats { get; set; }
     public virtual ICollection<MaintenanceRecord> MaintenanceRecords { get; set ;}
     public virtual Person Person { get; set; }
}
public class MaintenanceRecord
{
     public int Id { get; set; }
     public int AutomobileId { get; set; }
     public DateTime DatePerformed { get; set; }
     public virtual Automobile Automobile{ get; set; }
}

我正在编辑模型,类似于上面的这些对象,然后将这些模型传递到数据层保存,在这个实例中,我碰巧使用实体框架。因此,我正在将这些模型转换为DAL内部的POCO实体。

看起来,除非我的模型有一个状态,表明它们是新的还是更新的,否则我有相当多的工作要做,以"保存"更改。我必须首先选择Person实体并更新它,然后匹配任何现有的汽车并更新它们并添加任何新的,然后为每辆汽车检查任何新的或更新的维护记录。

是否有更快/更简单的方法来做这件事?这是可能的,我可以跟踪模型状态,我想这将是有帮助的,但这将意味着改变数据层之外的代码,我宁愿避免。我只是希望有一个使用模式,我可以遵循这样的更新。

实体框架6更新图

我在一段时间前遇到了这个问题,并一直在EF Codeplex网站上关注这个线程。https://entityframework.codeplex.com/workitem/864

似乎正在考虑下一个版本,我假设是EF 7,这显然是EF的一个相当大的内部改革。这可能值得一看……http://www.nuget.org/packages/RefactorThis.GraphDiff/

当我在做这件事的时候,我发现了另一个关于SO的EF帖子,有人有一个如何手动做到这一点的例子。当时我决定手动完成,不知道为什么,GraphDiff看起来很酷。这是我所做的一个例子。

  public async Task<IHttpActionResult> PutAsync([FromBody] WellEntityModel model)
    {
        try
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            var kne = TheContext.Companies.First();
            var entity = TheModelFactory.Create(model);
            entity.DateUpdated = DateTime.Now;
            var currentWell = TheContext.Wells.Find(model.Id);
            // Update scalar/complex properties of parent
            TheContext.Entry(currentWell).CurrentValues.SetValues(entity);
            //We don't pass back the company so need to attached the associated company... this is done after mapping the values to ensure its not null.
            currentWell.Company = kne;
            // Updated geometry - ARGHHH NOOOOOO check on this once in a while for a fix from EF-Team https://entityframework.codeplex.com/workitem/864
            var geometryItemsInDb = currentWell.Geometries.ToList();
            foreach (var geometryInDb in geometryItemsInDb)
            {
                // Is the geometry item still there?
                var geometry = entity.Geometries.SingleOrDefault(i => i.Id == geometryInDb.Id);
                if (geometry != null)
                    // Yes: Update scalar/complex properties of child
                    TheContext.Entry(geometryInDb).CurrentValues.SetValues(geometry);
                else
                    // No: Delete it
                    TheContext.WellGeometryItems.Remove(geometryInDb);
            }
            foreach (var geometry in entity.Geometries)
            {
                // Is the child NOT in DB?
                if (geometryItemsInDb.All(i => i.Id != geometry.Id))
                    // Yes: Add it as a new child
                    currentWell.Geometries.Add(geometry);
            }
            // Update Surveys
            var surveyPointsInDb = currentWell.SurveyPoints.ToList();
            foreach (var surveyInDb in surveyPointsInDb)
            {
                // Is the geometry item still there?
                var survey = entity.SurveyPoints.SingleOrDefault(i => i.Id == surveyInDb.Id);
                if (survey != null)
                    // Yes: Update scalar/complex properties of child
                    TheContext.Entry(surveyInDb).CurrentValues.SetValues(survey);
                else
                    // No: Delete it
                    TheContext.WellSurveyPoints.Remove(surveyInDb);
            }
            foreach (var survey in entity.SurveyPoints)
            {
                // Is the child NOT in DB?
                if (surveyPointsInDb.All(i => i.Id != survey.Id))
                    // Yes: Add it as a new child
                    currentWell.SurveyPoints.Add(survey);
            }
            // Update Temperatures - THIS IS A HUGE PAIN = HOPE EF is updated to handle updating disconnected graphs.
            var temperaturesInDb = currentWell.Temperatures.ToList();
            foreach (var tempInDb in temperaturesInDb)
            {
                // Is the geometry item still there?
                var temperature = entity.Temperatures.SingleOrDefault(i => i.Id == tempInDb.Id);
                if (temperature != null)
                    // Yes: Update scalar/complex properties of child
                    TheContext.Entry(tempInDb).CurrentValues.SetValues(temperature);
                else
                    // No: Delete it
                    TheContext.WellTemperaturePoints.Remove(tempInDb);
            }
            foreach (var temps in entity.Temperatures)
            {
                // Is the child NOT in DB?
                if (surveyPointsInDb.All(i => i.Id != temps.Id))
                    // Yes: Add it as a new child
                    currentWell.Temperatures.Add(temps);
            }
            await TheContext.SaveChangesAsync();
            return Ok(model);
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.Message);
        }
        return InternalServerError();
    }

这对我来说也是一个巨大的痛苦。我将@GetFuzzy的答案提取到一个更可重用的方法:

public void UpdateCollection<TCollection, TKey>(
    DbContext context, IList<TCollection> databaseCollection, 
    IList<TCollection> detachedCollection, 
    Func<TCollection, TKey> keySelector) where TCollection: class where TKey: IEquatable<TKey>
{
    var databaseCollectionClone = databaseCollection.ToArray();
    foreach (var databaseItem in databaseCollectionClone)
    {
        var detachedItem = detachedCollection.SingleOrDefault(item => keySelector(item).Equals(keySelector(databaseItem)));
        if (detachedItem != null)
        {
            context.Entry(databaseItem).CurrentValues.SetValues(detachedItem);
        }
        else
        {
            context.Set<TCollection>().Remove(databaseItem);
        }
    }
    foreach (var detachedItem in detachedCollection)
    {
        if (databaseCollectionClone.All(item => keySelector(item).Equals(keySelector(detachedItem)) == false))
        {
            databaseCollection.Add(detachedItem);
        }
    }
}

有了这个方法,我可以这样使用它:

public void UpdateProduct(Product product)
{
   ...
   var databaseProduct = productRepository.GetById(product.Id);
   UpdateCollection(context, databaseProduct.Accessories, product.Accessories, productAccessory => productAcccessory.ProductAccessoryId);
   UpdateCollection(context, databaseProduct.Categories, product.Categories, productCategory => productCategory.ProductCategoryId);
   ...
   context.SubmitChanges();
}

然而,当图形变得更深入时,我有一种感觉,这将是不够的。

您寻找的是工作单元模式:

http://msdn.microsoft.com/en-us/magazine/dd882510.aspx

您可以跟踪客户端上的UoW并将其与DTO一起传递,或者让服务器计算出它。可验证数据集和EF实体都有自己的UoW内部实现。对于一些独立的东西,有这个框架,但我从来没有使用过它,所以没有反馈:

http://genericunitofworkandrepositories.codeplex.com/

另一种选择是使用撤销功能进行实时更新,就像当你进入Gmail联系人时,它会保存你所做的更改,并提供撤销选项。

这取决于如何完成添加/更改实体。

我认为你可能在任何时候都试图对一个实体做太多的事情。同时允许编辑和添加可能会使您陷入一种情况,即您不确定正在对实体做什么,特别是在断开连接的场景中。除非要删除实体,否则一次只能对单个实体执行单个操作。这看起来很单调,但99%的用户都想要一个干净、容易理解的界面。很多时候,我们最终把应用程序的屏幕变成了"上帝"屏幕,在那里可以做任何事情。哪9/10次是不需要的(YAGNI)。

这样,当你编辑一个用户时,你知道你正在做一个更新操作。如果您正在添加一个新的维护记录,您知道您正在创建一个附加在汽车上的新记录。

总而言之,您应该限制单个屏幕可使用的操作数量,并确保为实体提供某种类型的唯一信息,以便您可以尝试查找实体以查看它是否存在。

我也遇到过类似的问题,却找不到自己的解决方案。我认为这个问题很复杂。我在扩展方法RefactoringThis中找到了在断开连接的场景中使用EF6更新图的完整解决方案。图表diff由Brent McKendric制作。

作者带来的例子是:

using (var context = new TestDbContext())  
{
    // Update the company and state that the company 'owns' the collection Contacts.
    context.UpdateGraph(company, map => map
        .OwnedCollection(p => p.Contacts, with => with
            .AssociatedCollection(p => p.AdvertisementOptions))
        .OwnedCollection(p => p.Addresses)
    );
    context.SaveChanges();
}

详情见:http://blog.brentmckendrick.com/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/