简体   繁体   English

首先使用实体​​框架代码时将外键设置为空

[英]Setting a foreign key to null when using entity framework code first

I'm using the database first implementation of Entity Framework Code First as the data layer for a project, but I've run into a problem.我使用 Entity Framework Code First 的数据库优先实现作为项目的数据层,但我遇到了一个问题。

I need to be able to set a foreign key to null in order to remove an association in the database.我需要能够将外键设置为 null 以删除数据库中的关联。

I have 2 objects.我有 2 个对象。 One is called Project.一种叫做项目。

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

This matches what I have in the Database:这与我在数据库中的内容相匹配:

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
)

I can assign an Employee to a project.我可以为一个项目分配一个员工。 However, I want to be able to remove an employee from a project and have the Employee field be null.但是,我希望能够从项目中删除一名员工,并让 Employee 字段为空。 In my UI this will show as 'No EMployee Assigned'.在我的用户界面中,这将显示为“未分配员工”。

However, short of a direct sql query, I cannot seem to find a way to do this in the entity framework 4.1.但是,缺少直接的 sql 查询,我似乎无法在实体框架 4.1 中找到执行此操作的方法。

I've tried:我试过了:

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

But this doesn't do anything.但这没有任何作用。

Does anyone have any ideas?有没有人有任何想法?

I think the problem is that as far as the context is concerned, you haven't actually changed anything.我认为问题在于,就上下文而言,您实际上并没有改变任何东西。

You can use the lazy loading approach previously suggested by using virtual , but since you haven't requested that the Employee be loaded yet, it's still null.您可以使用之前通过使用virtual建议的延迟加载方法,但由于您尚未请求加载 Employee ,它仍然为空。 You could try this:你可以试试这个:

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

Alternatively, explicitly include it in your original request:或者,明确将其包含在您的原始请求中:

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

On a side note, FirstOrDefault will return null if no Project matches the given id.附带说明一下,如果没有Project与给定的 ID 匹配,则FirstOrDefault将返回null If you know the project exists, you can just use First .如果您知道该项目存在,则可以使用First You could even use Single which will assert that there is only one such project.您甚至可以使用Single ,它会断言只有一个这样的项目。 If you continue to use FirstOrDefault , I'd recommend checking for null before working with project .如果您继续使用FirstOrDefault ,我建议在使用project之前检查null

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

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

The answer to this is quite simple.答案很简单。 EF can't infer the type given the information you've provided.鉴于您提供的信息,EF 无法推断类型。

Just do this instead:只需这样做:

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

and it will work.它会起作用。

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

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

As another workaround, I compiled two methods into a extension method:作为另一种解决方法,我将两个方法编译为一个扩展方法:

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

This allows you to supply a DbContext if you have one, in which case it will use the most efficient method and set the CurrentValue of the Entry Reference to null.这允许您提供一个 DbContext(如果有),在这种情况下它将使用最有效的方法并将条目引用的 CurrentValue 设置为 null。

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

If no DBContext is supplied, it will lazy load first.如果没有提供 DBContext,它将首先延迟加载。

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

if you enable lazy loading by making the employee property virtual does it work?如果您通过将员工属性设为虚拟来启用延迟加载,它会起作用吗?

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

i'd also suggest encapsulating the remove method as part of your poco class to making the meaning more clear.我还建议将 remove 方法封装为 poco 类的一部分,以使含义更清晰。 see this article for more details on that.看到这个文章,对更多的细节。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM