Подтвердить что ты не робот

Установка внешнего ключа в значение null при первом использовании кода инфраструктуры сущности

Я использую первую реализацию базы данных Entity Framework Code First как слой данных для проекта, но я столкнулся с проблемой.

Мне нужно иметь возможность установить внешний ключ в значение null, чтобы удалить ассоциацию в базе данных.

У меня есть 2 объекта. Один из них называется 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;}
}

Это соответствует тому, что у меня есть в базе данных:

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 равным null. В моем пользовательском интерфейсе это будет отображаться как "No EMployee Assigned".

Однако, за исключением прямого SQL-запроса, я не могу найти способ сделать это в инфраструктуре сущностей 4.1.

Я пробовал:

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

Но это ничего не делает.

Есть ли у кого-нибудь идеи?

4b9b3361

Ответ 1

Я думаю, проблема в том, что в контексте контекста вы ничего не изменили.

Вы можете использовать ленивый подход к загрузке, который ранее предлагался с помощью virtual, но поскольку вы еще не запросили загрузку Employee, он все равно null. Вы можете попробовать следующее:

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

На боковой ноте FirstOrDefault вернет null, если no Project соответствует данному id. Если вы знаете, что проект существует, вы можете просто использовать First. Вы даже можете использовать Single, который будет утверждать, что существует только один такой проект. Если вы продолжаете использовать FirstOrDefault, я рекомендую проверить null перед работой с Project.

Ответ 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, чтобы сделать смысл более понятным. см. эту статью для получения более подробной информации об этом.

public class Project
{
    public int ProjectId {get; set;}
    public virtual Employee Employee {get;set;}
    public void RemoveEmployee()
    {
        Employee = 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 Entry Reference значение null.

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

Если DBContext не указан, он будет загружаться в первую очередь.

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