Подтвердить что ты не робот

Можно ли запретить EntityFramework 4 переписывать настроенные свойства?

Я использую сначала базу данных EF 4 + POCOs. Поскольку EF не имеет простого способа заявить, что входящие DateTimes имеют вид UTC, я переместил свойство из автоматически сгенерированного файла в частичный класс в другой файл.

    private DateTime _createdOn;
    public virtual System.DateTime CreatedOn
    {
        get { return _createdOn; }
        set
        {
            _createdOn =
                (value.Kind == DateTimeKind.Unspecified)
                    ? _createdOn = DateTime.SpecifyKind(value, DateTimeKind.Utc)
                    : value;
        }
    }

Однако теперь, каждый раз, когда я обновляю модель, автоматические свойства снова создаются в гене T4. Конечно, это приводит к следующей ошибке компиляции: "Тип" Foo "уже содержит определение для" CreatedOn ".

Есть ли способ сказать EF не генерировать это свойство и позволить мне обрабатывать его самостоятельно?

Обновление

Спасибо за все ответы...

Я создал новое настраиваемое свойство с другим именем.

    public virtual System.DateTime CreatedOnUtc
    {
        get
        {
            return (CreatedOn.Kind==DateTimeKind.Unspecified)
                ? DateTime.SpecifyKind(CreatedOn, DateTimeKind.Utc)
                : CreatedOn;
        }
        set
        {
            CreatedOn =
                (value.Kind == DateTimeKind.Unspecified)
                    ? CreatedOn = DateTime.SpecifyKind(value, DateTimeKind.Utc)
                    : value;
        }
    }

Я также установил все сеттеры и получатели автоматически сгенерированного свойства в Private, за исключением тех свойств, которые мне нужно было использовать в запросе Linq-to-Entities (sigh). В этих случаях я устанавливал эти геттеры на внутренние.

Я уверен, что было показано выпадающее меню в типах DateTime, чтобы указать, какой "вид" DateTime должен рассматривать EF как EF. Это спасло бы часы и дополнительное усложнение.

4b9b3361

Ответ 1

Я думаю, что все начинает запутываться, если вы пытаетесь вручную модифицировать классы, сгенерированные EF.

Есть два варианта, которые я бы предложил:

  • Не изменяйте существующее свойство, но добавляйте новый в свой неполный класс, CreatedOnUTC или что-то подобное.
  • Измените шаблон T4, чтобы изменить способ создания аксессуаров для этих свойств даты (проще, если каждое свойство DateTime должно работать одинаково). Это не будет тривиально, поскольку оно зависит от типа, но, по крайней мере, позволит вам использовать генератор в будущем.

Ответ 2

Другой подход заключается в том, чтобы подключиться к событию ObjectMaterialized в DbContext и установить там вид.

В моем конструкторе DbContext я делаю это:

    ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += new ObjectMaterializedEventHandler(ObjectMaterialized);

а затем метод выглядит следующим образом:

private void ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
        {
            Person person = e.Entity as Person;
            if (person != null) // the entity retrieved was a Person
            {
                if (person.BirthDate.HasValue)
                {
                    person.BirthDate = DateTime.SpecifyKind(person.BirthDate.Value, DateTimeKind.Utc);
                }
                person.LastUpdatedDate = DateTime.SpecifyKind(person.LastUpdatedDate, DateTimeKind.Utc);
                person.EnteredDate = DateTime.SpecifyKind(person.EnteredDate, DateTimeKind.Utc);
            }
        }

Недостатком является то, что вам нужно убедиться, что вы установите его для каждого свойства, которое вас волнует, но по крайней мере оно устанавливается на минимально возможном уровне.

Ответ 3

Я использовал тот же подход, что и Майкл, тогда я немного погрузился в глубь и использовал отражение для поиска DateTime и DateTime?

Мое решение гарантировать, что все значения DateTime будут считаны как Utc DateTimes:

Сначала я написал три метода, которые находятся в моем классе методов DbContext Extensions. Потому что мне нужно использовать его для нескольких DbContexts

public static void ReadAllDateTimeValuesAsUtc(this DbContext context)
{
        ((IObjectContextAdapter)context).ObjectContext.ObjectMaterialized += ReadAllDateTimeValuesAsUtc;
}

