繁体   English   中英

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

[英]Entity Framework set navigation property to null

我有一个实体框架数据库第一个项目。 这是模型的提取:

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; }
}

我在设置类LedProject的导航项ResponsibleUserLedProject 当我将ResponsibleUser用户设置为另一个用户并随后保存 DBContext 的更改时,更改将存储在数据库中。

但是,当我想删除LedProject的当前ResponsibleUser LedProject ,通过将导航属性设置为空。 更改不会存储在数据库中。

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();

删除导航属性有什么技巧吗?

问题在于导航属性的延迟加载。 似乎该值首先设置为空,然后从数据库加载。 因此,数据库中当前存储的值会覆盖所需的值(在我的情况下为null )。

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

这会在加载项目时加载责任用户 这终于解决了我的问题!

就像boindiil所说的,问题在于延迟加载。 但是,只有在想要将其置空时才需要加载该属性,以便实体框架机制知道它已更改。 代码可能如下所示:

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();

我一直在想应该有一种方法可以使用 db.ChangeTracker 在没有负载的情况下强制保存,但我还没有找到它(我尝试过的几件事看起来真的很糟糕)。

找出最好的方法来做到这一点,而不必急于加载导航属性,这样您仍然可以使用 EF 的Find()而不必进行黑客攻击。

在导航属性旁边使用原始 ID,其中类型是导航属性 ID 类型(通常为用户的字符串),例如:

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

在您创建记录的任何位置使用导航属性更新字符串,然后当您想要删除关系时,只需执行ledProject.ResponsibleUserId = null

如果您在末尾将 id 命名为导航属性名称 + id 以外的名称,那么您将需要使用注释或 fluent api 来映射我认为。

更多信息: 在什么情况下我需要实体框架中的外键和导航属性

从实体框架 5.0 开始:

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

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

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

创建和修改关系部分解释了在分配和设置为 null 时外键属性和导航属性会发生什么。

EF5 及以后有一些变化,但它们的关键是定义外键属性,使关系不再是独立关联(缺少外键属性)。

我遇到了这个问题并提出了一个不会破坏延迟加载的小“黑客”。

只需像这样定义模型上的属性 -

    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;
            }

        }
    }

现在您不必急切地加载导航属性,将其设置为 null 即可。 更重要的是,通过将 hack 直接放在您的实体中,您将不必记住在代码库的其他任何地方进行显式检查。

您可以像这样将所有导航属性设置为 null :(您需要拥有 EF 上下文),此处:导入的是 IEnumerable < YourEntity>

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

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;
            }
        }
    }

作为另一种解决方法,我将两个方法编译为一个扩展方法:

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;
}

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

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

如果没有提供 DBContext,它将首先延迟加载。

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

注意,这个问题本质上是重复的: 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