我得到这个错误时,我GetById()在一个实体,然后设置子实体的集合到我的新列表,来自MVC视图。
操作失败
关系是无法改变的
因为一个或多个外键
Properties是非空的。当一个
关系发生了变化
相关外键属性设置为
空值。如果外键是
不支持空值,新建
关系必须被定义
必须分配外键属性
另一个非空值或
必须删除不相关的对象。
我不太理解这句话:
这种关系无法改变
因为一个或多个外键
Properties是非空的。
我为什么要改变两个实体之间的关系?它应该在整个应用程序的生命周期内保持不变。
发生异常的代码只是简单地将集合中修改过的子类分配给现有的父类。这将有望满足取消子类,增加新的和修改。我本以为实体框架处理这个。
代码行可以提炼为:
var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
我也解决了我的问题与Mosh的答案,我认为PeterB的答案是有点,因为它使用枚举作为外键。请记住,在添加这段代码之后,您将需要添加一个新的迁移。
我也可以推荐这篇博客的其他解决方案:
http://www.kianryan.co.uk/2013/03/orphaned-child/
代码:
public class Child
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Heading { get; set; }
//Add other properties here.
[Key, Column(Order = 1)]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
我也解决了我的问题与Mosh的答案,我认为PeterB的答案是有点,因为它使用枚举作为外键。请记住,在添加这段代码之后,您将需要添加一个新的迁移。
我也可以推荐这篇博客的其他解决方案:
http://www.kianryan.co.uk/2013/03/orphaned-child/
代码:
public class Child
{
[Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Heading { get; set; }
//Add other properties here.
[Key, Column(Order = 1)]
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
我只是犯了同样的错误。
我有两个具有父子关系的表,但是我在子表的表定义中的外键列上配置了“on delete cascade”。
因此,当我手动删除父行(通过SQL)在数据库中,它将自动删除子行。
然而,这在EF中不起作用,出现了这个线程中描述的错误。
原因是,在我的实体数据模型(edmx文件)中,父表和子表之间的关联属性不正确。
End1的OnDelete选项被配置为none(“End1”在我的模型中是具有1的多重性的结束)。
我手动将End1 OnDelete选项改为Cascade,然后它就工作了。
我不知道为什么EF不能拾取这个,当我从数据库更新模型(我有一个数据库第一模型)。
为了完整起见,这是我删除代码的样子:
public void Delete(int id)
{
MyType myObject = _context.MyTypes.Find(id);
_context.MyTypes.Remove(myObject);
_context.SaveChanges();
}
如果我没有定义级联删除,我将不得不在删除父行之前手动删除子行。
你必须手动清除ChildItems集合,并在其中添加新项目:
thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);
之后,您可以调用DeleteOrphans扩展方法,它将处理孤立的实体(它必须在DetectChanges和SaveChanges方法之间调用)。
public static class DbContextExtensions
{
private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();
public static void DeleteOrphans( this DbContext source )
{
var context = ((IObjectContextAdapter)source).ObjectContext;
foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var entityType = entry.EntitySet.ElementType as EntityType;
if (entityType == null)
continue;
var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
var props = entry.GetModifiedProperties().ToArray();
foreach (var prop in props)
{
NavigationProperty navProp;
if (!navPropMap.TryGetValue(prop, out navProp))
continue;
var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
var enumerator = related.GetEnumerator();
if (enumerator.MoveNext() && enumerator.Current != null)
continue;
entry.Delete();
break;
}
}
}
private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
{
var result = type.NavigationProperties
.Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
.Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
.Where(v => v.DependentProperties.Length == 1)
.ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);
return new ReadOnlyDictionary<string, NavigationProperty>(result);
}
}
如果你在同一个类上使用AutoMapper和实体框架,你可能会遇到这个问题。例如,如果你的类是
class A
{
public ClassB ClassB { get; set; }
public int ClassBId { get; set; }
}
AutoMapper.Map<A, A>(input, destination);
这将尝试复制两个属性。在这种情况下,ClassBId是非空的。因为AutoMapper将复制目标。ClassB = input.ClassB;这将导致一个问题。
将您的AutoMapper设置为Ignore ClassB属性。
cfg.CreateMap<A, A>()
.ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId