26

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

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

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

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

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

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

4

8 回答 8

49

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

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

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

于 2012-10-11T07:42:23.650 回答
5

就像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 在没有负载的情况下强制保存,但我还没有找到它(我尝试过的几件事似乎真的很hacky)。

于 2014-12-03T12:46:53.730 回答
4

找到了无需急切加载导航属性的最佳方法,因此您仍然可以使用 EFFind()而不必进行黑客攻击。

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

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

无论您在何处创建记录,都使用导航属性更新字符串,然后在您想要删除关系时执行ledProject.ResponsibleUserId = null

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

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

于 2016-08-06T14:37:15.957 回答
3

从 Entity Framework 5.0 开始:

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

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

于 2016-09-30T16:45:00.880 回答
2

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

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

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

于 2018-10-16T06:39:26.037 回答
0

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

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

    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 直接放在您的实体中,您不必记得在代码库的其他任何地方进行显式检查。

于 2016-09-12T11:49:06.967 回答
0

您可以像这样将所有导航属性设置为 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;
            }
        }
    }
于 2018-03-23T11:07:00.923 回答
-1

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

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

于 2018-09-20T14:34:40.770 回答