private static void ReadAllDateTimeValuesAsUtc(object sender, ObjectMaterializedEventArgs e)
{
    //Extract all DateTime properties of the object type
    var properties = e.Entity.GetType().GetProperties()
        .Where(property => property.PropertyType == typeof (DateTime) ||
                           property.PropertyType == typeof (DateTime?)).ToList();
    //Set all DaetTimeKinds to Utc
    properties.ForEach(property => SpecifyUtcKind(property, e.Entity));
}

private static void SpecifyUtcKind(PropertyInfo property, object value)
{
    //Get the datetime value
    var datetime = property.GetValue(value, null);

    //set DateTimeKind to Utc
    if (property.PropertyType == typeof(DateTime))
    {
        datetime = DateTime.SpecifyKind((DateTime) datetime, DateTimeKind.Utc);
    }
    else if(property.PropertyType == typeof(DateTime?))
    {
        var nullable = (DateTime?) datetime;
        if(!nullable.HasValue) return;
        datetime = (DateTime?)DateTime.SpecifyKind(nullable.Value, DateTimeKind.Utc);
    }
    else
    {
        return;
    }

    //And set the Utc DateTime value
    property.SetValue(value, datetime, null);
}

И затем я перехожу к конструктору моего SiteReadModelContext, который является объектом DbContext и вызывает метод ReadAllDateTimeValuesAsUtc

public WebsiteReadModelContext()
{
      this.ReadAllDateTimeValuesAsUtc();
}

Ответ 4

Я бы использовал edmx и задал другое имя для свойства CreatedOn (например, CreatedOnInternal). Затем установите для модификатора доступа для генерации значение Internal вместо Public. Затем вы можете реализовать свое настраиваемое свойство в частичном классе и не беспокоиться об этом.

Ответ 5

EF может быть довольно неприятным, когда дело доходит до того, что он генерирует.
В приложениях Database First, когда я сталкиваюсь с аналогичной проблемой, я обычно следую этому подходу:

  • Оставьте автогенерируемые свойства, но сделайте их private и измените их имена;
  • Добавить общедоступные свойства "обертывания", которые имеют смысл для бизнес-кода.

Например, я бы переименовал CreatedOn в нечто другое, например CreatedOnInternal (запустил Jeff) и отметьте его закрытым в дизайнере. В частичном классе я бы добавил свойство public CreatedOn оболочки, которое выполняет преобразование взад и вперед.

Ответ 6

Я знаю, что это старый вопрос, но вот решение, которое не требует рефлексии или редактирования DbContext для каждого нового свойства.

Он состоит из редактирования Context.tt:

Сначала добавьте следующее, используя поверх tt файла (около строки 40):

using System.Data.Entity.Core.Objects;

Затем прямо под конструктором (вокруг строки 86 в моем случае) добавьте следующий код генерации:

<#
var entitiesWithDateTime = typeMapper.GetItemsToGenerate<EntityType>(itemCollection)
                                .Where(e => e.Properties.Any(p => typeMapper.GetTypeName(p.TypeUsage) == "System.DateTime"));

if(entitiesWithDateTime.Count() > 0)
{
#>
    private void ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
    {
<#
    var count = 0;
    foreach (var entityType in entitiesWithDateTime)
    {
#>
        <#=count != 0 ? "else " : String.Empty#>if(e.Entity is <#=entityType.Name#>)
        {
            var entity = e.Entity as <#=entityType.Name#>;
<#
            foreach(var property in entityType.Properties.Where(p => typeMapper.GetTypeName(p.TypeUsage) == "System.DateTime"))
            {
#>
            entity.<#=property.Name#> = DateTime.SpecifyKind(entity.<#=property.Name#>, DateTimeKind.Utc);
<#
            }
#>
        }
<#
        count++;
    }
#>
    }
<#
}
#>

Это будет выполняться во время компиляции во всех объектах DbContext и вызывать DateTime.SpecifyKind для каждого параметра DateTimeProperties.

Этот код будет генерировать тот же код, что и michael.aird, но не требует ручного редактирования для каждого нового свойства!