好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

动态属性的一个架构

动态属性的一个架构

最近做的一个项目的架构的一部分吧,这是一个项目管理系统,支持动态属性,也就是说一个资料
–  例如“项目”、“任务”就是资料,资料的属性
–  例如“名称”、“时间”都是可以在系统运行时动态增删改的。

本文就讲一讲在 .NET 和 SQL Server 里实现动态属性的方法,虽然演示代码都是 C# ,但我相信可以很容易的移植到 Java 中。

首先定义几个名词:

资料  –  是对于系统最终用户来说其要维护的数据,例如“项目”、“任务”信息等。

属性  –  即资料的一个方面的数据,或者称作字段,在 C# 代码里应该就是一个 Property 。

元数据  –  是解释属性的方式,有时我也会把它称作元属性。

属性和元数据的关系呢,可以参照 Excel 的实现来理解,好比说我们在一个单元格里输入了一个数据,实际上我们是输入了一个字符串,假设是“ 1 ”,当我们设置 Excel 使用“数字”格式呈现时,那用户在单元格里实际看到的是“ 1.0 ”,当我们设置 Excel 使用“日期”格式呈现时,那用户在单元格里看到的可能就是“ 1900-1-1 ”。这里,字符串“ 1 ”就是属性,而元数据实际上就类似 Excel 里的格式。

对于资料来说,它只保存一个属性列表,而属性有一个外键指向定义其格式的元数据,下面是资料、属性和元数据的 C# 定义:

资料

  1    public   class  GenericDynamicPropertiesEntity : IDynamicPropertiesTable, ISupportDefaultProperties
  2      {
  3           public  GenericDynamicPropertiesEntity()
  4          {
  5              Properties =  new  List<Property>();
  6               this .FillDefaultProperties();
  7          }
  8  
  9           public   string  Get( string  name)
 10          {
 11               var  property =  this .Property(name,  false );
 12               if  (property !=  null )
 13              {
 14                   return  property.Value;
 15              }
 16               else
 17              {
 18                   return   null ;
 19              }
 20          }
 21  
 22           public  Property Get(MetaProperty meta)
 23          {
 24               var  property =  this .Property(meta.Title,  false );
 25               if  (property !=  null )
 26              {
 27                   return   this .Property(meta.Title,  false );
 28              }
 29               else
 30              {
 31                   return   null ;
 32              }
 33          }
 34           public   void  Set( string  name,  string  value)
 35          {
 36               var  property =  this .Property(name,  true );
 37               if  (property.Meta.Valid(value))
 38                  property.Value = value;
 39               else
 40                   throw   new  InvalidValueException( string .Format( " 字段\"{0}\"的值\"{1}\"无效,字段\"{0}\"的类型是\"{2}\", 期望值的格式是\"{3}\" " ,
 41                      name, value, property.Meta.Type, property.Meta.ExpectedFormat));
 42          }
 43  
 44           public   void  Set( string  name,  double  value)
 45          {
 46               var  property =  this .Property(name,  true );
 47               if  (property.Meta.Valid(value))
 48                  property.Value = value.ToString();
 49               else
 50                   throw   new  InvalidValueException( string .Format( " 字段\"{0}\"的值\"{1}\"无效,字段\"{0}\"的类型是\"{2}\", 期望值的格式是\"{3}\" " ,
 51                      name, value, property.Meta.Type, property.Meta.ExpectedFormat));
 52          }
 53  
 54           public  List<Property> Properties {  get ;  private   set ; }
 55  
 56          [DataMember]
 57           public  Guid Id {  get ;  set ; }
 58  
 59           public   static  T New<T>()  where  T : GenericDynamicPropertiesEntity,  new ()
 60          {
 61               return   new  T()
 62              {
 63                  Id = Guid.NewGuid()
 64              };
 65          }
 66  
 67           protected   void  SetClassValue<T>( string  propertyName, T member, T value)
 68          {
 69              member = value;
 70              Set(propertyName, value !=  null  ? value.ToJson() :  null );
 71          }
 72  
 73           protected   void  SetNullableDateTime<T>( string  propertyName, T? member, T? value)  where  T :  struct
 74          {
 75              member = value;
 76              Set(propertyName, value.HasValue ? value.Value.ToString() :  null );
 77          }
 78  
 79           protected   void  SetDateTime( string  propertyName, DateTime member, DateTime value)
 80          {
 81              member = value;
 82              Set(propertyName, value.ToString());
 83          }
 84  
 85           protected   void  SetSingle( string  propertyName,  float  member,  float  value)
 86          {
 87              member = value;
 88              Set(propertyName, value);
 89          }
 90  
 91           protected   void  SetPrimeValue<T>( string  propertyName, T member, T value)  where  T :  struct
 92          {
 93              member = value;
 94              Set(propertyName, value.ToString());
 95          }
 96  
 97           protected  DateTime? GetNullableDateTime( string  propertyName, DateTime? date)
 98          {
 99               if  (!date.HasValue)
100              {
101                   var  value = Get(propertyName);
102                   if  (value !=  null )
103                  {
104                      date = DateTime.Parse(value);
105                  }
106              }
107  
108               return  date;
109          }
110  
111           protected   float  GetSingle( string  propertyName,  float  member)
112          {
113               if  ( float .IsNaN(member))
114              {
115                   var  property =  this .Property(propertyName,  false );
116                   if  (property !=  null )
117                  {
118                      member = Single.Parse(property.Value);
119                  }
120              }
121  
122               return  member;
123          }
124  
125           protected  DateTime GetDateTime( string  propertyName, DateTime member)
126          {
127               if  (member == DateTime.MinValue)
128              {
129                   var  value = Get(propertyName);
130                   if  (value !=  null )
131                  {
132                      member = DateTime.Parse(value);
133                       return  member;
134                  }
135                   else
136                  {
137                       throw   new  PropertyNotFoundException( string .Format( " 在Id为\"{0}\"的对象里找不到名为\"{1}\"的属性! " , Id, propertyName));
138                  }
139              }
140               else
141              {
142                   return  member;
143              }
144          }
145  
146           public  DateTime? ClosedDate
147          {
148               get ;
149               set ;
150          }
151  
152           public  DateTime OpenDate
153          {
154               get ;
155               set ;
156          }
157  
158           public  DateTime LastModified
159          {
160               get ;
161               set ;
162          }
163  
164           public   string  Creator
165          {
166               get ;
167               set ;
168          }
169  
170           public   string  LastModifiedBy
171          {
172               get ;
173               set ;
174          }
175      }

