Json 反序列化和 ADO 持久性期间的字符串截断

String Truncation during Json Deserialization and ADO Persistance

我正在编写一个控制台应用程序,它将命中 REST api,将 JSON 反序列化为 C# 模型,然后使用 ADO 将该模型保存到数据库 table。

第一个问题是,在应用程序的第一个 运行 期间,我们发现其中一个 JSON 属性超出了 nvarchar(300) 的列定义。我们将该列增加到 nvarchar(4000),但我不知道是否有其他十几个字符串属性 可能 超过我给它们的默认值 300。

仅供参考,我得到的 SQL 错误是:

String or binary data would be truncated.

The data for table-valued parameter "@Items" doesn't conform to the table type of the parameter. SQL Server error is: 8152, state: 10

The statement has been terminated.

...如果我将长度为 500 的字符串传递给 nvarchar(300)

,这就有意义了

所以我的愿望:在 C# 中反序列化或模型创建期间,我想 t运行cate 字符串 properties/fields 并在我点击我的持久性代码之前给它们一个最大长度,这样我就可以以 100% 的信心确保我的字段永远不会超过 nvarchar 长度并且永远不会触发 'truncation error'.

我尝试使用 System.ComponentModel.DataAnnotations 和 [MaxLength(4000)],但这似乎仅适用于表单发布期间的 MVC 和输入验证。

我考虑过使用自定义设置器制作支持字段,但这意味着我的每个实体中的代码行数增加了一倍。我有 9 个实体,每个实体可能有 2 个我想要 configure/truncate.

的字符串

那么问题来了:有什么奇特的方法可以使用某种 NewtonSoft 数据注释或 C# 数据注释来分类字符串吗?运行另外,有没有一种神奇的方法可以避免拥有无数的后台?或者我应该只制作一个自定义字符串 class 并从具有最大长度 属性 的字符串继承?

Json.Net 没有内置的字符串截断功能,但您可以使用自定义的 ContractResolver in combination with a custom ValueProvider 来执行您想要的操作。 ContractResolver 将在所有 类 中查找字符串属性并将 ValueProvider 应用于它们,而 ValueProvider 将在反序列化期间进行实际截断。您可以使解析器使用默认的最大长度 300(或其他),但也可以查找您可能已应用于字符串属性的任何 [MaxLength] 属性(来自 System.ComponentModel.DataAnnotations)并将该长度用作覆盖。这样就可以处理长度为 4000 的情况。

这是您需要的代码:

public class StringTruncatingPropertyResolver : DefaultContractResolver
{
    public int DefaultMaxLength { get; private set; }

    public StringTruncatingPropertyResolver(int defaultMaxLength)
    {
        DefaultMaxLength = defaultMaxLength;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

        // Apply a StringTruncatingValueProvider to all string properties
        foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
        {
            var attr = prop.AttributeProvider
                           .GetAttributes(true)
                           .OfType<MaxLengthAttribute>()
                           .FirstOrDefault();
            int maxLength = attr != null ? attr.Length : DefaultMaxLength;
            prop.ValueProvider = new StringTruncatingValueProvider(prop.ValueProvider, maxLength);
        }

        return props;
    }

    class StringTruncatingValueProvider : IValueProvider
    {
        private IValueProvider InnerValueProvider { get; set; }
        private int MaxLength { get; set; }

        public StringTruncatingValueProvider(IValueProvider innerValueProvider, int maxLength)
        {
            InnerValueProvider = innerValueProvider;
            MaxLength = maxLength;
        }

        // GetValue is called by Json.Net during serialization.
        // The target parameter has the object from which to read the string;
        // the return value is a string that gets written to the JSON.
        public object GetValue(object target)
        {
            return InnerValueProvider.GetValue(target);
        }

        // SetValue gets called by Json.Net during deserialization.
        // The value parameter has the string value read from the JSON;
        // target is the object on which to set the (possibly truncated) value.
        public void SetValue(object target, object value)
        {
            string s = (string)value;
            if (s != null && s.Length > MaxLength)
            {
                s = s.Substring(0, MaxLength);
            }
            InnerValueProvider.SetValue(target, s);
        }
    }
}

要使用解析器,请将其添加到 JsonSerializerSettings 的实例并将设置传递给 JsonConvert.DeserializeObject,如下所示:

var settings = new JsonSerializerSettings
{
    ContractResolver = new StringTruncatingPropertyResolver(300)
};
var foo = JsonConvert.DeserializeObject<Foo>(json, settings);

这是一个工作演示:https://dotnetfiddle.net/YOGsP5