Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 实体框架将仅将相关实体属性设置为";空";如果我第一次得到财产_C#_Entity Framework_Entity Framework 6 - Fatal编程技术网

C# 实体框架将仅将相关实体属性设置为";空";如果我第一次得到财产

C# 实体框架将仅将相关实体属性设置为";空";如果我第一次得到财产,c#,entity-framework,entity-framework-6,C#,Entity Framework,Entity Framework 6,编辑 对于在一个方向上引用另一个实体的任何实体属性,似乎都会发生这种情况。换句话说,对于下面的示例,Bar覆盖相等的事实似乎无关紧要 假设我有以下几个类: public class Foo { public int? Id { get; set; } public virtual Bar { get; set; } } public class Bar : IEquatable<Bar> { public int Id { get; set; }

编辑 对于在一个方向上引用另一个实体的任何实体属性,似乎都会发生这种情况。换句话说,对于下面的示例,
Bar
覆盖相等的事实似乎无关紧要

假设我有以下几个类:

public class Foo
{
    public int? Id { get; set; }

    public virtual Bar { get; set; }

}

public class Bar : IEquatable<Bar>
{
    public int Id { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as Bar;

        return Equals(other);
    }

    public bool Equals(Bar other)
    {
        if (object.Equals(other, null))
            return false;

        return this.Id == other.Id;
    }

    public static bool operator ==(Bar left, Bar right)
    {
        return object.Equals(left, right);
    }

    public static bool operator !=(Bar left, Bar right)
    {
        return !object.Equals(left, right);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}
那么属性实际上不会改变

如果我这样做:

var throwAway = foo.Bar;
foo.Bar = null;
然后该属性将实际设置并另存为null

由于
Foo.Bar
属性只是一个虚拟的、自动实现的属性,因此我只能得出结论,这与延迟加载和实体框架代理有关-但是为什么这个特定场景会导致问题,我不知道


为什么实体框架的行为是这样的,我如何才能让它真正可靠地设置
null

你是对的-这是因为你在EF中使用了延迟加载(
virtual
属性)。您可以删除
virtual
(但这对您来说可能是不可能的)。您在question-call属性中描述的其他方法,并将其设置为null


您也可以在SO上阅读有关此问题的信息。

作为一种解决方法,我发现缓解此问题的最简单方法是让setter在将backing字段设置为null之前调用getter,例如

public class Foo
{
    public int? Id { get; set; }

    private Bar _bar;
    public virtual Bar 
    { 
        get { return _bar; }

        set
        {
            var entityFrameworkHack = this.Bar; //ensure the proxy has loaded
            _bar = value;
        }
    }
}

这样,无论其他代码是否实际加载了该属性,该属性都可以工作,但可能会导致不必要的实体加载。

使其工作的一种方法是使用属性API:

var foo = context.Foos.Find(1);

context.Entry(foo).Reference(f => f.Bar).CurrentValue = null;

context.SaveChanges();

这样做的好处是,无需通过延迟加载加载
foo.Bar
,也可用于不支持延迟加载或更改跟踪代理(无
virtual
properties)的纯POCO。缺点是,您需要一个
上下文
实例,该实例位于要将相关
设置为

的位置,我对官方解决方案不满意:

    context.Entry(foo).Reference(f => f.Bar).CurrentValue = null;
因为POCO对象的用户需要太多的上下文知识。我的解决方案是在将值设置为null时触发惰性属性的加载,这样我们就不会从EF中得到误判比较:

    public virtual User CheckoutUser
    {
        get { return checkoutUser; }
        set
        {
            if (value != null || !LazyPropertyIsNull(CheckoutUser))
            {
                checkoutUser = value;
            }
        }
    }
在我的基本DbEntity类中:

