Définition de la valeur par défaut d'une propriété DateTime sur DateTime.Now dans la valeur par défaut System.ComponentModel Attrbute


94

Quelqu'un sait-il comment je peux spécifier la valeur par défaut pour une propriété DateTime à l'aide de l'attribut System.ComponentModel DefaultValue?

par exemple j'essaye ceci:

[DefaultValue(typeof(DateTime),DateTime.Now.ToString("yyyy-MM-dd"))]
public DateTime DateCreated { get; set; }

Et il s'attend à ce que la valeur soit une expression constante.

C'est dans le contexte de l'utilisation avec ASP.NET Dynamic Data. Je ne veux pas échafauder la colonne DateCreated mais simplement fournir le DateTime.Now s'il n'est pas présent. J'utilise Entity Framework comme couche de données

À votre santé,

Andrew

Réponses:


93

Vous ne pouvez pas faire cela avec un attribut car ce ne sont que des méta-informations générées au moment de la compilation. Ajoutez simplement du code au constructeur pour initialiser la date si nécessaire, créez un déclencheur et gérez les valeurs manquantes dans la base de données, ou implémentez le getter de manière à renvoyer DateTime.Now si le champ de sauvegarde n'est pas initialisé.

public DateTime DateCreated
{
   get
   {
      return this.dateCreated.HasValue
         ? this.dateCreated.Value
         : DateTime.Now;
   }

   set { this.dateCreated = value; }
}

private DateTime? dateCreated = null;

2
Merci, avant que vous éditiez, je suis allé dans l'autre sens et j'ai intercepté le passeur. Vive l'aide :-)
REA_ANDREW

10
Une getpartie peut être simplifiée à:return dateCreated ?? DateTime.Now;
Vsevolod Krasnov

Cette solution pourrait être un problème au cas où vous voudriez que vos classes soient POCO. Dans mon cas, ma classe doit être aussi claire que la classe POCO pour être transférée via WCF.
Jacob

1
@zHs Cela a très bien fonctionné pour moi en utilisant Database First avec Entity Framework.
Joshua K

1
Veuillez voir cette réponse si vous êtes venu ici pour un attribut.
Storm Muller

55

Ajouter ci-dessous à la propriété DateTime

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

3
C'est en fait ce que je recherchais et ça devrait monter plus haut. Beaucoup d'autres réponses ajoutent la date actuelle lors de l'insertion d'une entité, mais cela n'affecte pas le schéma (c'est-à-dire que getdate () est la valeur par défaut). Le problème pour moi était lorsque je voulais ajouter un enregistrement directement dans la table (en utilisant SQL), je devais fournir la date (ou la laisser NULL). C'est une bien meilleure solution. Merci.
Storm Muller

8
.Calculé est pour les actions Ajouter et Mettre à jour. Utilisez .Identity pour Add uniquement.
Renan Coelho

1
Meilleure réponse de loin: il faut ajouter qu'une référence à System.ComponentModel.DataAnnotations.Schema devrait être ajoutée pour l'annotation DatabaseGenerated
Yazan Khalaileh

Quel fuseau horaire sera utilisé? Sera-ce UTC?
Almis

25

Il n'y a aucune raison que je puisse trouver que cela ne devrait pas être possible à travers un attribut. C'est peut-être dans l'arriéré de Microsoft. Qui sait.

La meilleure solution que j'ai trouvée est d'utiliser le paramètre defaultValueSql dans la première migration du code.

CreateTable(
    "dbo.SomeTable",
    c => new
        {
            TheDateField = c.DateTime(defaultValueSql: "GETDATE()")
        });

Je n'aime pas la solution souvent de référence consistant à le définir dans le constructeur de la classe d'entité, car si autre chose que Entity Framework colle un enregistrement dans cette table, le champ de date n'obtiendra pas de valeur par défaut. Et l'idée d'utiliser un déclencheur pour gérer ce cas me semble tout simplement erronée.


1
en cas de modification et d'ajout de nullable, utilisez AddColumn ("dbo.table", "Created", c => c.DateTime (nullable: true, defaultValueSql: "getdate ()"));
Iman

Ce n'est pas une bonne idée, si vous choisissez votre base de données, vous devez refactoriser ce code.
lails

