我使用数据库第一次实现Entity Framework Code First作为项目的数据层,但我遇到了一个问题。
我需要能够将外键设置为null才能删除数据库中的关联。
我有2个物体。一个叫做项目。
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字段为空。在我的UI中,这将显示为“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();
}
但这没有做任何事情。
有没有人有任何想法?
答案 0 :(得分:63)
我认为问题在于,就上下文而言,实际上并没有改变任何内容。
您可以使用先前使用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
与给定的ID匹配,null
将返回Project
。如果您知道项目存在,则可以使用First
。您甚至可以使用Single
来断言只有一个这样的项目。如果您继续使用FirstOrDefault
,我建议您在使用null
之前检查project
。
答案 1 :(得分:10)
您可以这样做,这意味着您不必加载相关实体。
context.Entry(Project).Reference(r => r.Employee).CurrentValue = null;
答案 2 :(得分:4)
答案很简单。 EF无法根据您提供的信息推断出类型。
请改为:
public void RemoveEmployeeFromProject(int projectId)
{
var project = Context.Projects.FirstOrDefault(x => x.ProjectId == projectId);
project.EmployeeId = (int?)null;
Context.SaveChanges();
}
它会起作用。
答案 3 :(得分:0)
如果您通过使employee属性为virtual来启用延迟加载吗?
public class Project
{
public int ProjectId {get; set;}
public virtual Employee Employee {get;set;}
}
我还建议将remove方法封装为poco类的一部分,以使意义更清晰。有关详细信息,请参阅this文章。
public class Project
{
public int ProjectId {get; set;}
public virtual Employee Employee {get;set;}
public void RemoveEmployee()
{
Employee = null;
}
}
答案 4 :(得分:0)
您需要在linq查询中包含要分配的属性,使用与Project类中相同的名称:
var project = Context.Projects.Include("Employee").FirstOrDefault(x => x.ProjectId == projectId);
答案 5 :(得分:0)
作为另一种解决方法,我将两个方法编译为扩展方法:
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,则可以提供它,在这种情况下,它将使用最有效的方法并将Entry Reference的CurrentValue设置为null。
entity.SetToNull(e => e.ReferenceProperty, dbContext);
如果未提供DBContext,它将首先延迟加载。
entity.SetToNull(e => e.ReferenceProperty);