C# 首先使用实体框架代码时将外键设置为null

C# 首先使用实体框架代码时将外键设置为null,c#,entity-framework-4.1,C#,Entity Framework 4.1,我首先使用实体框架代码的数据库第一个实现作为项目的数据层,但我遇到了一个问题 我需要能够将外键设置为null,以便删除数据库中的关联 我有两个物体。一个叫做项目 public class Project { public int ProjectId {get; set;} public Employee Employee {get;set;} } public class Employee { public int EmployeeId {get; set;}

我首先使用实体框架代码的数据库第一个实现作为项目的数据层,但我遇到了一个问题

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

我有两个物体。一个叫做项目

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;}
}
这与数据库中的内容相匹配:

创建表项目(
投影整数标识(1,1)不为空,
EmployeeId int NULL
)
创建表项目(
EmployeeId整数标识(1,1)不为空,
EmployeeName varchar(100)空
)
我可以为项目指派一名员工。但是,我希望能够从项目中删除雇员,并使雇员字段为空。在我的UI中,这将显示为“未分配员工”

然而,由于缺少直接sql查询,我似乎无法在EntityFramework4.1中找到这样做的方法

我试过:

public void RemoveEmployeeFromProject(int projectId)
{
    var project = Context.Projects.FirstOrDefault(x => x.ProjectId == projectId);
    project.Employee = null;
    Context.SaveChanges();
}
但这没什么用


有人有什么想法吗?

如果您通过使员工财产虚拟化来启用延迟加载,它会工作吗

public class Project
{
    public int ProjectId {get; set;}
    public virtual Employee Employee {get;set;}
}
我还建议将remove方法封装为poco类的一部分,以使其含义更加明确。有关这方面的更多详细信息,请参阅文章

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

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

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

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

另一方面,如果没有与给定id匹配的
Project
,则
FirstOrDefault
将返回
null
。如果您知道该项目存在,只需使用
First
。您甚至可以使用
Single
,它将断言只有一个这样的项目。如果继续使用
FirstOrDefault
,我建议在使用
project
之前检查
null
,答案非常简单。EF无法根据您提供的信息推断类型

只需这样做:

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

它会工作。

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

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

您需要在linq查询中包含要分配的属性,使用该属性在项目类中具有的相同名称:

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

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

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,它将首先延迟加载

entity.SetToNull(e => e.ReferenceProperty);

不幸的是,不,那不行。我认为部分原因是由于启用了延迟加载,您不请求的值被设置为null。因此,当您调用Context.SaveChanges时,它必须忽略空值,这样它就不会用未加载的值替换实际值。这是有意义的,直到你想将一个值设置为null。我想这与延迟加载有关。是的,这就是问题所在。谢谢有同样的问题,非常有帮助,得到我+1i认为这是一个错误在EF。请参阅issue and related SO issue Lazy load是罪魁祸首,浪费了一点时间,但这已经解释并修复了。@David Ruttka当我们使用存储过程var list=_db.Database.SqlQuery(“spGetProjectInfo@EmployeeID”,sqlParams.ToListSync()时,这将如何工作我正在获取列表。Employee=null如何获取项目中的员工我喜欢这个解决方案。虽然冗长,但它自己记录了正在发生的事情。对于我来说,强制进行延迟加载或快速加载然后清除属性的公认答案有点不成熟,并且由于不必要的加载可能会带来严重的损失。谢谢
entity.SetToNull(e => e.ReferenceProperty);