![](/img/trans.png)
[英]Entity Framework - Foreign Key not set (0 / null) but navigation property is not null
[英]Entity Framework set navigation property to null
我有一个实体框架数据库第一个项目。 这是模型的提取:
public partial class LedProject
{
public LedProject()
{
this.References = new HashSet<LedProjectReference>();
this.Results = new HashSet<LedProjectResult>();
this.History = new HashSet<LedProjectHistory>();
}
public string Identifier { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> CompletionDate { get; set; }
public System.DateTime CreationDate { get; set; }
public System.Guid ProjectId { get; set; }
public string Comment { get; set; }
public virtual User ContactUser { get; set; }
public virtual User CreationUser { get; set; }
public virtual Customer Customer { get; set; }
public virtual LedProjectAccounting Accounting { get; set; }
public virtual LedProjectState State { get; set; }
public virtual ICollection<LedProjectReference> References { get; set; }
public virtual ICollection<LedProjectResult> Results { get; set; }
public virtual User ResponsibleUser { get; set; }
public virtual ICollection<LedProjectHistory> History { get; set; }
}
public partial class User
{
public System.Guid UserId { get; set; }
public string LoginName { get; set; }
public System.DateTime CreationDate { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
}
我在设置类LedProject
的导航项ResponsibleUser
时LedProject
。 当我将ResponsibleUser
用户设置为另一个用户并随后保存 DBContext 的更改时,更改将存储在数据库中。
但是,当我想删除LedProject
的当前ResponsibleUser
LedProject
,通过将导航属性设置为空。 更改不会存储在数据库中。
LedProject project = db.LedProject.Find(projectId);
project.Name = string.IsNullOrEmpty(name) ? null : name;
...
project.ResponsibleUser = responsibleUser == null ? null : db.User.Find(responsibleUser.UserId);
...
db.SaveChanges();
删除导航属性有什么技巧吗?
问题在于导航属性的延迟加载。 似乎该值首先设置为空,然后从数据库加载。 因此,数据库中当前存储的值会覆盖所需的值(在我的情况下为null )。
LedProject project = db.LedProject
.Include("ResponsibleUser")
.Where(p => p.ProjectId == projectId)
.FirstOrDefault();
这会在加载项目时加载责任用户。 这终于解决了我的问题!
就像boindiil所说的,问题在于延迟加载。 但是,只有在想要将其置空时才需要加载该属性,以便实体框架机制知道它已更改。 代码可能如下所示:
responsibleUser = responsibleUser == null ? null : db.User.Find(responsibleUser.UserId);
if (responsibleUser == null)
{
// load the value to assure setting it to null will cause a change
var dummy = project.ResponsibleUser;
}
project.ResponsibleUser = responsibleUser;
...
db.SaveChanges();
我一直在想应该有一种方法可以使用 db.ChangeTracker 在没有负载的情况下强制保存,但我还没有找到它(我尝试过的几件事看起来真的很糟糕)。
找出最好的方法来做到这一点,而不必急于加载导航属性,这样您仍然可以使用 EF 的Find()
而不必进行黑客攻击。
在导航属性旁边使用原始 ID,其中类型是导航属性 ID 类型(通常为用户的字符串),例如:
public partial class LedProject
{
public string ResponsibleUserId { get; set; }
public virtual User ResponsibleUser { get; set; }
}
在您创建记录的任何位置使用导航属性更新字符串,然后当您想要删除关系时,只需执行ledProject.ResponsibleUserId = null
。
如果您在末尾将 id 命名为导航属性名称 + id 以外的名称,那么您将需要使用注释或 fluent api 来映射我认为。
更多信息: 在什么情况下我需要实体框架中的外键和导航属性
从实体框架 5.0 开始:
db.Entry(project).Reference(p => p.ResponsibleUser).CurrentValue = null;
请参阅https://docs.microsoft.com/en-us/ef/ef6/fundamentals/relationships 。
创建和修改关系部分解释了在分配和设置为 null 时外键属性和导航属性会发生什么。
EF5 及以后有一些变化,但它们的关键是定义外键属性,使关系不再是独立关联(缺少外键属性)。
我遇到了这个问题并提出了一个不会破坏延迟加载的小“黑客”。
只需像这样定义模型上的属性 -
public int? AccountId { get; set; }
//workaround for entity framework lazy loading problem
Account account;
public virtual Account Account
{
get
{
return account;
}
set
{
account = value;
if (value == null)
{
AccountId = null;
}
}
}
现在您不必急切地加载导航属性,将其设置为 null 即可。 更重要的是,通过将 hack 直接放在您的实体中,您将不必记住在代码库的其他任何地方进行显式检查。
您可以像这样将所有导航属性设置为 null :(您需要拥有 EF 上下文),此处:导入的是 IEnumerable < YourEntity>
foreach (var entity in imported)
{
foreach (var np in _YourEntityRepository.GetReferenceProperties())
entity.GetType().GetProperty(np.Name).SetValue(entity, null);
}
GetReferenceProperties 定义为:
public IEnumerable<NavigationProperty> GetReferenceProperties()
{
var oc = ((IObjectContextAdapter)Context).ObjectContext;
var entityType = oc.MetadataWorkspace.GetItems(DataSpace.OSpace)
.OfType<EntityType>()
.FirstOrDefault(et => et.Name == typeof(TEntity).Name);
if (entityType != null)
{
foreach (NavigationProperty np in entityType.NavigationProperties
.Where(p => p.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One
|| p.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne))
{
yield return np;
}
}
}
作为另一种解决方法,我将两个方法编译为一个扩展方法:
public static void SetToNull<TEntity, TProperty>(this TEntity entity, Expression<Func<TEntity, TProperty>> navigationProperty, DbContext context = null)
where TEntity : class
where TProperty : class
{
var pi = GetPropertyInfo(entity, navigationProperty);
if (context != null)
{
//If DB Context is supplied, use Entry/Reference method to null out current value
context.Entry(entity).Reference(navigationProperty).CurrentValue = null;
}
else
{
//If no DB Context, then lazy load first
var prevValue = (TProperty)pi.GetValue(entity);
}
pi.SetValue(entity, null);
}
static PropertyInfo GetPropertyInfo<TSource, TProperty>( TSource source, Expression<Func<TSource, TProperty>> propertyLambda)
{
Type type = typeof(TSource);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyLambda.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
这允许您提供一个 DbContext(如果有),在这种情况下它将使用最有效的方法并将条目引用的 CurrentValue 设置为 null。
entity.SetToNull(e => e.ReferenceProperty, dbContext);
如果没有提供 DBContext,它将首先延迟加载。
entity.SetToNull(e => e.ReferenceProperty);
注意,这个问题本质上是重复的: Entity Framework will only set related entity property to "null" if I first get the property and Setting a foreign key to null when using entity framework code first
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.