简体   繁体   English

实体框架将导航属性设置为 null

[英]Entity Framework set navigation property to null

I have a entity framework database first project.我有一个实体框架数据库第一个项目。 here is a extraction of the model:这是模型的提取:

public partial class LedProject
{
    public LedProject()
    {
        this.References = new HashSet<LedProjectReference>();
        this.Results = new HashSet<LedProjectResult>();
        this.History = new HashSet<LedProjectHistory>();
    }

    public string Identifier { get; set; }
    public string Name { get; set; }
    public Nullable<System.DateTime> CompletionDate { get; set; }
    public System.DateTime CreationDate { get; set; }
    public System.Guid ProjectId { get; set; }
    public string Comment { get; set; }

    public virtual User ContactUser { get; set; }
    public virtual User CreationUser { get; set; }
    public virtual Customer Customer { get; set; }
    public virtual LedProjectAccounting Accounting { get; set; }
    public virtual LedProjectState State { get; set; }
    public virtual ICollection<LedProjectReference> References { get; set; }
    public virtual ICollection<LedProjectResult> Results { get; set; }
    public virtual User ResponsibleUser { get; set; }
    public virtual ICollection<LedProjectHistory> History { get; set; }
}
public partial class User
{
    public System.Guid UserId { get; set; }
    public string LoginName { get; set; }
    public System.DateTime CreationDate { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public string Email { get; set; }
}

I have a problem with setting the navigation item ResponsibleUser of the class LedProject .我在设置类LedProject的导航项ResponsibleUserLedProject When I set the ResponsibleUser to a another user and afterwards save the changes of the DBContext, the changes are stored in the database.当我将ResponsibleUser用户设置为另一个用户并随后保存 DBContext 的更改时,更改将存储在数据库中。

But, when I want to delete the current ResponsibleUser of an LedProject , by setting the navigation property to null.但是,当我想删除LedProject的当前ResponsibleUser LedProject ,通过将导航属性设置为空。 The changes are not stored in the database.更改不会存储在数据库中。

LedProject project = db.LedProject.Find(projectId);
project.Name = string.IsNullOrEmpty(name) ? null : name;
...
project.ResponsibleUser = responsibleUser == null ? null : db.User.Find(responsibleUser.UserId);
...
db.SaveChanges();

Is there any trick for deleting navigation properties?删除导航属性有什么技巧吗?

The problem lies in the lazy loading of the navigation property.问题在于导航属性的延迟加载。 It seems that the value is first set to null and afterwards loaded from the database.似乎该值首先设置为空,然后从数据库加载。 So the desired value ( null in my case) is overridden by the currently stored value in the database.因此,数据库中当前存储的值会覆盖所需的值(在我的情况下为null )。

LedProject project = db.LedProject
    .Include("ResponsibleUser")
    .Where(p => p.ProjectId == projectId)
    .FirstOrDefault();

This loads the ResponsibleUser when the Project is loaded.这会在加载项目时加载责任用户 This finally solved my issue!这终于解决了我的问题!

Like boindiil said, the problem is with the lazy loading.就像boindiil所说的,问题在于延迟加载。 However, you only have to load the property when you want to null it so the Entity Framework machinery will know it has changed.但是,只有在想要将其置空时才需要加载该属性,以便实体框架机制知道它已更改。 The code could look like:代码可能如下所示:

responsibleUser = responsibleUser == null ? null : db.User.Find(responsibleUser.UserId);
if (responsibleUser == null)
{
    // load the value to assure setting it to null will cause a change
    var dummy = project.ResponsibleUser; 
}

project.ResponsibleUser = responsibleUser;
...
db.SaveChanges();

I keep thinking there should be a way to use db.ChangeTracker to force a save without the load but I haven't found it yet (and the few things I have tried seemed really hacky).我一直在想应该有一种方法可以使用 db.ChangeTracker 在没有负载的情况下强制保存,但我还没有找到它(我尝试过的几件事看起来真的很糟糕)。

Figured out the best way to do this without having to eager load the navigation property so you can still use EF's Find() and not have to do a hack.找出最好的方法来做到这一点,而不必急于加载导航属性,这样您仍然可以使用 EF 的Find()而不必进行黑客攻击。

Use a primitive ID alongside the navigation property where the type is whatever the navigation properties ID type is (usually string for users), eg:在导航属性旁边使用原始 ID,其中类型是导航属性 ID 类型(通常为用户的字符串),例如:

public partial class LedProject
{
    public string ResponsibleUserId { get; set; }
    public virtual User ResponsibleUser { get; set; }
}

Update the string with the navigation property wherever you create the record and then when you want to remove the relationship just do ledProject.ResponsibleUserId = null .在您创建记录的任何位置使用导航属性更新字符串,然后当您想要删除关系时,只需执行ledProject.ResponsibleUserId = null

If you name the id something other than navigation properties name + id at the end then you will need to use annotations or fluent api to map I think.如果您在末尾将 id 命名为导航属性名称 + id 以外的名称,那么您将需要使用注释或 fluent api 来映射我认为。

More info here: In what scenarios do I need foreign keys AND navigation properties in entity framework更多信息: 在什么情况下我需要实体框架中的外键和导航属性

Starting with the Entity Framework 5.0:从实体框架 5.0 开始:

db.Entry(project).Reference(p => p.ResponsibleUser).CurrentValue = null;

https://msdn.microsoft.com/en-us/data/jj713564.aspx https://msdn.microsoft.com/en-us/data/jj713564.aspx

See https://docs.microsoft.com/en-us/ef/ef6/fundamentals/relationships .请参阅https://docs.microsoft.com/en-us/ef/ef6/fundamentals/relationships

The Creating and modifying relationships section explains what happens in regards to the foreign key property and the navigational property both when assigning and setting to null.创建和修改关系部分解释了在分配和设置为 null 时外键属性和导航属性会发生什么。

There are some changes in EF5 and forward, but the they key is to define the foreign key property so that the relationship is no longer an independant association (lacks the foreign key property). EF5 及以后有一些变化,但它们的关键是定义外键属性,使关系不再是独立关联(缺少外键属性)。

I ran into this problem and come up with a small "hack" that doesn't break lazy loading.我遇到了这个问题并提出了一个不会破坏延迟加载的小“黑客”。

Simply define the property on your model like this -只需像这样定义模型上的属性 -

