Значення за замовчуванням для обов’язкових полів у міграціях Entity Framework?


91

Я додав [Required]анотацію даних до однієї зі своїх моделей у програмі ASP.NET MVC . Після створення міграції, запуск Update-Databaseкоманди призводить до такої помилки:

Неможливо вставити значення NULL у стовпець "Директор", таблиця "MOVIES_cf7bad808fa94f89afa2e5dae1161e78.dbo.Movies"; стовпець не допускає нулів. Не вдається оновити. Заява припинена.

Це пов’язано з деякими записами, які мають NULL у своїх Directorстовпцях. Як я можу автоматично змінити ці значення на якийсь за замовчуванням (скажімо "Джон Доу") режисер?

Ось моя модель:

  public class Movie
    {
        public int ID { get; set; }
        [Required]
        public string Title { get; set; }

        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }

        [Required]
        public string Genre { get; set; }

        [Range(1,100)]
        [DataType(DataType.Currency)]
        public decimal Price { get; set; }

        [StringLength(5)]
        public string Rating { get; set; }

        [Required]     /// <--- NEW
        public string Director { get; set; }
    }

і ось моя остання міграція:

public partial class AddDataAnnotationsMig : DbMigration
{
    public override void Up()
    {
        AlterColumn("dbo.Movies", "Title", c => c.String(nullable: false));
        AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false));
        AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
        AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false));
    }

    public override void Down()
    {
        AlterColumn("dbo.Movies", "Director", c => c.String());
        AlterColumn("dbo.Movies", "Rating", c => c.String());
        AlterColumn("dbo.Movies", "Genre", c => c.String());
        AlterColumn("dbo.Movies", "Title", c => c.String());
    }
}

Відповіді:


74

Якщо я добре пам’ятаю, щось подібне повинно працювати:

AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, defaultValueSql: "'John Doe'"));

Примітка: Значення параметра defaultValueSql розглядається як дослівний оператор SQL, тому, якщо потрібне значення є фактично рядком, як приклад Джона Доу, тоді навколо значення потрібні одинарні лапки.


9
Я теж так думав, але, здається, це не працює для існуючих записів. Тому я все ще отримую повідомлення про помилку.
Андрій Дроздюк

@drozzy Можливо, це помилка, як тут: EF 4.3.1 Виняток міграції - AlterColumn defaultValueSql створює одне і те ж ім'я обмеження за замовчуванням для різних таблиць. Ви можете оновити рядки за допомогою IS NULLперевірки за вашим запитом.
розробник

Цікаво, але я не впевнений, що розумію, про що вони говорять. Однак якщо це помилка, то так, це мало б сенс.
Андрій Дроздюк

6
Я думаю, це повинно бути: "'John Doe'"- вам потрібно використовувати цитати SQL.
Шон

1
@webdeveloper, я не думаю, що це помилка, навіщо AlterColumnоновлювати поточні значення? Це команда DDL (не DML).
Антон

110

На додаток до відповіді від @webdeveloper та @Pushpendra, вам потрібно вручну додати оновлення до міграції, щоб оновити існуючі рядки. Наприклад:

public override void Up()
{
    Sql("UPDATE [dbo].[Movies] SET Title = 'No Title' WHERE Title IS NULL");
    AlterColumn("dbo.Movies", "Title", c => c.String(nullable: false,defaultValue:"MyTitle"));
}

Це відбувається тому, що AlterColumnвиробляє DDL для встановлення за замовчуванням стовпця певного значення в специфікації таблиці. DDL не впливає на існуючі рядки в базі даних.

Ви фактично вносите дві зміни одночасно (встановлюючи значення за замовчуванням і роблячи стовпець NOT NULL), і кожне з них є дійсним окремо, але оскільки ви робите ці два одночасно, ви можете очікувати, що система ' розумно 'реалізуйте свій намір і налаштуйте все NULL значень значення за замовчуванням, але це не те, що очікується весь час.