22

J'ai testé cela sur EF core 2.1

Ici, vous ne pouvez utiliser ni les conventions ni les annotations de données. Vous devez utiliser l' API Fluent .

class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Created)
            .HasDefaultValueSql("getdate()");
    }
}

Doc officiel


12

C'est possible et assez simple:

pour DateTime.MinValue

[System.ComponentModel.DefaultValue(typeof(DateTime), "")]

pour toute autre valeur en tant que dernier argument de la DefaultValueAttributechaîne de spécification qui représente la valeur DateTime souhaitée.

Cette valeur doit être une expression constante et est requise pour créer object ( DateTime) en utilisant TypeConverter.


J'aimerais croire que l'utilisation fonctionne pour moi, mais malheureusement ce n'est pas le cas. Résultat de la migration; defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)
Abdurrahman I.

J'ai juste essayé de mettre une date de chaîne ("1/1/20") au lieu d'une chaîne vide et il la convertit avec succès.
user890332

5

Une solution simple si vous utilisez Entity Framework est d'ajouter une classe partielle et de définir un constructeur pour l'entité car le framework n'en définit pas un. Par exemple, si vous avez une entité nommée Example, vous placez le code suivant dans un fichier séparé.

namespace EntityExample
{
    public partial class Example : EntityObject
    {
        public Example()
        {
            // Initialize certain default values here.
            this._DateCreated = DateTime.Now;
        }
    }
}

C'est de loin la solution la plus élégante (et probablement aussi la solution envisagée) au problème. Mais cela suppose que les gens utilisent EF Code First.

1
C'est comme ça que je fais, jusqu'à ce que je réalise que l'ajout d'une propriété complexe à mon entité oblige EF à générer un constructeur par défaut, ne me laissant aucun moyen d'écrire le mien ...
Dave Cousineau

5

Je pense que la solution la plus simple est de définir

Created DATETIME2 NOT NULL DEFAULT GETDATE()

dans la déclaration de colonne et dans le concepteur EntityModel VS2010, définissez la propriété de colonne correspondante StoreGeneratedPattern = Computed .


Si je le fais comme ça et que je rends la propriété Nullable, j'obtiens une erreur de validation EDMX, car une colonne non Nullable est mappée à une propriété Nullable.
Niklas Peter

4

La création d'une nouvelle classe d'attributs est une bonne suggestion. Dans mon cas, je voulais spécifier «default (DateTime)» ou «DateTime.MinValue» afin que le sérialiseur Newtonsoft.Json ignore les membres DateTime sans valeurs réelles.

[JsonProperty( DefaultValueHandling = DefaultValueHandling.Ignore )]
[DefaultDateTime]
public DateTime EndTime;

public class DefaultDateTimeAttribute : DefaultValueAttribute
{
    public DefaultDateTimeAttribute()
        : base( default( DateTime ) ) { }

    public DefaultDateTimeAttribute( string dateTime )
        : base( DateTime.Parse( dateTime ) ) { }
}

Sans l'attribut DefaultValue, le sérialiseur JSON afficherait "1/1/0001 12:00:00 AM" même si l'option DefaultValueHandling.Ignore a été définie.


4

Pensez simplement à définir sa valeur dans le constructeur de votre classe d'entité

public class Foo
{
       public DateTime DateCreated { get; set; }
       public Foo()
       {
           DateCreated = DateTime.Now;
       }

}

Cela a fonctionné pour moi. Bien que j'ai dû définir la Datetimepropriété nullable avant de faire cela.
Ahashan Alam Sojib

3

J'avais besoin d'un horodatage UTC comme valeur par défaut et j'ai donc modifié la solution de Daniel comme ceci:

    [Column(TypeName = "datetime2")]
    [XmlAttribute]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
    [Display(Name = "Date Modified")]
    [DateRange(Min = "1900-01-01", Max = "2999-12-31")]
    public DateTime DateModified {
        get { return dateModified; }
        set { dateModified = value; } 
    }
    private DateTime dateModified = DateTime.Now.ToUniversalTime();

Pour le didacticiel DateRangeAttribute, consultez cet article de blog génial


3

