Reputation: 6914
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.
I need to be able to set a foreign key to null in order to remove an association in the database.
I have 2 objects. 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. 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.
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?
Upvotes: 45
Views: 28074
Reputation: 8169
You can do it this way, which means you don't have to load the related entity.
context.Entry(Project).Reference(r => r.Employee).CurrentValue = null;
Upvotes: 14
Reputation: 254
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.
entity.SetToNull(e => e.ReferenceProperty, dbContext);
If no DBContext is supplied, it will lazy load first.
entity.SetToNull(e => e.ReferenceProperty);
Upvotes: 0
Reputation: 869
You need to include in the linq query, the property to assign, using the same name it has in the Project class:
var project = Context.Projects.Include("Employee").FirstOrDefault(x => x.ProjectId == projectId);
Upvotes: 0
Reputation: 41
The answer to this is quite simple. EF can't infer the type given the information you've provided.
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.
Upvotes: 4
Reputation: 14409
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. 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. If you know the project exists, you can just use First
. You could even use Single
which will assert that there is only one such project. If you continue to use FirstOrDefault
, I'd recommend checking for null
before working with project
.
Upvotes: 73
Reputation: 7105
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. 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;
}
}
Upvotes: 0