【问题标题】:Setting a foreign key to null when using entity framework code first首次使用实体框架代码时将外键设置为 null
【发布时间】:2011-06-21 05:59:47
【问题描述】:

我正在使用 Entity Framework Code First 的数据库优先实现作为项目的数据层,但我遇到了问题。

我需要能够将外键设置为 null 才能删除数据库中的关联。

我有 2 个对象。一个叫做项目。

public class Project
{
    public int ProjectId {get; set;}
    public Employee Employee {get;set;}
}

public class Employee
{
    public int EmployeeId {get; set;}
    public string EmployeeName {get;set;}
}

这与我在数据库中的内容相符:

CREATE TABLE Project(
    ProjectId int IDENTITY(1,1) NOT NULL,
    EmployeeId int NULL
)

CREATE TABLE Project(
    EmployeeId int IDENTITY(1,1) NOT NULL,
    EmployeeName varchar(100) NULL
)

我可以将员工分配给项目。但是,我希望能够从项目中删除员工并将 Employee 字段为空。在我的 UI 中,这将显示为“未分配员工”。

但是,没有直接的 sql 查询,我似乎无法在实体框架 4.1 中找到执行此操作的方法。

我试过了:

public void RemoveEmployeeFromProject(int projectId)
{
    var project = Context.Projects.FirstOrDefault(x => x.ProjectId == projectId);
    project.Employee = null;
    Context.SaveChanges();
}

但这没有任何作用。

有人有什么想法吗?

【问题讨论】:

    标签: c# entity-framework-4.1


    【解决方案1】:

    我认为问题在于,就上下文而言,您实际上并没有改变任何东西。

    您可以使用之前通过 virtual 建议的延迟加载方法,但由于您尚未请求加载 Employee,它仍然为空。你可以试试这个:

    var forceLoad = project.Employee;
    project.Employee = null; // Now EF knows something has changed
    Context.SaveChanges();
    

    或者,将其明确包含在您的原始请求中:

    var project = Context.Projects.Include(x => x.Employee).FirstOrDefault(x => x.ProjectId == projectId);
    project.Employee = null;
    Context.SaveChanges();
    

    附带说明,如果没有Project 匹配给定的ID,FirstOrDefault 将返回null。如果您知道该项目存在,您可以使用First。你甚至可以使用Single,它会断言只有一个这样的项目。如果您继续使用FirstOrDefault,我建议您在使用project 之前检查null

    【讨论】:

    • 是的,这就是问题所在。谢谢!
    • 遇到了同样的问题,非常有帮助,得到了我的 +1
    • 我认为这是 EF 中的一个错误。请参阅问题 entityframework.codeplex.com/workitem/2074 和相关的 SO 问题 stackoverflow.com/questions/21692401/…
    • 延迟加载是罪魁祸首,浪费了一点时间,但这已经解释并修复了。
    • @David Ruttka 当我们使用存储过程时这将如何工作 var list = _db.Database.SqlQuery("spGetProjectInfo @EmployeeID", sqlParams).ToListAsync() 我得到 list.Employee = null 我如何让员工参与项目
    【解决方案2】:

    您可以这样做,这意味着您不必加载相关实体。

    context.Entry(Project).Reference(r => r.Employee).CurrentValue = null;
    

    【讨论】:

    • 我喜欢这个解决方案。虽然冗长,但它会自我记录正在发生的事情。强制延迟加载或急切加载然后清除属性的公认答案对我来说有点棘手,并且由于不必要的负载可能会带来重大损失。谢谢。
    【解决方案3】:

    这个问题的答案很简单。 EF 无法根据您提供的信息推断出类型。

    只需这样做:

    public void RemoveEmployeeFromProject(int projectId)
    {
        var project = Context.Projects.FirstOrDefault(x => x.ProjectId == projectId);
        project.EmployeeId = (int?)null;
        Context.SaveChanges();
    }
    

    它会起作用的。

    【讨论】:

      【解决方案4】:

      如果您通过将员工属性设置为虚拟来启用延迟加载,它会起作用吗?

      public class Project
      {
          public int ProjectId {get; set;}
          public virtual Employee Employee {get;set;}
      }
      

      我还建议将 remove 方法封装为 poco 类的一部分,以使含义更清楚。有关详细信息,请参阅this 文章。

      public class Project
      {
          public int ProjectId {get; set;}
          public virtual Employee Employee {get;set;}
          public void RemoveEmployee()
          {
              Employee = null;
          }
      }
      

      【讨论】:

      • 不幸的是,不,这不起作用。我认为部分原因是因为启用延迟加载后,您不请求的值被设置为 null。因此,当您调用 Context.SaveChanges 时,它必须忽略空值,因此它不会用未加载的值替换实际值。在您想要将值实际设置为 null 之前,这是有道理的。
      • 我认为这与延迟加载有关。
      【解决方案5】:

      您需要在 linq 查询中包含要分配的属性,使用与 Project 类中相同的名称:

      var project = Context.Projects.Include("Employee").FirstOrDefault(x => x.ProjectId == projectId);
      

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          猜你喜欢
          • 2017-01-06
          • 1970-01-01
          • 1970-01-01
          • 2013-09-01
          • 2023-03-23
          • 1970-01-01
          • 1970-01-01
          • 2016-09-25
          • 2023-04-11
          相关资源
          最近更新 更多