wrz
4
2014

EF - wywoływanie swojej logiki przy konkretnych getterach/setterach na entity

Dzisiaj natrafiłem na następujący problem: potrzebuję wyświetlać na ekranie daty dla użytkowników przekonwertowane na ich strefy czasowe. Każdy użytkownik ma zapisaną w bazie strefę czasową i ustawiam ją przy logowaniu (dla wygody trzymam w User.Identity). Daty w bazie danych są trzymane w formacie UTC. 

Teraz, chcę aby dla każdego użytkownika daty wyświetlały mu się w jego strefie czasowej. Nie chcę tego robić na poziomie kontrolerów (aplikacja jest w MVC) ponieważ logika tam jest już zakodowana i zmiana jej wymagałaby dużo czasu. Chcę, aby konwersję przeprowadziło za mnie automatycznie EF. Z tym że nie wszystkie property DateTime na encjach powinny być w ten sposób konwertowane - jedynie te które chcę.

Po przeanalizowaniu wyników google znalazłem pewien pattern którym chcę się podzielić. W skrócie polega on na utworzeniu customowego atrybutu którym dekorujemy interesujące nas property na encji. Następnie, overridujemy zdarzenie OnMaterializing wywoływane przy utworzeniu obiektu encji i tam przez reflekcję odnajdujemy interesujące nas property i wykonujemy akcję. 

Czy zaczynamy od stworzenia atrybutu:

[AttributeUsage(AttributeTargets.Property)]
    public class ConvertDateTimeFromUTCToLocalAttribute : Attribute
    {}
 

Następnie przechodzimy do naszego DataContextu i w konstruktorze umieszczamy kod:

public DatabaseContext( string connectionStringName )
            : base( connectionStringName )
        {
            ( (IObjectContextAdapter)this ).ObjectContext.ObjectMaterialized += ( s, e ) =>
            {
                var entity = e.Entity;
                if( entity == null )
                    return;

                foreach( var prop in entity.GetType().GetProperties().Where( p => p.GetCustomAttributes( typeof( ConvertDateTimeFromUTCToLocalAttribute ), true ).Count() == 1 ) )
                {
                    var value = (DateTime?)prop.GetValue( entity );
                    if( value != null )
                    {
                        prop.SetValue( entity, value.Value.UTCToLocalTime() );
                    }
                }
            };
        }

Teraz wystarczy na encji w której property chcemy "obsłużyć" dodać nasz atrybut:

[ConvertDateTimeFromUTCToLocal]
        public DateTime? StartDate { get; set; }