Установка внешнего ключа в значение 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();
}
Но это ничего не делает.
Есть ли у кого-нибудь идеи?
Ответы
Ответ 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);