J'utilise la première implémentation de la base de données Entity Framework Code First en tant que couche de données pour un projet, mais j'ai rencontré un problème.
Je dois pouvoir définir une clé étrangère sur null afin de supprimer une association dans la base de données.
J'ai 2 objets. L'un s'appelle le projet.
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;}
}
Cela correspond à ce que j'ai dans la base de données:
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
)
Je peux affecter un employé à un projet. Cependant, je veux pouvoir supprimer un employé d'un projet et que le champ Employé soit nul. Dans mon interface utilisateur, cela affichera «Aucun employé affecté».
Cependant, à moins d'une requête SQL directe, je ne semble pas pouvoir trouver un moyen de le faire dans le framework d'entité 4.1.
J'ai essayé:
public void RemoveEmployeeFromProject(int projectId)
{
var project = Context.Projects.FirstOrDefault(x => x.ProjectId == projectId);
project.Employee = null;
Context.SaveChanges();
}
Mais cela ne fait rien.
Quelqu'un a-t-il une idée?
Je pense que le problème est que, en ce qui concerne le contexte, vous n’avez en fait changé rien.
Vous pouvez utiliser l'approche de chargement paresseux précédemment suggérée en utilisant virtual
, mais puisque vous n'avez pas encore demandé que l'employé soit chargé, il est toujours nul. Vous pouvez essayer ceci:
var forceLoad = project.Employee;
project.Employee = null; // Now EF knows something has changed
Context.SaveChanges();
Sinon, incluez-le explicitement dans votre demande initiale:
var project = Context.Projects.Include(x => x.Employee).FirstOrDefault(x => x.ProjectId == projectId);
project.Employee = null;
Context.SaveChanges();
Sur une note de côté, FirstOrDefault
retournera null
si aucune Project
ne correspond à l'id donné. Si vous savez que le projet existe, vous pouvez simplement utiliser First
. Vous pouvez même utiliser Single
qui affirmera qu’il n’ya qu’un seul projet de ce type. Si vous continuez à utiliser FirstOrDefault
, je vous conseillerais de vérifier null
avant de travailler avec project
.
Vous pouvez le faire de cette façon, ce qui signifie que vous n'avez pas à charger l'entité associée.
context.Entry(Project).Reference(r => r.Employee).CurrentValue = null;
La réponse à cette question est assez simple. EF ne peut pas déduire le type en fonction des informations que vous avez fournies.
Faites juste ceci à la place:
public void RemoveEmployeeFromProject(int projectId)
{
var project = Context.Projects.FirstOrDefault(x => x.ProjectId == projectId);
project.EmployeeId = (int?)null;
Context.SaveChanges();
}
et ça va marcher.
Vous devez inclure dans la requête linq, la propriété à affecter, en utilisant le même nom que celui utilisé dans la classe Project:
var project = Context.Projects.Include("Employee").FirstOrDefault(x => x.ProjectId == projectId);
si vous activez le chargement différé en rendant la propriété employee virtuelle, cela fonctionne-t-il?
public class Project
{
public int ProjectId {get; set;}
public virtual Employee Employee {get;set;}
}
je suggérerais également d'encapsuler la méthode remove dans le cadre de votre classe de poco pour clarifier la signification. voir this article pour plus de détails à ce sujet.
public class Project
{
public int ProjectId {get; set;}
public virtual Employee Employee {get;set;}
public void RemoveEmployee()
{
Employee = null;
}
}
Pour contourner le problème, j'ai compilé deux méthodes en une méthode d'extension:
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;
}
Cela vous permet de fournir un DbContext si vous en avez un, auquel cas il utilisera la méthode la plus efficace et définira la CurrentValue de la référence d'entrée sur null.
entity.SetToNull(e => e.ReferenceProperty, dbContext);
Si aucun DBContext n'est fourni, il sera d'abord chargé paresseux.
entity.SetToNull(e => e.ReferenceProperty);