属性

 1       ///   <summary>
 2        ///  资料的属性
 3        ///   </summary>
 4       public   class  Property : ITable
 5      {
 6           ///   <summary>
 7            ///  获取和设置资料的值
 8            ///   </summary>
 9            ///   <remarks>
10            ///  对于普通类型,例如float等类型直接就保存其ToString的返回结果
11            ///  对于复杂类型,则保存其json格式的对象
12            ///   </remarks>
13           //  TODO: 第二版 - 需要考虑国际化情形下,属性有多个值的情形!
14           public   string  Value {  get ;  set ; }
15          
16           ///   <summary>
17            ///  获取和设置属性的Id
18            ///   </summary>
19           public  Guid Id {  get ;  set ; }
20  
21           public  MetaProperty Meta {  get ;  set ; }
22  
23           ///   <summary>
24            ///  获取和设置该属性对应的元数据Id
25            ///   </summary>
26           public  Guid MetaId {  get ;  set ; }
27  
28           ///   <summary>
29            ///  该属性对应的资料的编号
30            ///   </summary>
31           public  Guid EntityId {  get ;  set ; }
32  
33           ///   <summary>
34            ///  获取和设置该属性所属的资料
35            ///   </summary>
36           public  GenericDynamicPropertiesEntity Entity {  get ;  set ; }
37  }


元数据

  1   public   class  MetaProperty : INamedTable, ISecret
  2      {
  3           public  Guid Id {  get ;  set ; }
  4  
  5           public   string  BelongsToMaterial {  get ;  set ; }
  6  
  7           public  String Title {  get ;  set ; }
  8  
  9           public   string  Type {  get ;  set ; }
 10  
 11           public   string  DefaultValue {  get ;  private   set ; }
 12  
 13           ///   <summary>
 14            ///  获取和设置属性的权限
 15            ///   </summary>
 16           public   int  Permission {  get ;  set ; }
 17  
 18           public   virtual   string  ExpectedFormat {  get  {  return   string .Empty; } }
 19  
 20           public   virtual   bool  Valid( string  value)
 21          {
 22               return   true ;
 23          }
 24  
 25           public   virtual   bool  Valid( double  value)
 26          {
 27               return   true ;
 28          }
 29  
 30           public   static  MetaProperty NewString( string  name)
 31          {
 32               return   new  MetaProperty()
 33              {
 34                  Id = Guid.NewGuid(),
 35                  Title = name,
 36                  Type = Default.MetaProperty.Type.String,
 37                  Permission = Default.Permission.Mask
 38              };
 39          }
 40  
 41           public   static  MetaProperty NewNumber( string  name,  double  defaultValue =  0.0 )
 42          {
 43               return   new  MetaProperty()
 44              {
 45                  Id = Guid.NewGuid(),
 46                  Title = name,
 47                  Type = Default.MetaProperty.Type.Number,
 48                  Permission = Default.Permission.Mask,
 49                  DefaultValue = defaultValue.ToString()
 50              };
 51          }
 52  
 53           public   static  MetaProperty NewAddress( string  name)
 54          {
 55               return   new  MetaProperty()
 56              {
 57                  Id = Guid.NewGuid(),
 58                  Title = name,
 59                  Type = Default.MetaProperty.Type.Address,
 60                  Permission = Default.Permission.Mask
 61              };
 62          }
 63  
 64           public   static  MetaProperty NewRelationship( string  name)
 65          {
 66               return   new  MetaProperty()
 67              {
 68                  Id = Guid.NewGuid(),
 69                  Title = name,
 70                  Type = Default.MetaProperty.Type.Relationship,
 71                  Permission = Default.Permission.Mask
 72              };
 73          }
 74  
 75           public   static  MetaProperty NewDateTime( string  name)
 76          {
 77               return   new  MetaProperty()
 78              {
 79                  Id = Guid.NewGuid(),
 80                  Title = name,
 81                  Type = Default.MetaProperty.Type.DateTime,
 82                  Permission = Default.Permission.Mask
 83              };
 84          }
 85  
 86           public   static  MetaProperty NewDate( string  name)
 87          {
 88               return   new  MetaProperty()
 89              {
 90                  Id = Guid.NewGuid(),
 91                  Title = name,
 92                  Type = Default.MetaProperty.Type.Date,
 93                  Permission = Default.Permission.Mask
 94              };
 95          }
 96  
 97           public   static  MetaProperty NewTime( string  name)
 98          {
 99               return   new  MetaProperty()
100              {
101                  Id = Guid.NewGuid(),
102                  Title = name,
103                  Type = Default.MetaProperty.Type.Time,
104                  Permission = Default.Permission.Mask
105              };
106          }
107  
108           public   static  MetaProperty NewUser( string  name)
109          {
110               return   new  MetaProperty()
111              {
112                  Id = Guid.NewGuid(),
113                  Title = name,
114                  Type = Default.MetaProperty.Type.User,
115                  Permission = Default.Permission.Mask
116              };
117          }
118  
119           public   static  MetaProperty NewUrl( string  name)
120          {
121               return   new  UrlMetaProperty()
122              {
123                  Id = Guid.NewGuid(),
124                  Title = name,
125                  Type = Default.MetaProperty.Type.Url,
126                  Permission = Default.Permission.Mask
127              };
128          }
129  
130           public   static  MetaProperty NewTag( string  name)
131          {
132               return   new  MetaProperty()
133              {
134                  Id = Guid.NewGuid(),
135                  Title = name,
136                  Type = Default.MetaProperty.Type.Tag,
137                  Permission = Default.Permission.Mask
138              };
139          }
140      }
141  
142       public   class  MetaProperties : List<MetaProperty>
143      {
144           public  MetaProperty Find( string  name)
145          {
146               return   this .SingleOrDefault(p => String.Compare(p.Title, name) ==  0 );
147          }
148  }

维护资料时,使用类似下面的代码就可以给资料创建无限多的属性,可以事先、事后给属性关联元数据,以便定义编辑和显示方式(里面用到一些 Ioc 和 Mock ):

 1       [TestMethod]
 2           public   void  验证客户资料的动态属性的可行性()
 3          {
 4               var  rep =  new  MemoryContext();
 5              MemoryMetaSet metas =  new  MemoryMetaSet();
 6              metas.Add( typeof (Customer), MetaProperty.NewString( " 姓名 " ));
 7              metas.Add( typeof (Customer), MetaProperty.NewNumber( " 年龄 " ));
 8              metas.Add( typeof (Customer), MetaProperty.NewAddress( " 地址 " ));
 9              metas.Add( typeof (Customer), MetaProperty.NewRelationship( " 同事 " ));
10              rep.MetaSet = metas;
11  
12               var  builder =  new  ContainerBuilder();
13               var  mocks =  new  Mockery();
14               var  user = mocks.NewMock<IUser>();
15              Expect.AtLeastOnce.On(user).GetProperty( " Email " ).Will(Return.Value(DEFAULT_USER_EMAIL));
16  
17              builder.RegisterInstance(user).As<IUser>();
18              builder.RegisterInstance(rep).As<IContext>();
19               var  back = IocHelper.Container;
20               try
21              {
22                  IocHelper.Container = builder.Build();
23  
24                   var  customer = Customer.New<Customer>();
25                  customer.Set( " 姓名 " ,  " XXX " );
26                  customer.Set( " 年龄 " ,  28 );
27                  customer.Set( " 地址 " ,  " ZZZZZZZZZZZZZZZZZZZZZZ " );
28  
29                   var  colleague = Customer.New<Customer>();
30                  colleague.Set( " 姓名 " ,  " YYY " );
31  
32                   //  对于稍微复杂一点的对象,我们可以用json对象
33                  customer.Set( " 同事 " , Relationship.Colleague(customer, colleague).ToString());
34  
35                  Assert.AreEqual( " XXX " , customer.Get( " 姓名 " ));
36              }
37               finally
38              {
39                  IocHelper.Container = back;
40              }
41          }

