Несколько дней назад я писал в блоге о возможности сохранения журналов log4net в базе данных Mongo , но вы должны знать, что этот метод может быть опасным, если ваши объекты имеют циклические ссылки. Циклическая происходит , когда объект А ссылка на объект B и объект B , прямо или косвенно , ссылка на объект возвращается назад, и это высокий риск , когда вы работаете с Монго Serializer.
Mongo Serializer не любит циклические ссылки (это совершенно приемлемо, поскольку документы с циклическими ссылками не могут быть сохранены в базе данных документов), но проблема заключается в следующем: если вы попытаетесь сериализовать объект, имеющий циклическую ссылку, вы получите исключение StackOverflowException и ваш процесс потерпит крах, как указано в официальной документации MSDN
Начиная с версии .NET Framework 2.0, объект StackOverflowException не может быть перехвачен блоком try-catch, и соответствующий процесс завершается по умолчанию . Следовательно, пользователям рекомендуется писать свой код для обнаружения и предотвращения переполнения стека.
Если вы помните, как я модифицировал приложение MongoDb log4net, я решил сохранить сложные объекты MongoDB с помощью следующего кода:
if (compositeProperties != null && compositeProperties.Count > 0) { var properties = new BsonDocument(); foreach (DictionaryEntry entry in compositeProperties) { BsonValue value; if (!BsonTypeMapper.TryMapToBsonValue(entry.Value, out value)) { properties[entry.Key.ToString()] = entry.Value.ToBsonDocument(); } else { properties[entry.Key.ToString()] = value; } } toReturn["customproperties"] = properties; }
Ключевой момент заключается в entry.Value.ToBsonDocument (), потому что, если кто-то сохранит в глобальном контексте log4Net объект, содержащий циклическую ссылку, ваша программа будет прервана при следующем вызове log4net , поскольку исключение StackOverflowException не может быть перехвачено .
Это особенно раздражает, когда вы хотите сохранить в своем журнале объект, который поступает из базы данных с ORM, например NHibernate , потому что каждый объект, имеющий ссылку на Bag, обычно гидратируется с помощью PersistentBag , внутреннего класса по nhibernate , который имеет циклическую ссылку , Простое решение этого процесса — сообщить MONGO, какой сериализатор использовать для таких специфических типов.
Техника проста, драйверы Mongo предоставляют возможность легко зарегистрировать пользовательский поставщик сериализации.
BsonSerializer.RegisterSerializationProvider(new LoggerBsonSerializerProvider());
И это код класса, который реализует интерфейс ISerializationProvider
public class LoggerBsonSerializerProvider : IBsonSerializationProvider { public IBsonSerializer GetSerializer(Type type) { if (type.FullName.Contains("Nhibernate", StringComparison.OrdinalIgnoreCase)) { return BsonNullSerializer.Instance; } return null; } }
Единственная функция, которую вам нужно реализовать — это GetSerializer , в этом простом примере для всех типов, которые содержат строку NHibernate, просто вернуть BsonNullSerializer. Это в основном говорит Mongo Serializer игнорировать эти типы. На мой взгляд, это лучший подход, поскольку он позволяет избежать риска сериализации внутренних классов NHibernate, которые фактически могут вызвать исключение StackOverflowException . Если вы хотите сериализовать NHibernate PersistentGenericBag, но не хотите рисковать циклической ссылкой, вы можете использовать этот код вместо этого.
public class LoggerBsonSerializerProvider : IBsonSerializationProvider { public IBsonSerializer GetSerializer(Type type) { if (type.FullName.Contains("Nhibernate", StringComparison.OrdinalIgnoreCase)) { if (type.TypeImplementsGenericInterface(typeof(IList<>))) { return EnumerableSerializer.Instance; } return BsonNullSerializer.Instance; } return null; } } public static Boolean TypeImplementsGenericInterface(this Type typeToCheck, Type interfaceToLookFor) { return typeToCheck.GetInterface(interfaceToLookFor.Name) != null; }
Основное отличие состоит в том, что для каждого внутреннего типа NHibernate, реализующего общий IList <>, я говорю Mongo сериализовать с использованием EnumerableSerializer, этот тип сериализатора позволяет избежать проблемы циклической ссылки, поскольку PersistentGenericBag обрабатывается как IList <>, игнорируя его действительный свойства. Этот подход по-прежнему небезопасен, поскольку необходимо убедиться, что коллекция уже загружена из базы данных или объект не отсоединен, чтобы избежать исключения во время ведения журнала, поскольку коллекция не может быть инициализирована . Этот тип исключений можно отследить, поэтому он может быть незначительной проблемой, потому что вы можете обработать его с помощью простой попытки try внутри Mongo Appender.