亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

為什么 Delta.Patch 不會將字節數組更新為空值?

為什么 Delta.Patch 不會將字節數組更新為空值?

C#
慕無忌1623718 2023-09-24 16:12:20
我的對象模型有一個用于存儲圖像的字節數組。當我嘗試通過Delta.Patch將此字節數組更新為新值時,它工作得很好,但是當我嘗試將其更新為 null 時,它失敗了。這是我正在查看的代碼行update.Patch(entity);如果我查看該update對象,我可以看到其中update.ChangedProperties包含我的圖像屬性,因此它知道它知道應該更新。我還可以看到它update._instance包含圖像字段具有空值的對象實例,并且我可以使用 Fiddler 來查看更改后的值作為空值發送。entity但是,當我在調用后查看對象.Patch時,新值應該為空,字節數組不會更新。Delta 中的其他更新得到正確更新,但此字節數組除外。可能是什么原因造成的?我對 OData 還很陌生,不確定我是否在這里遺漏了一些明顯的東西。
查看完整描述

1 回答

?
慕仙森

TA貢獻1827條經驗 獲得超8個贊

我查看了 OData(WebAPI 版本)的源代碼并(可能)發現了核心問題。問題也適用于 ASP.NET Core 版本,因為它與 ASP.NET WebAPI 共享代碼庫。

問題

你調用Patch(TStructuralType original)方法,方法又調用CopyChangedValues(TStructuralType original)方法。兩者都是班級的公共成員Delta<T>

public?void?Patch(TStructuralType?original){
????CopyChangedValues(original);
}

內部CopyChangedValues(TStructuralType original)方法是一段處理將值復制到原始實例的代碼。代碼迭代PropertyAccessor<TStructuralType>數組并調用Copy(TStructuralType from, TStructuralType to)方法。

// For regular non-structural properties at current level.

PropertyAccessor<TStructuralType>[] propertiesToCopy =

? ? ? ? ? ? ? ? this._changedProperties.Select(s => _allProperties[s]).ToArray();

foreach (PropertyAccessor<TStructuralType> propertyToCopy in propertiesToCopy)

{

? ? propertyToCopy.Copy(_instance, original);

}

在里面Copy(TStructuralType from, TStructuralType to)實現PropertyAccessor<TStructuralType>你會發現對abstract的調用SetValue(TStructuralType instance, object value)。


public void Copy(TStructuralType from, TStructuralType to)

{

? ? if (from == null)

? ? {

? ? ? ? throw Error.ArgumentNull("from");

? ? }

? ? if (to == null)

? ? {

? ? ? ? throw Error.ArgumentNull("to");

? ? }

? ? SetValue(to, GetValue(from));

}

這個方法是通過FastPropertyAccessor<TStructuralType>類來實現的。


public override void SetValue(TStructuralType instance, object value)

{

? ? if (instance == null)

? ? {

? ? ? ? throw Error.ArgumentNull("instance");

? ? }


? ? if (_isCollection)

? ? {

? ? ? ? DeserializationHelpers.SetCollectionProperty(instance, _property.Name, edmPropertyType: null,

? ? ? ? ? ? value: value, clearCollection: true);

? ? }

? ? else

? ? {

? ? ? ? _setter(instance, value);

? ? }

}

重要的代碼行是if (_isCollection). 該布爾標志在構造函數中設置并調用類IsCollection()中的靜態方法TypeHelper。


public FastPropertyAccessor(PropertyInfo property)

? ? : base(property)

{

? ? _property = property;

? ? _isCollection = TypeHelper.IsCollection(property.PropertyType);


? ? if (!_isCollection)

? ? {

? ? ? ? _setter = PropertyHelper.MakeFastPropertySetter<TStructuralType>(property);

? ? }

? ? _getter = PropertyHelper.MakeFastPropertyGetter(property);

}

在IsCollection(Type clrType)我們遍歷調用IsCollection(this Type type, out Type elementType).


public static bool IsCollection(Type clrType)

{

? ? Type elementType;

? ? return TypeHelper.IsCollection(clrType, out elementType);

}

以下是注釋后面的重要幾行// see if this type should be ignored.(這很奇怪,可能表明有人忘記完成他已經開始的事情),其中僅排除string( ) 。char[]其他數組(包括byte[])會跳到以下代碼,該代碼會積極評估 byte[](以及任何其他數組類型),因為這些類型正在實現IEnumerable<T>接口。


public static bool IsCollection(Type clrType, out Type elementType)

