实体框架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实体并更新它,然后匹配任何现有的汽车并更新它们并添加任何新的,然后为每辆汽车检查任何新的或更新的维护记录。
是否有更快/更简单的方法来做这件事?这是可能的,我可以跟踪模型状态,我想这将是有帮助的,但这将意味着改变数据层之外的代码,我宁愿避免。我只是希望有一个使用模式,我可以遵循这样的更新。
我在一段时间前遇到了这个问题,并一直在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/