16

我首先使用 EF 4 数据库 + POCO。因为 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;
        }
    }

我还将自动生成的属性的所有 setter 和 getter 设置为 Private,但我需要在 Linq-to-Entities 查询中使用的那些属性除外(叹气)。在这些情况下,我将这些吸气剂设置为内部。

我当然希望在 DateTime 类型上有一个下拉菜单,以指定 EF 应将其视为什么“种类”的 DateTime。这将节省时间和额外的复杂性。

4

6 回答 6

24

另一种方法是挂钩 DbContext 中的 ObjectMaterialized 事件并在那里设置类型。

在我的 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);
            }
        }

缺点是您需要确保为您关心的每个属性设置它,但至少它被设置在可能的最低级别。

于 2012-02-21T22:32:05.760 回答
15

我使用了与 Michael 相同的方法,然后我潜得更深一些,并使用反射来搜索 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);
}

然后我转到我的 WebsiteReadModelContext 的构造函数,它是一个 DbContext 对象并调用 ReadAllDateTimeValuesAsUtc 方法

public WebsiteReadModelContext()
{
      this.ReadAllDateTimeValuesAsUtc();
}
于 2012-07-27T07:05:31.150 回答
6

我将使用 edmx 并为 CreatedOn 属性指定另一个名称(例如 CreatedOnInternal)。然后将生成的访问修饰符设置为 Internal 而不是 Public。然后您可以在部分类中实现您的自定义属性,而不必担心这一点。

于 2011-08-03T18:04:49.947 回答
4

我认为如果您尝试手动修改 EF 生成的类,事情就会变得一团糟。

我建议追求两种选择:

  1. 不要修改现有属性,而是将新属性添加到您的部分类CreatedOnUTC或类似的东西中。
  2. 修改 T4 模板以更改它为这些日期属性生成访问器的方式(如果每个 DateTime 属性都以相同的方式工作,则更容易)。这不是微不足道的,因为它依赖于类型,但至少可以让您在未来使用生成器。
于 2011-08-03T18:05:31.650 回答
4

当涉及到它生成的东西时,EF 有时会非常讨厌。
在 Database First 应用程序中,当我遇到类似问题时,我通常采用以下方法:

  • 保留自动生成的属性,但制作它们private并更改它们的名称;
  • 添加对业务代码有意义的公共“包装”属性。

例如,我将重命名CreatedOn为其他名称,例如CreatedOnInternal(credits to Jeff ) 并在设计器中将其标记为私有。在部分类中,我将添加一个来回转换的公共包装器属性。 CreatedOn

于 2011-08-03T18:11:24.727 回答
0

我知道这是一个老问题,但这是一个不需要反思或为每个新属性编辑 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 相同的代码,但无需手动编辑每个新属性!

于 2017-06-13T15:45:13.767 回答