using System.ComponentModel.DataAnnotations.Schema;

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedOn { get; private set; }

1
S'il vous plaît vérifier comment répondre
Morse

2

Il y a un moyen. Ajoutez ces classes:

DefaultDateTimeValueAttribute.cs

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Custom.Extensions;

namespace Custom.DefaultValueAttributes
{
    /// <summary>
    /// This class's DefaultValue attribute allows the programmer to use DateTime.Now as a default value for a property.
    /// Inspired from https://code.msdn.microsoft.com/A-flexible-Default-Value-11c2db19. 
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public sealed class DefaultDateTimeValueAttribute : DefaultValueAttribute
    {
        public string DefaultValue { get; set; }
        private object _value;

        public override object Value
        {
            get
            {
                if (_value == null)
                    return _value = GetDefaultValue();

                return _value;
            }
        }

        /// <summary>
        /// Initialized a new instance of this class using the desired DateTime value. A string is expected, because the value must be generated at runtime.
        /// Example of value to pass: Now. This will return the current date and time as a default value. 
        /// Programmer tip: Even if the parameter is passed to the base class, it is not used at all. The property Value is overridden.
        /// </summary>
        /// <param name="defaultValue">Default value to render from an instance of <see cref="DateTime"/></param>
        public DefaultDateTimeValueAttribute(string defaultValue) : base(defaultValue)
        {
            DefaultValue = defaultValue;
        }

        public static DateTime GetDefaultValue(Type objectType, string propertyName)
        {
            var property = objectType.GetProperty(propertyName);
            var attribute = property.GetCustomAttributes(typeof(DefaultDateTimeValueAttribute), false)
                ?.Cast<DefaultDateTimeValueAttribute>()
                ?.FirstOrDefault();

            return attribute.GetDefaultValue();
        }

        private DateTime GetDefaultValue()
        {
            // Resolve a named property of DateTime, like "Now"
            if (this.IsProperty)
            {
                return GetPropertyValue();
            }

            // Resolve a named extension method of DateTime, like "LastOfMonth"
            if (this.IsExtensionMethod)
            {
                return GetExtensionMethodValue();
            }

            // Parse a relative date
            if (this.IsRelativeValue)
            {
                return GetRelativeValue();
            }

            // Parse an absolute date
            return GetAbsoluteValue();
        }

        private bool IsProperty
            => typeof(DateTime).GetProperties()
                .Select(p => p.Name).Contains(this.DefaultValue);

        private bool IsExtensionMethod
            => typeof(DefaultDateTimeValueAttribute).Assembly
                .GetType(typeof(DefaultDateTimeExtensions).FullName)
                .GetMethods()
                .Where(m => m.IsDefined(typeof(ExtensionAttribute), false))
                .Select(p => p.Name).Contains(this.DefaultValue);

        private bool IsRelativeValue
            => this.DefaultValue.Contains(":");

        private DateTime GetPropertyValue()
        {
            var instance = Activator.CreateInstance<DateTime>();
            var value = (DateTime)instance.GetType()
                .GetProperty(this.DefaultValue)
                .GetValue(instance);

            return value;
        }

        private DateTime GetExtensionMethodValue()
        {
            var instance = Activator.CreateInstance<DateTime>();
            var value = (DateTime)typeof(DefaultDateTimeValueAttribute).Assembly
                .GetType(typeof(DefaultDateTimeExtensions).FullName)
                .GetMethod(this.DefaultValue)
                .Invoke(instance, new object[] { DateTime.Now });

            return value;
        }

        private DateTime GetRelativeValue()
        {
            TimeSpan timeSpan;
            if (!TimeSpan.TryParse(this.DefaultValue, out timeSpan))
            {
                return default(DateTime);
            }

            return DateTime.Now.Add(timeSpan);
        }

        private DateTime GetAbsoluteValue()
        {
            DateTime value;
            if (!DateTime.TryParse(this.DefaultValue, out value))
            {
                return default(DateTime);
            }

            return value;
        }
    }
}

DefaultDateTimeExtensions.cs

using System;