Припустимо, ви встановлюєте лише значення за замовчуванням для стовпця, а не робите його NOT NULL. Ви очевидно не очікуєте, що всі записи NULL будуть оновлені за замовчуванням, яке ви надаєте.

Отже, на мій погляд, це не помилка, і я не хочу, щоб EF оновлював свої дані так, як я явно не наказав це робити. Розробник несе відповідальність за інструктаж системи щодо того, що робити з даними.


17
Для людей, які знайшли цю відповідь через google: я щойно спробував це в EF6, і заява про оновлення, здається, не потрібна (більше). Я думаю, вони все-таки вважали це помилкою.
EPLKleijntjens

3
Я також можу за це поручитися. Якщо вам потрібне значення за замовчуванням навіть для поля, що допускає нуль, просто змініть його спочатку на ненульоване зі значенням за замовчуванням, а потім знову змініть на нульове. Дуже зручно, коли ви додали ненульоване поле до дочірнього класу :)
Wouter Schut

1
Пляма щодо пояснення. AlterColumn () просто змінює визначення стовпця. Це ніяк не впливає на існуючі записи
Korayem,

10
public partial class AddDataAnnotationsMig : DbMigration
{
    public override void Up()
    {
        AlterColumn("dbo.Movies", "Title", c => c.String(nullable: false,defaultValue:"MyTitle"));
        AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false,defaultValue:"Genre"));
        AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
        AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false,defaultValue:"Director"));

    }

    public override void Down()
    {       
        AlterColumn("dbo.Movies", "Director", c => c.String());
        AlterColumn("dbo.Movies", "Rating", c => c.String());
        AlterColumn("dbo.Movies", "Genre", c => c.String());
        AlterColumn("dbo.Movies", "Title", c => c.String());       
    }
}

2
Гм ... дякую, але чим це відрізняється від відповіді @ webdeveloper?
Андрій Дроздюк

1
він не повідомляє вам, куди потрібно додати параметр значення за замовчуванням
Пушпендра

1
@Pushpendra, смішно, як розробники, як правило, забувають, що колись давно вони мало що знали. Мені подобаються докладні відповіді, які задовольняють всі рівні. Відмінна робота!
корисно Бджола

5

Не впевнений, чи завжди був цей параметр, але просто зіткнувся з подібною проблемою, виявив, що я зміг встановити значення за замовчуванням, не виконуючи жодних оновлень вручну, використовуючи наступне

defaultValueSql: "'NY'"

Я отримав помилку, коли вказане значення було "NY"тоді, я зрозумів, що вони очікують такого значення SQL, як "GETDATE()"я намагався, "'NY'"і це зробило трюк

весь рядок виглядає так

AddColumn("TABLE_NAME", "State", c => c.String(maxLength: 2, nullable: false, defaultValueSql: "'NY'"));

Завдяки цій відповіді я вийшов на правильний шлях


2

Оскільки EF Core 2.1, ви можете використовувати MigrationBuilder.UpdateDataдля зміни значень перед зміною стовпця (чистіше, ніж використання необробленого SQL):

protected override void Up(MigrationBuilder migrationBuilder)
{
    // Change existing NULL values to NOT NULL values
    migrationBuilder.UpdateData(
        table: tableName,
        column: columnName,
        value: valueInsteadOfNull,
        keyColumn: columnName,
        keyValue: null);

    // Change column type to NOT NULL
    migrationBuilder.AlterColumn<ColumnType>(
        table: tableName,
        name: columnName,
        nullable: false,
        oldClrType: typeof(ColumnType),
        oldNullable: true);
}

1

Я виявив, що простого використання Ініціалізатора властивостей власності на властивість сутності достатньо, щоб зробити роботу.

Наприклад:

public class Thing {
    public bool IsBigThing { get; set; } = false;
}

2
Це хороша відповідь (мені допомогло), але це не додає значення за замовчуванням до бази даних, воно встановлює значення в коді.
chris31389