{

? ? if (clrType == null)

? ? {

? ? ? ? throw Error.ArgumentNull("clrType");

? ? }


? ? elementType = clrType;


? ? // see if this type should be ignored.

? ? if (clrType == typeof(string))

? ? {

? ? ? ? return false;

? ? }


? ? Type collectionInterface

? ? ? ? = clrType.GetInterfaces()

? ? ? ? ? ? .Union(new[] { clrType })

? ? ? ? ? ? .FirstOrDefault(

? ? ? ? ? ? ? ? t => TypeHelper.IsGenericType(t)

? ? ? ? ? ? ? ? ? ? ? ? && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));


? ? if (collectionInterface != null)

? ? {

? ? ? ? elementType = collectionInterface.GetGenericArguments().Single();

? ? ? ? return true;

? ? }


? ? return false;

}

如果我們跳回方法實現,我們最終會在類中SetValue(TEntityType entity, object value)調用。DeserializationHelpers.SetCollectionProperty(entity, _property.Name, edmPropertyType: null, value: value, clearCollection: true);DeserializationHelpers


if (_isCollection)

{

? ? DeserializationHelpers.SetCollectionProperty(instance, _property.Name, edmPropertyType: null,

? ? ? ? value: value, clearCollection: true);

}

很明顯,此方法的實現非常具有防御性,可以避免在集合值為 時拋出異常null。該方法的第一行是,并且在要執行的代碼塊之后if (value != null)沒有任何塊或代碼。else我們可以從字面上說,對于每個實現 的類型,空值都會被忽略IEnumerable<T>,因此不會被設置。


internal static void SetCollectionProperty(object resource, string propertyName,

? ? IEdmCollectionTypeReference edmPropertyType, object value, bool clearCollection)

{

? ? if (value != null)

? ? {

? ? ? ? IEnumerable collection = value as IEnumerable;

? ? ? ? Contract.Assert(collection != null,

? ? ? ? ? ? "SetCollectionProperty is always passed the result of ODataFeedDeserializer or ODataCollectionDeserializer");


? ? ? ? Type resourceType = resource.GetType();

? ? ? ? Type propertyType = GetPropertyType(resource, propertyName);


? ? ? ? Type elementType;

? ? ? ? if (!TypeHelper.IsCollection(propertyType, out elementType))

? ? ? ? {

? ? ? ? ? ? string message = Error.Format(SRResources.PropertyIsNotCollection, propertyType.FullName, propertyName, resourceType.FullName);

? ? ? ? ? ? throw new SerializationException(message);

? ? ? ? }


? ? ? ? IEnumerable newCollection;

? ? ? ? if (CanSetProperty(resource, propertyName) &&

? ? ? ? ? ? CollectionDeserializationHelpers.TryCreateInstance(propertyType, edmPropertyType, elementType, out newCollection))

? ? ? ? {

? ? ? ? ? ? // settable collections

? ? ? ? ? ? collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType);

? ? ? ? ? ? if (propertyType.IsArray)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? newCollection = CollectionDeserializationHelpers.ToArray(newCollection, elementType);

? ? ? ? ? ? }


? ? ? ? ? ? SetProperty(resource, propertyName, newCollection);

? ? ? ? }

? ? ? ? else

? ? ? ? {

? ? ? ? ? ? // get-only collections.

? ? ? ? ? ? newCollection = GetProperty(resource, propertyName) as IEnumerable;

? ? ? ? ? ? if (newCollection == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? string message = Error.Format(SRResources.CannotAddToNullCollection, propertyName, resourceType.FullName);

? ? ? ? ? ? ? ? throw new SerializationException(message);

? ? ? ? ? ? }


? ? ? ? ? ? if (clearCollection)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? newCollection.Clear(propertyName, resourceType);

? ? ? ? ? ? }


? ? ? ? ? ? collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType);

? ? ? ? }

? ? }

}

解決方案1


第一個可能的解決方案是創建自定義模型綁定程序并處理用于返回空字節數組并向模型綁定程序添加類null的值。byte[]NullByteArrayModelBinder


免責聲明:沒有測試過,但應該可以。


public class NullByteArrayModelBinder : DefaultModelBinder {

? ? public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {

? ? ? ? if(bindingContext.ModelType == typeof(byte[])) {

? ? ? ? ? ? return base.BindModel(controllerContext, bindingContext) ?? new byte[0];

? ? ? ? }


? ? ? ? return base.BindModel(controllerContext, bindingContext);

? ? }

}

這種方法有一個缺點。OData 的使用者還需要在現在進行檢查的array.Length > 0任何地方處理空數組。array != null


解決方案2


第二個選項是自定義序列化和反序列化。


序列化:從空array到null=>array.Length > 0 ? array : null;


反序列化:從null到空array=>array ?? new byte[0];


希望能幫助到你!


查看完整回答
反對 回復 2023-09-24
  • 1 回答
  • 0 關注
  • 140 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號