因为动态属性事先不知道其格式,为了实现搜索功能,无法在编写程序的时候拼接查询用的 SQL 语句,因此我抽象了一层,定义了一个小的查询语法,写了一个小小的编译器将查询语句转化成 SQL 语句,实现了对动态属性的查询功能,请看下面的测试用例:

 1          [TestMethod]
 2           public   void  测试简单的条件组合查询()
 3          {
 4               using  ( var  context = IocHelper.Container.Resolve<IContext>())
 5              {
 6                   var  customer = Customer.New<Customer>();
 7                  customer.Set( " 姓名 " ,  " 测试简单的条件组合查询 " );
 8                  customer.Set( " 年龄 " ,  28 );
 9                  customer.Set( " 地址 " ,  " 上海市浦东新区 " );
10                  context.Customers.Add(customer);
11                  context.SaveChanges();
12  
13                   var  result = context.Customers.Query( " (AND (姓名='测试简单的条件组合查询') "  +
14                                                        "      (年龄 介于 1 到 30) "  +
15                                                        " ) " );
16                  Assert.IsTrue(result.Count() >  0 );
17                   var  actual = result.First();
18                  Assert.AreEqual( " 测试简单的条件组合查询 " , actual.Get( " 姓名 " ));
19                  Assert.AreEqual( " 28 " , actual.Get( " 年龄 " ));
20              }
21          }

上面测试用例里的查询语句:

(AND ( 姓名 =' 测试简单的条件组合查询 ') ( 年龄   介于  1  到  30) )

经过 Query 函数的编译之后,会转化成下面的两段 SQL 语句:

SELECT  e. * , p. *   FROM   Properties  AS  p  INNER   JOIN   GenericDynamicPropertiesEntities  AS  e  ON  p.EntityId  =  e.Id  INNER   JOIN   MetaSet  AS  m  ON  p.MetaId  =  m.Id   WHERE   (( CASE    WHEN  m.Type  =   ' 日期时间型 '   AND  ( CONVERT ( datetime , p.Value)  =  N ' 测试简单的条件组合查询 ' )  AND  (m.Title  =  N ' 姓名 ' )  THEN   1    WHEN  m.Type  =   ' 字符串 '   AND  (p.Value  =  N ' 测试简单的条件组合查询 ' )  AND  (m.Title  =  N ' 姓名 ' )  THEN   1    ELSE   0   END   =   1 ))

SELECT  e. * , p. *   FROM   Properties  AS  p  INNER   JOIN   GenericDynamicPropertiesEntities  AS  e  ON  p.EntityId  =  e.Id  INNER   JOIN   MetaSet  AS  m  ON  p.MetaId  =  m.Id   WHERE   (( CASE    WHEN  m.Type  =   ' 数字型 '   AND  ( CONVERT ( int , p.Value)  BETWEEN   1   AND   30 )  AND  (m.Title  =  N ' 年龄 ' )  THEN   1    WHEN  m.Type  =   ' 日期时间型 '   AND  ( CONVERT ( datetime , p.Value)  BETWEEN  N ' 1 '   AND  N ' 30 ' )  AND  (m.Title  =  N ' 年龄 ' )  THEN   1    ELSE   0   END   =   1 ))

然后分别执行查询并在业务层将求解查询结果的交集,也许是可以直接生成一条SQL语句交给数据库处理成最精简的结果再返回的,但是因为开发时间、以及目标客户的关系,暂时没有花精力做这个优化。

当然上面的查询语句写起来还比较复杂,因此我做了一个界面方便用户编辑查询条件,另外对资料属性的编辑、元数据维护等内容,后面再写文章说,蚊子太多了…… 



 

 

 

标签:  NET ,  ASP.NET

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于动态属性的一个架构的详细内容...

  阅读:40次