право, це не додало значення за замовчуванням у базі даних після міграційних змін
Четан Чаудхарі,

1

Багато інших відповідей зосереджені на тому, як втручатися вручну, коли виникають ці проблеми.

Після генерації міграції виконайте будь-яку з наступних змін у міграції:

  1. Змініть визначення стовпця, щоб включити оператор defaultValue або defaultSql:
    AlterColumn("dbo.Movies", "Director", c => c.String(nullable: false, default: ""));

  2. Введіть оператор SQL для попереднього заповнення існуючих стовпців перед AlterColumn:
    Sql("UPDATE dbo.Movies SET Director = '' WHERE Director IS NULL");

Майте на увазі, що ручні зміни, застосовані до сценарію міграції, будуть замінені, якщо ви перемонтуєте міграцію. Для першого рішення досить просто розширити EF, щоб автоматично визначити значення за замовчуванням у полі як частину генерації міграції.

ПРИМІТКА. EF не робить це автоматично для вас, оскільки реалізація значення за замовчуванням буде різною для кожного постачальника СУБД, а також тому, що значення за замовчуванням мають менше значення в чистому середовищі виконання EF, оскільки кожна вставка рядка надає поточне значення для кожної властивості, навіть якщо воно є нульовим, тому обмеження значення за замовчуванням ніколи не обчислюється.
Ця заява AlterColumn - це єдиний раз, коли вступає у дію обмеження за замовчуванням, я думаю, це стає нижчим пріоритетом для команди, яка розробила реалізацію міграції SQL Server.

