使用Attribute解决在Entity Framework中无法使用默认时间的问题

  相信很多使用过Entity Framework的人都遇到过这个DateTime类型的Entity字段无法使用默认值的问题。

  比方说在声明一个Entity的时候。

 1     [Table("ctUser")]
 2     public class ctUser
 3     {
 4         [Key]
 5         public int UserID { get; set; }
 6 
 7         [Required(ErrorMessage = "电子邮件不能为空!")]
 8         [MaxLength(200, ErrorMessage = "电子邮件的字符长度不能超过200!")]
 9         public string Email { get; set; }
10 
11         [Required(ErrorMessage = "密码不能为空!")]
12         [MaxLength(32, ErrorMessage = "密码的字符长度不能超过32!")]
13         public string Password { get; set; }
14 
15         [Required(ErrorMessage = "真实姓名不能为空!")]
16         [MaxLength(20, ErrorMessage = "真实姓名的字符长度不能超过20!")]
17         public string RealName { get; set; }
18     }

  为了能够调用对数据库的具体操作我创建了一个简单的继承“DbContext”的数据库操作类型。

1     public class ctDbContext : DbContext
2     {
3         public DbSet<ctUser> Users { get; set; }
4 
5         public ctDbContext()
6             : base("DefaultConn")
7         {
8         }
9     }

  ctDbContext的实现相当简单,一个构造函数为基类传递了web.config文件中链接字符串的名称。一个DbSet<ctUser>类型的public属性。使用这个类型的时候也非常简单,比方说,当我想要添加一个用户到数据库中时

 1     using (ctDbContext ctx = new ctDbContext())
 2     {
 3         ctUser user = new ctUser()
 4         {
 5             Email = "test@163.com",
 6             Password = "123456",
 7             RealName = "Kevin.Cai"
 8         };
 9 
10         ctx.Users.Add(user);
11         ctx.SaveChanges();
12     }

  这么几行代码就实现了向数据库中添加一个用户的功能。如果你还没有创建数据库文件,它甚至能直接帮你把数据库文件都生成好。这种东西相当适合我这种懒人。

  在很多情况下,我们在设计数据表的时候都会习惯的创建一个“CreateDate”或者“RegisterDate”或者其它任何名字,反正是用来记录当前时间的一个字段。这个在数据库中直接使用的时候是相当简单的,只需要在“Default Value”中使用“GETDATE()”函数即可,我们已经习惯了在创建一条数据的时候不去手动获取时间。但是,对于EF来说,这却是一个问题,因为在我向Entity中添加一个DateTime类型的字段时,如果不设置它,它将使用该类型的默认值。而且在EF中根本没有DefaultValueAttribute这种东西。好吧,既然它没有,我们自己来创建一个。

  实际上DefaultValueAttribute的原理还是蛮简单。首先给需要使用默认值的字段加上DefaultValueAttribute,然后创建一条新数据的时候将有DefaultValueAttribute标记的字段值修改一下就可以了。

 1     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
 2     public class DefaultValueAttribute : Attribute
 3     {
 4         public Type Type { get; set; }
 5         public string Property { get; set; }
 6 
 7         public DefaultValueAttribute(Type type, string property)
 8         {
 9             this.Type = type;
10             this.Property = property;
11         }
12     }

  在DefaultValueAttribute中包含了两个public字段,这两个字段是为了动态获取值准备的。因为Attribute的编译时特性,我们无法传递动态的对象,我实在不知道还有没有其他的方法传递DateTime.Now这种运行时产生的值,不过这里我暂且使用反射的方法。因为这里没有传递对象,所以请确保Property是静态的。而DateTime的Now属性恰好就是个静态的。

  在一般情况下,默认值的适用范围是在向数据库中“添加一条数据时,并且“未设置”这个值。而我们修改具体值的时间肯定在数据提交之前,数据提交之后当然也可以修改,不过稍微有点性能概念的人我想也是不会在数据提交之后再修改的,而且在数据提交之前修改也并不复杂。按照这些原则我对ctDbContext类型做了一些修改。

  

 1     public class ctDbContext : DbContext
 2     {
 3         public DbSet<ctUser> Users { get; set; }
 4 
 5         public ctDbContext()
 6             : base("DefaultConn")
 7         {
 8         }
 9 
10         public override int SaveChanges()
11         {
12             IEnumerable<DbEntityEntry> entityList = this.ChangeTracker.Entries();
13             foreach (DbEntityEntry entity in entityList)
14             {
15                 if (entity.State != System.Data.EntityState.Added)
16                     continue;
17 
18                 DefaultValueAttribute.UpdateValue(entity.Entity);
19             }
20 
21             return base.SaveChanges();
22         }
23     }

  新的ctDbContext重写了SaveChanges方法。对所有添加数据的操作做了一次DefaultValueAttribute的UpdateValue操作。UpdateValue做为静态方法需要被添加到DefaultValueAttribute类型中。

 1     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
 2     public class DefaultValueAttribute : Attribute
 3     {
 4         public Type Type { get; set; }
 5         public string Property { get; set; }
 6 
 7         public DefaultValueAttribute(Type type, string property)
 8         {
 9             this.Type = type;
10             this.Property = property;
11         }
12 
13         public static void UpdateValue(object obj)
14         {
15             Type type = obj.GetType();
16             PropertyInfo[] pInfoList = type.GetProperties();
17             if (pInfoList == null || pInfoList.Length == 0)
18                 return;
19 
20             foreach (PropertyInfo pInfo in pInfoList)
21             {
22                 DefaultValueAttribute attr = GetAttribute(pInfo);
23                 if (attr == null)
24                     continue;
25 
26                 object original = pInfo.GetValue(obj, null);
27                 if (!IsDefaultValue(attr.Type, original))
28                     continue;
29 
30                 object value = attr.GetPropertyValue();
31                 if (value == null)
32                     continue;
33 
34                 pInfo.SetValue(obj, value, null);
35             }
36         }
37 
38         private static DefaultValueAttribute GetAttribute(PropertyInfo pInfo)
39         {
40             object[] attrs = pInfo.GetCustomAttributes(typeof(DefaultValueAttribute), true);
41             if (attrs == null || attrs.Length != 1)
42                 return null;
43 
44             return attrs[0] as DefaultValueAttribute;
45         }
46 
47         private static bool IsDefaultValue(Type type, object value)
48         {
49             if (type.IsValueType)
50                 return Activator.CreateInstance(type).Equals(value);
51             else
52                 return value == null;
53         }
54 
55         private object GetPropertyValue()
56         {
57             if (this.Type == null || string.IsNullOrEmpty(this.Property))
58                 return null;
59 
60             PropertyInfo pInfo = this.Type.GetProperty(this.Property);
61             return pInfo.GetValue(null, null);
62         }
63     }

  新的DefaultValueAttribute复杂了一些。其中IsDefaultValue通过判断值是否是默认值来判断该值是否被修改过。我必须要承认这几条函数的性能不怎么样,可能会有装箱拆箱操作。实际上,使用了反射性能都不怎么样。

  当这些都写好了之后我们终于可以使用DefaultValueAttribute了,新的Entity被添加了CreateDate字段并且使用了DefaultValueAttribute特性。

 1     [Table("ctUser")]
 2     public class ctUser
 3     {
 4         [Key]
 5         public int UserID { get; set; }
 6 
 7         [Required(ErrorMessage = "电子邮件不能为空!")]
 8         [MaxLength(200, ErrorMessage = "电子邮件的字符长度不能超过200!")]
 9         public string Email { get; set; }
10 
11         [Required(ErrorMessage = "密码不能为空!")]
12         [MaxLength(32, ErrorMessage = "密码的字符长度不能超过32!")]
13         public string Password { get; set; }
14 
15         [Required(ErrorMessage = "真实姓名不能为空!")]
16         [MaxLength(20, ErrorMessage = "真实姓名的字符长度不能超过20!")]
17         public string RealName { get; set; }
18 
19         [DefaultValue(typeof(DateTime), "Now")]
20         public DateTime CreateDate { get; set; }
21     }

  就这样,如果你像我一样懒得每次去设置当前时间值的话,Entity Framework终于可以实现了,恼人的overflow异常(数据库和DateTime的取值范围不同)将不再出现。

  切记,这是Demo,不是项目中的代码。

posted @ 2012-08-07 16:56  Kevin.Cai  阅读(868)  评论(0编辑  收藏  举报