web-dev-qa-db-fra.com

Définition d'une clé étrangère sur null lors de la première utilisation du code d'infrastructure d'entité

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?

34
mccow002

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.

62
David Ruttka

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;
10
Sprintstar

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.

4
user1325940

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);
0
ndarriulat

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;
    }
}
0
David Wick

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);
0
jonh