Наступне рішення поєднує позначення атрибутів, конфігурації моделі та анотації стовпців для передачі через метадані до спеціального генератора коду міграції. Етапи 1 і 2 можна замінити плавними позначеннями для кожного ураженого поля, якщо ви не використовуєте позначення атрибутів.
Тут грає багато прийомів, сміливо використовуйте деякі чи всі, я сподіваюся, що тут є цінність для всіх


  1. Оголосіть значення за замовчуванням
    Створіть або переназначте існуючий атрибут, щоб визначити значення за замовчуванням, для цього прикладу ми створимо новий атрибут, який називається DefaultValue, який успадковується від ComponentModel.DefaultValueAttribute, оскільки використання є інтуїтивним, і існує ймовірність того, що існуючі бази коду вже реалізують цей атрибут. За допомогою цієї реалізації вам потрібно використовувати лише цей атрибут для доступу до DefaultValueSql, який корисний для дат та інших нестандартних сценаріїв.

    Впровадження

    [DefaultValue("Insert DefaultValue Here")]
    [Required]     /// <--- NEW
    public string Director { get; set; }
    
    // Example of default value sql
    [DefaultValue(DefaultValueSql: "GetDate()")]
    [Required]
    public string LastModified { get; set; }

    Визначення атрибутів

    namespace EFExtensions
    {
        /// <summary>
        /// Specifies the default value for a property but allows a custom SQL statement to be provided as well. <see cref="MiniTuber.Database.Conventions.DefaultValueConvention"/>
        /// </summary>
        public class DefaultValueAttribute : System.ComponentModel.DefaultValueAttribute
        {
            /// <summary>
            /// Specifies the default value for a property but allows a custom SQL statement to be provided as well. <see cref="MiniTuber.Database.Conventions.DefaultValueConvention"/>
            /// </summary>
            public DefaultValueAttribute() : base("")
            {
            }
    
            /// <i
            /// <summary>
            /// Optional SQL to use to specify the default value.
            /// </summary>
            public string DefaultSql { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a Unicode character.
            /// </summary>
            /// <param name="value">
            /// A Unicode character that is the default value.
            /// </param>
            public DefaultValueAttribute(char value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using an 8-bit unsigned integer.
            /// </summary>
            /// <param name="value">
            /// An 8-bit unsigned integer that is the default value.
            /// </param>
            public DefaultValueAttribute(byte value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a 16-bit signed integer.
            /// </summary>
            /// <param name="value">
            /// A 16-bit signed integer that is the default value.
            /// </param>
            public DefaultValueAttribute(short value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a 32-bit signed integer.
            /// </summary>
            /// <param name="value">
            /// A 32-bit signed integer that is the default value.
            /// </param>
            public DefaultValueAttribute(int value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a 64-bit signed integer.
            /// </summary>
            /// <param name="value">
            /// A 64-bit signed integer that is the default value.
            /// </param>
            public DefaultValueAttribute(long value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a single-precision floating point number.
            /// </summary>
            /// <param name="value">
            /// A single-precision floating point number that is the default value.
            /// </param>
            public DefaultValueAttribute(float value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a double-precision floating point number.
            /// </summary>
            /// <param name="value">
            /// A double-precision floating point number that is the default value.
            /// </param>
            public DefaultValueAttribute(double value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a System.Boolean value.
            /// </summary>
            /// <param name="value">
            /// A System.Boolean that is the default value.
            /// </param>
            public DefaultValueAttribute(bool value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class using a System.String.
            /// </summary>
            /// <param name="value">
            /// A System.String that is the default value.
            /// </param>
            public DefaultValueAttribute(string value) : base(value) { }
    
            /// <summary>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class.
            /// </summary>
            /// <param name="value">
            /// An System.Object that represents the default value.
            /// </param>
            public DefaultValueAttribute(object value) : base(value) { }
    
            /// /// <inheritdoc/>
            /// Initializes a new instance of the System.ComponentModel.DefaultValueAttribute
            /// class, converting the specified value to the specified type, and using an invariant
            /// culture as the translation context.
            /// </summary>
            /// <param name="type">
            /// A System.Type that represents the type to convert the value to.
            /// </param>
            /// <param name="value">
            /// A System.String that can be converted to the type using the System.ComponentModel.TypeConverter
            /// for the type and the U.S. English culture.
            /// </param>
            public DefaultValueAttribute(Type type, string value) : base(value) { }
        }
    }
  2. Створіть домовленість про введення значення за замовчуванням до анотацій стовпців
    стовпців використовуються для передачі користувацьких метаданих про стовпці до генератора сценаріїв міграції.
    Використання домовленості для цього демонструє силу нотації атрибутів для спрощення того, як можна визначити та маніпулювати плавними метаданими для багатьох властивостей, а не вказувати їх окремо для кожного поля.

    namespace EFExtensions
    {
    
        /// <summary>
        /// Implement SQL Default Values from System.ComponentModel.DefaultValueAttribute
        /// </summary>
        public class DefaultValueConvention : Convention
        {
            /// <summary>
            /// Annotation Key to use for Default Values specified directly as an object
            /// </summary>
            public const string DirectValueAnnotationKey = "DefaultValue";
            /// <summary>
            /// Annotation Key to use for Default Values specified as SQL Strings
            /// </summary>
            public const string SqlValueAnnotationKey = "DefaultSql";
    
            /// <summary>
            /// Implement SQL Default Values from System.ComponentModel.DefaultValueAttribute
            /// </summary>
            public DefaultValueConvention()
            {
                // Implement SO Default Value Attributes first
                this.Properties()
                        .Where(x => x.HasAttribute<EFExtensions.DefaultValueAttribute>())
                        .Configure(c => c.HasColumnAnnotation(
                            c.GetAttribute<EFExtensions.DefaultValueAttribute>().GetDefaultValueAttributeKey(),
                            c.GetAttribute<EFExtensions.DefaultValueAttribute>().GetDefaultValueAttributeValue()
                            ));
    
                // Implement Component Model Default Value Attributes, but only if it is not the SO implementation
                this.Properties()
                        .Where(x => x.HasAttribute<System.ComponentModel.DefaultValueAttribute>())
                        .Where(x => !x.HasAttribute<MiniTuber.DataAnnotations.DefaultValueAttribute>())
                        .Configure(c => c.HasColumnAnnotation(
                            DefaultValueConvention.DirectValueAnnotationKey, 
                            c.GetAttribute<System.ComponentModel.DefaultValueAttribute>().Value
                            ));
            }
        }
    
        /// <summary>
        /// Extension Methods to simplify the logic for building column annotations for Default Value processing
        /// </summary>
        public static partial class PropertyInfoAttributeExtensions
        {
            /// <summary>
            /// Wrapper to simplify the lookup for a specific attribute on a property info.
            /// </summary>
            /// <typeparam name="T">Type of attribute to lookup</typeparam>
            /// <param name="self">PropertyInfo to inspect</param>
            /// <returns>True if an attribute of the requested type exists</returns>
            public static bool HasAttribute<T>(this PropertyInfo self) where T : Attribute
            {
                return self.GetCustomAttributes(false).OfType<T>().Any();
            }
    
            /// <summary>
            /// Wrapper to return the first attribute of the specified type
            /// </summary>
            /// <typeparam name="T">Type of attribute to return</typeparam>
            /// <param name="self">PropertyInfo to inspect</param>
            /// <returns>First attribuite that matches the requested type</returns>
            public static T GetAttribute<T>(this System.Data.Entity.ModelConfiguration.Configuration.ConventionPrimitivePropertyConfiguration self) where T : Attribute
            {
                return self.ClrPropertyInfo.GetCustomAttributes(false).OfType<T>().First();
            }
    
            /// <summary>
            /// Helper to select the correct DefaultValue annotation key based on the attribute values
            /// </summary>
            /// <param name="self"></param>
            /// <returns></returns>
            public static string GetDefaultValueAttributeKey(this EFExtensions.DefaultValueAttribute self)
            {
                return String.IsNullOrWhiteSpace(self.DefaultSql) ? DefaultValueConvention.DirectValueAnnotationKey : DefaultValueConvention.SqlValueAnnotationKey;
            }
    
            /// <summary>
            /// Helper to select the correct attribute property to send as a DefaultValue annotation value
            /// </summary>
            /// <param name="self"></param>
            /// <returns></returns>
            public static object GetDefaultValueAttributeValue(this EFExtensions.DefaultValueAttribute self)
            {
                return String.IsNullOrWhiteSpace(self.DefaultSql) ? self.Value : self.DefaultSql;
            }
        }
    
    }
  3. Додайте Конвенцію до DbContext
    Є багато способів досягти цього, я хотів би оголосити конвенції першим користувацьким кроком у моїй логіці ModelCreation, це буде у вашому класі DbContext.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        // Use our new DefaultValueConvention
        modelBuilder.Conventions.Add<EFExtensions.DefaultValueConvention>();
    
        // My personal favourites ;)
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
    
    }
  4. Замінити MigrationCodeGenerator
    Тепер, коли ці анотації були застосовані до визначень стовпців у моделі, нам потрібно змінити генератор сценарію міграції, щоб використовувати ці анотації. Для цього ми успадкуємо від, System.Data.Entity.Migrations.Design.CSharpMigrationCodeGeneratorоскільки нам потрібно лише внести мінімальну кількість змін.
    Після обробки нашої власної анотації нам потрібно видалити її з визначення стовпця, щоб запобігти її серіалізації до остаточного виводу.

    Перегляньте код базового класу, щоб вивчити інше використання: http://entityframework.codeplex.com/sourcecontrol/latest#src/EntityFramework/Migrations/Design/CSharpMigrationCodeGenerator.cs

    namespace EFExtensions
    {
        /// <summary>
        /// Implement DefaultValue constraint definition in Migration Scripts.
        /// </summary>
        /// <remarks>
        /// Original guide that provided inspiration for this https://romiller.com/2012/11/30/code-first-migrations-customizing-scaffolded-code/
        /// </remarks>
        public class CustomCodeGenerator : System.Data.Entity.Migrations.Design.CSharpMigrationCodeGenerator
        {
            /// <summary>
            /// Inject Default values from the DefaultValue attribute, if the DefaultValueConvention has been enabled.
            /// </summary>
            /// <seealso cref="DefaultValueConvention"/>
            /// <param name="column"></param>
            /// <param name="writer"></param>
            /// <param name="emitName"></param>
            protected override void Generate(ColumnModel column, IndentedTextWriter writer, bool emitName = false)
            {
                var annotations = column.Annotations?.ToList();
                if (annotations != null && annotations.Any())
                {
                    for (int index = 0; index < annotations.Count; index ++)
                    {
                        var annotation = annotations[index];
                        bool handled = true;
    
                        try
                        {
                            switch (annotation.Key)
                            {
                                case DefaultValueConvention.SqlValueAnnotationKey:
                                    if (annotation.Value?.NewValue != null)
                                    {
                                        column.DefaultValueSql = $"{annotation.Value.NewValue}";
                                    }
                                    break;
                                case DefaultValueConvention.DirectValueAnnotationKey:
                                    if (annotation.Value?.NewValue != null)
                                    {
                                        column.DefaultValue = Convert.ChangeType(annotation.Value.NewValue, column.ClrType);
                                    }
                                    break;
                                default:
                                    handled = false;
                                    break;
                            }
                        }
                        catch(Exception ex)
                        {
                            // re-throw with specific debug information
                            throw new ApplicationException($"Failed to Implement Column Annotation for column: {column.Name} with key: {annotation.Key} and new value: {annotation.Value.NewValue}", ex);
                        }
    
                        if(handled)
                        {
                            // remove the annotation, it has been applied
                            column.Annotations.Remove(annotation.Key);
                        }
                    }
                }
                base.Generate(column, writer, emitName);
            }
    
            /// <summary>
            /// Generates class summary comments and default attributes
            /// </summary>
            /// <param name="writer"> Text writer to add the generated code to. </param>
            /// <param name="designer"> A value indicating if this class is being generated for a code-behind file. </param>
            protected override void WriteClassAttributes(IndentedTextWriter writer, bool designer)
            {
                writer.WriteLine("/// <summary>");
                writer.WriteLine("/// Definition of the Migration: {0}", this.ClassName);
                writer.WriteLine("/// </summary>");
                writer.WriteLine("/// <remarks>");
                writer.WriteLine("/// Generated Time: {0}", DateTime.Now);
                writer.WriteLine("/// Generated By: {0}", Environment.UserName);
                writer.WriteLine("/// </remarks>");
                base.WriteClassAttributes(writer, designer);
            }
    
    
        }
    }
  5. Реєстрація CustomCodeGenerator
    Останній крок, у файлі конфігурації DbMigration нам потрібно вказати генератор коду, який слід використовувати, за замовчуванням шукайте Configuration.cs у вашій папці Migration ...

    internal sealed class Configuration : DbMigrationsConfiguration<YourApplication.Database.Context>
    {
        public Configuration()
        {
            // I recommend that auto-migrations be disabled so that we control
            // the migrations explicitly 
            AutomaticMigrationsEnabled = false;
            CodeGenerator = new EFExtensions.CustomCodeGenerator();
        }
    
        protected override void Seed(YourApplication.Database.Context context)
        {
            //   Your custom seed logic here
        }
    }

0

З якоїсь причини те, що я не зміг пояснити себе, схвалена відповідь більше не працює для мене.

Він працював з іншим додатком, а з тим, що я працюю, ні.

Отже, альтернативним, але досить неефективним рішенням було б замінити метод SaveChanges (), як показано нижче. Цей метод повинен належати до класу Context.

    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries().Where(entry => entry.Entity.GetType().GetProperty("ColumnName") != null))
        {
            if (entry.State == EntityState.Added)
            {
                entry.Property("ColumnName").CurrentValue = "DefaultValue";
            }
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.