【问题标题】:Entity Framework set navigation property to null实体框架将导航属性设置为 null
【发布时间】:2012-10-10 21:31:09
【问题描述】:

我有一个实体框架数据库的第一个项目。这是模型的提取:

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 时遇到问题。当我将ResponsibleUser 设置为另一个用户并随后保存DBContext 的更改时,更改将存储在数据库中。

但是,当我想通过将导航属性设置为 null 来删除 LedProject 的当前 ResponsibleUser 时。更改不会存储在数据库中。

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

删除导航属性有什么技巧吗?

【问题讨论】:

  • 当您设置project.ResponsibleUser 时,您确定responsibleUser 实际上为空吗?声明不在此处。如果将整行替换为project.ResponsibleUser = null;,它会清除导航吗?
  • 当我将行更改为 project.ResponsibleUser = null; 时,它仍然不会删除该属性。在调试模式下,我看到该属性设置为 null,但它不是由方法 SaveChanges() 存储的
  • 数据库列是否可以为空? ID 列的映射是否设置为允许空值?我经常看到声明为 Nullable&lt;User&gt; 的属性,而 null 实际上是它们的合法值。但我没有大量工作 EF 实践,所以如果不是这样,我就没有想法了。
  • 数据库列可以为空。舒尔可以在这个属性中存储一个空值,因为我可以在没有 ResponibleUser 的情况下创建一个新的 LedProject。
  • 那我没主意了。祝你好运。

标签: c# entity-framework


【解决方案1】:

问题在于导航属性的延迟加载。似乎该值首先设置为 null ,然后从数据库中加载。因此,所需的值(在我的例子中为 null)被数据库中当前存储的值覆盖。

LedProject project = db.LedProject
    .Include("ResponsibleUser")
    .Where(p => p.ProjectId == projectId)
    .FirstOrDefault();

这会在 Project 加载时加载 ResponsibleUser。这终于解决了我的问题!

【讨论】:

  • 你刚刚把我从疯狂中拯救了出来——谢谢!
  • 在不急于加载 nav 属性的情况下有什么办法解决这个问题吗?
  • 这太糟糕了。我不得不停止使用Find() 并改用Where()。在我发现这个之前一直在打自己的脸,谢谢。
  • 这很有效,谢谢,这是official 方式吗?它记录在某处吗?感觉就像一个错误。
  • @SimonGates 我找到了轻松更新导航的官方方法,并在下面给出了答案。
【解决方案2】:

就像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 来强制保存而无需加载,但我还没有找到它(我尝试过的几件事似乎真的很老套)。

【讨论】:

  • 这很有效,尽管这两个答案都很骇人听闻,这个效果更好。您可以看到这也是问题所在,因为如果您在代码上断点并检查 ResponsibleUser,它会将其加载到内存中并将更改保存为 null。
  • 您可能想使用_ = project.ResponsibleUser;。这将停止编译器抱怨未使用的变量。
【解决方案3】:

找到了无需急于加载导航属性的最佳方法,因此您仍然可以使用 EF 的 Find() 而不必进行破解。

在导航属性旁边使用原始 ID,其中类型是导航属性 ID 类型的任何类型(通常是用户的字符串),例如:

public partial class LedProject
{
    public string ResponsibleUserId { get; set; }
    public virtual User ResponsibleUser { get; set; }
}

无论您在何处创建记录,都使用导航属性更新字符串,然后当您想要删除关系时,只需执行ledProject.ResponsibleUserId = null

如果您在最后将 id 命名为导航属性名称 + id 以外的名称,那么您将需要使用注释或流利的 api 来映射我认为。

更多信息在这里:In what scenarios do I need foreign keys AND navigation properties in entity framework

【讨论】:

    【解决方案4】:

    从 Entity Framework 5.0 开始:

    db.Entry(project).Reference(p => p.ResponsibleUser).CurrentValue = null;
    

    https://msdn.microsoft.com/en-us/data/jj713564.aspx

    【讨论】:

      【解决方案5】:

      https://docs.microsoft.com/en-us/ef/ef6/fundamentals/relationships

      创建和修改关系部分解释了在分配和设置为 null 时外键属性和导航属性会发生什么。

      EF5 和 forward 有一些变化,但它们的关键是定义外键属性,使关系不再是独立关联(缺少外键属性)。

      【讨论】:

        【解决方案6】:

        我遇到了这个问题并想出了一个不会破坏延迟加载的小“hack”。

        像这样在模型上简单地定义属性 -

            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 直接放在您的实体中,您不必记得在代码库的其他任何地方进行显式检查。

        【讨论】:

          【解决方案7】:

          您可以像这样将所有导航属性设置为 null:(您需要有 EF 上下文),这里:导入的是 IEnumerable

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

          【讨论】:

            【解决方案8】:

            作为另一种解决方法,我将两个方法编译为扩展方法:

            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 propertySetting a foreign key to null when using entity framework code first

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-04-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-03-29
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多