namespace Custom.Extensions
{
    /// <summary>
    /// Inspired from https://code.msdn.microsoft.com/A-flexible-Default-Value-11c2db19. See usage for more information.
    /// </summary>
    public static class DefaultDateTimeExtensions
    {
        public static DateTime FirstOfYear(this DateTime dateTime)
            => new DateTime(dateTime.Year, 1, 1, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

        public static DateTime LastOfYear(this DateTime dateTime)
            => new DateTime(dateTime.Year, 12, 31, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

        public static DateTime FirstOfMonth(this DateTime dateTime)
            => new DateTime(dateTime.Year, dateTime.Month, 1, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);

        public static DateTime LastOfMonth(this DateTime dateTime)
            => new DateTime(dateTime.Year, dateTime.Month, DateTime.DaysInMonth(dateTime.Year, dateTime.Month), dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
    }
}

Et utilisez DefaultDateTimeValue comme attribut de vos propriétés. Les valeurs à saisir dans votre attribut de validation sont des éléments tels que "Now", qui sera rendu au moment de l'exécution à partir d'une instance DateTime créée avec un activateur. Le code source est inspiré de ce fil: https://code.msdn.microsoft.com/A-flexible-Default-Value-11c2db19 . Je l'ai changé pour que ma classe hérite avec DefaultValueAttribute au lieu d'un ValidationAttribute.


2

J'ai rencontré le même problème, mais celui qui fonctionne le mieux pour moi est ci-dessous:

public DateTime CreatedOn { get; set; } = DateTime.Now;

1

Je viens de trouver ceci à la recherche de quelque chose de différent, mais dans la nouvelle version C #, vous pouvez utiliser une version encore plus courte pour cela:

public DateTime DateCreated { get; set; } = DateTime.Now;

0
public DateTime DateCreated
{
   get
   {
      return (this.dateCreated == default(DateTime))
         ? this.dateCreated = DateTime.Now
         : this.dateCreated;
   }

   set { this.dateCreated = value; }
}
private DateTime dateCreated = default(DateTime);

0

La façon dont vous gérez cela pour le moment dépend du modèle que vous utilisez Linq to SQL ou EntityFramework?

Dans L2S, vous pouvez ajouter

public partial class NWDataContext
{
    partial void InsertCategory(Category instance)
    {
        if(Instance.Date == null)
            Instance.Data = DateTime.Now;

        ExecuteDynamicInsert(instance);
    }
}

EF est un peu plus compliqué, voir http://msdn.microsoft.com/en-us/library/cc716714.aspx pour plus d'informations sur la logique d'entreprise EF.


0

Je sais que cet article est un peu vieux, mais j'ai une suggestion qui pourrait aider certains.

J'ai utilisé un Enum pour déterminer ce qu'il faut définir dans le constructeur d'attributs.

Déclaration de propriété:

[DbProperty(initialValue: EInitialValue.DateTime_Now)]
public DateTime CreationDate { get; set; }

Constructeur de propriété:

Public Class DbProperty Inherits System.Attribute

    Public Property InitialValue As Object

    Public Sub New(ByVal initialValue As EInitialValue)
       Select Case initialValue
          Case EInitialValue.DateTime_Now
             Me.InitialValue = System.DateTime.Now

          Case EInitialValue.DateTime_Min
             Me.InitialValue = System.DateTime.MinValue

          Case EInitialValue.DateTime_Max
             Me.InitialValue = System.DateTime.MaxValue

       End Select

    End Sub
End Class

Énumération:

Public Enum EInitialValue
   DateTime_Now
   DateTime_Min
   DateTime_Max
End Enum

0

Je pense que vous pouvez le faire en utilisant StoreGeneratedPattern = Identity(défini dans la fenêtre des propriétés du concepteur de modèle).

Je n'aurais pas deviné que ce serait comment le faire, mais en essayant de le comprendre, j'ai remarqué que certaines de mes colonnes de dates étaient déjà par défaut CURRENT_TIMESTAMP()et d'autres pas. En vérifiant le modèle, je vois que la seule différence entre les deux colonnes en plus du nom est que celle qui obtient la valeur par défaut est StoreGeneratedPatterndéfinie sur Identity.

Je ne m'attendais pas à ce que ce soit le cas, mais en lisant la description, cela a du sens:

Détermine si la colonne correspondante dans la base de données sera générée automatiquement lors des opérations d'insertion et de mise à jour.

De plus, bien que cela donne à la colonne de base de données une valeur par défaut "maintenant", je suppose que cela ne définit pas réellement la propriété comme étant DateTime.Nowdans le POCO. Cela n'a pas été un problème pour moi car j'ai un fichier .tt personnalisé qui définit déjà toutes mes colonnes de date DateTime.Nowautomatiquement (il n'est en fait pas difficile de modifier le fichier .tt vous-même, surtout si vous avez ReSharper et obtenez une coloration syntaxique (Les versions plus récentes de VS peuvent déjà mettre en évidence les fichiers .tt, pas sûr.))

Le problème pour moi était le suivant: comment faire pour que la colonne de base de données ait une valeur par défaut afin que les requêtes existantes qui omettent cette colonne fonctionnent toujours? Et le paramètre ci-dessus a fonctionné pour cela.

Je ne l'ai pas encore testé, mais il est également possible que la définition de cela interfère avec la définition de votre propre valeur explicite. (Je suis seulement tombé sur cela en premier lieu parce que EF6 Database First a écrit le modèle pour moi de cette façon.)


0

Dans la version C # 6, il est possible de fournir une valeur par défaut

public DateTime fieldname { get; set; } = DateTime.Now;

1
Bienvenue dans StackOverflow: si vous publiez des échantillons de code, XML ou de données, veuillez mettre en évidence ces lignes dans l'éditeur de texte et cliquer sur le bouton "exemples de code" ({}) dans la barre d'outils de l'éditeur ou en utilisant Ctrl + K sur votre clavier pour bien formater et la syntaxe le met en évidence!
WhatsThePoint

1
La même chose que la réponse de Brandtware stackoverflow.com/a/47528230/52277
Michael Freidgeim

-1

Avec EF 7:

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
Column(TypeName = "datetime2")]
DateTime? Dateadded { get; set; }

script de migration:

AlterColumn("myschema.mytable", "Dateadded", c => c.DateTime(nullable: false, precision: 7, storeType: "datetime2", defaultValueSql: "getutcdate()"));

résultat:

ALTER TABLE [MySchema].[MyTable] ADD  CONSTRAINT [DF_MySchema.MyTable_Dateadded]  DEFAULT (getutcdate()) FOR [Dateadded]

EF 7 n'existe pas, une colonne DateTime ne peut pas être une identité et l'annotation ne crée pas ce script de migration. Et il n'est pas nécessaire de modifier manuellement le script de migration, si c'est ce que vous voulez dire. Il y a des annotations qui ajoutent la valeur par défaut, comme cela a déjà été amplement répondu ici.
Gert Arnold

-5

Je voulais aussi cela et j'ai trouvé cette solution (j'utilise uniquement la partie date - une heure par défaut n'a aucun sens comme valeur par défaut de PropertyGrid):

public class DefaultDateAttribute : DefaultValueAttribute {
  public DefaultDateAttribute(short yearoffset)
    : base(DateTime.Now.AddYears(yearoffset).Date) {
  }
}

Cela crée simplement un nouvel attribut que vous pouvez ajouter à votre propriété DateTime. Par exemple, si la valeur par défaut est DateTime.Now.Date:

[DefaultDate(0)]

32
AVERTISSEMENT!!!!!! ceci est très mauvais et devrait être supprimé de SO. Je viens de passer des heures à déboguer les problèmes causés par cette suggestion. À première vue, cela a l'air bien et semble fonctionner. Mais c'est un piège. Les attributs nécessitent des valeurs statiques pour une raison. Ils sont initialisés une fois. Après cela, la valeur change. Ainsi, alors que la première instance que vous créez aura l'air correcte et que les instances suivantes auront l'air bien, elles utilisent toutes la première valeur. Une fois que votre application a fonctionné pendant un certain temps, vous remarquerez le problème et vous vous demanderez pourquoi. Et en débogage, lorsque vous l'exécutez pendant une minute, il fonctionnera bien. IL FAUT SE MÉFIER!
Chris Hynes

3
Chris a raison. J'ai trouvé le même problème. N'utilisez pas cette réponse .
sohtimsso1970
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.