Установка внешнего ключа в null при первом использовании кода структуры объекта - PullRequest
34 голосов
/ 21 июня 2011

Я использую базу данных First 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 было пустым.В моем пользовательском интерфейсе это будет отображаться как «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();
}

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

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

Ответы [ 6 ]

63 голосов
/ 21 июня 2011

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

Вы можете использовать подход отложенной загрузки, ранее предложенный с помощью * 1005.*, но поскольку вы еще не запросили, чтобы сотрудник был загружен, он по-прежнему равен нулю.Вы можете попробовать это:

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, если нет Project соответствует указанному идентификатору.Если вы знаете, что проект существует, вы можете просто использовать First.Вы даже можете использовать Single, который будет утверждать, что существует только один такой проект.Если вы продолжите использовать FirstOrDefault, я бы порекомендовал проверить null перед началом работы с project.

10 голосов
/ 02 августа 2013

Вы можете сделать это таким образом, что означает, что вам не нужно загружать связанный объект.

context.Entry(Project).Reference(r => r.Employee).CurrentValue = null;
4 голосов
/ 05 ноября 2012

Ответ на это довольно прост. EF не может определить тип, учитывая предоставленную вами информацию.

Просто сделайте это вместо:

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

и это будет работать.

0 голосов
/ 20 сентября 2018

В качестве другого обходного пути я скомпилировал два метода в метод расширения:

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);
0 голосов
/ 17 июля 2017

Необходимо включить в запрос linq свойство для назначения, используя то же имя, которое есть в классе Project:

var project = Context.Projects.Include("Employee").FirstOrDefault(x => x.ProjectId == projectId);
0 голосов
/ 21 июня 2011

если вы включите отложенную загрузку, сделав свойство сотрудника виртуальным, оно работает?

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...