39 votes

Définition d'une clé étrangère à null lors de l'utilisation du code du cadre d'entité d'abord

J'utilise la première implémentation de la base de données d'Entity Framework Code First comme couche de données pour un projet, mais j'ai rencontré un problème.

Je dois être en mesure de définir une clé étrangère comme nulle afin de supprimer une association dans la base de données.

J'ai 2 objets. L'un s'appelle 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 retirer un employé d'un projet et faire en sorte que le champ Employé soit nul. Dans mon interface utilisateur, cela se traduira par "Aucun employé affecté".

Cependant, à part une requête sql directe, je n'arrive pas à trouver un moyen de le faire dans le cadre de l'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 ?

70voto

David Ruttka Points 8105

Je pense que le problème est qu'en ce qui concerne le contexte, vous n'avez rien changé en fait.

Vous pouvez utiliser l'approche de chargement paresseux suggérée précédemment en utilisant virtual mais comme 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();

Vous pouvez également l'inclure explicitement dans votre demande initiale :

var project = Context.Projects.Include(x => x.Employee).FirstOrDefault(x => x.ProjectId == projectId);
project.Employee = null;
Context.SaveChanges();

En passant, FirstOrDefault retournera null si non Project correspond à l'identifiant donné. Si vous savez que le projet existe, vous pouvez simplement utiliser First . Vous pouvez même utiliser Single qui affirmera qu'il n'existe qu'un seul projet de ce type. Si vous continuez à utiliser FirstOrDefault je recommande de vérifier null avant de travailler avec project .

1 votes

Oui, c'était le problème. Merci.

2 votes

J'ai eu le même problème, très utile, j'ai eu mon +1.

2 votes

Je considère qu'il s'agit d'un bug dans EF. Voir le problème entityframework.codeplex.com/workitem/2074 et question connexe sur le SO stackoverflow.com/questions/21692401/

12voto

Sprintstar Points 3665

Vous pouvez le faire de cette façon, ce qui signifie que vous ne devez pas charger l'entité liée.

context.Entry(Project).Reference(r => r.Employee).CurrentValue = null;

0 votes

J'aime cette solution. Bien que verbeuse, elle documente elle-même ce qui se passe. La réponse acceptée, qui consiste à forcer un chargement paresseux ou un chargement rapide, puis à effacer la propriété, me semble un peu compliquée et peut entraîner une pénalité importante en raison des chargements inutiles. Merci.

4voto

user1325940 Points 21

La réponse à cette question est très simple. EF ne peut pas déduire le type à partir des informations que vous avez fournies.

Fais ça à la place :

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

et ça marchera.

0voto

ndarriulat Points 316

Vous devez inclure dans la requête linq, la propriété à assigner, en utilisant le même nom qu'elle a dans la classe Project :

var project = Context.Projects.Include("Employee").FirstOrDefault(x => x.ProjectId == projectId);

0voto

jonh Points 213

Comme autre solution de contournement, 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 mettra la valeur actuelle de la référence d'entrée à null.

entity.SetToNull(e => e.ReferenceProperty, dbContext);

Si aucun DBContext n'est fourni, il sera chargé paresseusement en premier.

entity.SetToNull(e => e.ReferenceProperty);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X