    protected bool LazyPropertyIsNull<T>(T currentValue) where T : DbEntity
    {
        return (currentValue == null);
    }
受保护的bool LazyPropertyIsNull(T currentValue),其中T:DbEntity
{
返回值(currentValue==null);
}
将属性传递给LazyPropertyIsNull函数将触发延迟加载,并进行正确的比较


请在:

上投票支持这个问题,我个人认为Nathan的答案(在属性设置器中延迟加载)是最可靠的。但是,它使您的域类(每个属性10行)变得更不可读

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

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,它将首先延迟加载

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

如果要避免操纵
EntityEntry
,可以通过在POCO中包含FK属性(如果不希望用户具有访问权限,可以将其设置为私有)并在setter
为null时让Nav属性setter将FK值设置为null来避免对数据库的延迟加载调用。例如:

public class InaccessibleFKDependent
{
    [Key]
    public int Id { get; set; }
    private int? PrincipalId { get; set; }
    private InaccessibleFKPrincipal _principal;
    public virtual InaccessibleFKPrincipal Principal
    {
        get => _principal;
        set
        {
            if( null == value )
            {
                PrincipalId = null;
            }

            _principal = value;
        }
    }
}

public class InaccessibleFKDependentConfiguration : IEntityTypeConfiguration<InaccessibleFKDependent>
{
    public void Configure( EntityTypeBuilder<InaccessibleFKDependent> builder )
    {
        builder.HasOne( d => d.Principal )
            .WithMany()
            .HasForeignKey( "PrincipalId" );
    }
}

我的猜测是,延迟加载实现为
bartypebar{get{if(base.Bar==null){base.Bar=(lazyly load);}返回base.Bar;}设置{base.Bar=value;}}
,其中
base.Bar
没有看到从
null
(尚未加载)到
null
(无)的更改。但如果这是正确的,我认为这是一个bug,值得在他们的CodePlex页面上向EF人员报告。@hvd我打开了,所以我们会看看他们怎么说。在我的例子中,上下文不容易获得,但这仍然是一个好消息。这可能会很昂贵,取决于模型。只有在新值为null时才执行延迟加载更有效,因为只有在这些情况下才会忽略新值。我在下面的回答中给出了一个例子。有关避免不必要的DB查询的方法,请参见我的回答。这是一个不错的解决方案,但代价是为每个受影响的属性引入额外的FK字段,并为每个接受的属性手动配置。鉴于此问题仅在将属性设置为null时发生,是否值得?也许如果我们可以在FK字段中使用一个属性来自动配置,这将减轻痛苦。@RobKent-当然,
DataAnnotation
属性可以正常工作;在任何情况下,如果它避免了一个浪费的DB查询,对我来说都是值得的,但是还有其他的选择,比如如果知道他们可能会更改属性,那么就立即加载属性,或者,如另一个答案中所述,直接操作
EntityEntry
(但是用户需要访问一些人不允许的
DbContext
,但即使这样,他们也可以在存储库中创建helper方法,例如,将属性设置为null。鉴于EF Core不允许延迟加载,并且在某个时候我们必须尝试将EF6应用程序移植过来,我最初的问题是l。)我想建议应该是:选择EF Core,或者,如果你被EF 6困住了(像我一样!),不要使用延迟加载。EF Core现在支持延迟加载这很有趣,谢谢。这应该会让我们的应用程序移植更容易。
entity.SetToNull(e => e.ReferenceProperty);
public class InaccessibleFKDependent
{
    [Key]
    public int Id { get; set; }
    private int? PrincipalId { get; set; }
    private InaccessibleFKPrincipal _principal;
    public virtual InaccessibleFKPrincipal Principal
    {
        get => _principal;
        set
        {
            if( null == value )
            {
                PrincipalId = null;
            }

            _principal = value;
        }
    }
}

public class InaccessibleFKDependentConfiguration : IEntityTypeConfiguration<InaccessibleFKDependent>
{
    public void Configure( EntityTypeBuilder<InaccessibleFKDependent> builder )
    {
        builder.HasOne( d => d.Principal )
            .WithMany()
            .HasForeignKey( "PrincipalId" );
    }
}
    public static void TestInaccessibleFKSetToNull( DbContextOptions options )
    {
        using( var dbContext = DeleteAndRecreateDatabase( options ) )
        {
            var p = new InaccessibleFKPrincipal();

            dbContext.Add( p );
            dbContext.SaveChanges();

            var d = new InaccessibleFKDependent()
            {
                Principal = p,
            };

            dbContext.Add( d );
            dbContext.SaveChanges();
        }

        using( var dbContext = new TestContext( options ) )
        {
            var d = dbContext.InaccessibleFKDependentEntities.Single();
            d.Principal = null;
            dbContext.SaveChanges();
        }

        using( var dbContext = new TestContext( options ) )
        {
            var d = dbContext.InaccessibleFKDependentEntities
                .Include( dd => dd.Principal )
                .Single();

            System.Console.WriteLine( $"{nameof( d )}.{nameof( d.Principal )} is NULL: {null == d.Principal}" );
        }
    }