    public int? AccountId { get; set; }

    //workaround for entity framework lazy loading problem

    Account account;

    public virtual Account Account
    {
        get
        {
            return account;
        }
        set
        {
            account = value;


            if (value == null)
            {
                AccountId = null;
            }

        }
    }

Now you don't have to eagerly load the navigation property, and setting it to null will work.现在您不必急切地加载导航属性,将其设置为 null 即可。 More importantly, by putting the hack directly inside your entity, you won't have to remember to do explicit checks anywhere else in the codebase.更重要的是,通过将 hack 直接放在您的实体中,您将不必记住在代码库的其他任何地方进行显式检查。

You can set all navigation properties to null like this : ( you need to have your EF Context),here : imported is IEnumerable < YourEntity>您可以像这样将所有导航属性设置为 null :(您需要拥有 EF 上下文),此处:导入的是 IEnumerable < YourEntity>

  foreach (var entity in imported)
        {
            foreach (var np in _YourEntityRepository.GetReferenceProperties())
                entity.GetType().GetProperty(np.Name).SetValue(entity, null);
        }

with GetReferenceProperties defined as : GetReferenceProperties 定义为:

public IEnumerable<NavigationProperty> GetReferenceProperties()
    {

        var oc = ((IObjectContextAdapter)Context).ObjectContext;
        var entityType = oc.MetadataWorkspace.GetItems(DataSpace.OSpace)
                           .OfType<EntityType>()
                           .FirstOrDefault(et => et.Name == typeof(TEntity).Name);
        if (entityType != null)
        {
            foreach (NavigationProperty np in entityType.NavigationProperties
                    .Where(p => p.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One
                             || p.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne))
            {
                yield return np;
            }
        }
    }

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.这允许您提供一个 DbContext(如果有),在这种情况下它将使用最有效的方法并将条目引用的 CurrentValue 设置为 null。

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

If no DBContext is supplied, it will lazy load first.如果没有提供 DBContext,它将首先延迟加载。

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

Note, this issue is essentially a duplicate of: Entity Framework will only set related entity property to "null" if I first get the property and Setting a foreign key to null when using entity framework code first注意,这个问题本质上是重复的: Entity Framework will only set related entity property to "null" if I first get the property and Setting a foreign key to null when using entity framework code first

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM