CommunityServer中广泛应用了一种对象持久化的机制, 这种机制和.Net2.0中的Profile是相同的原理,把对象序列化为一个字符串保存到数据库的一个字段中,同时使用另一个字段保存对象所在字符串的位置,看看一个例子:
[PropertyNames]字段的值:
EnableAllPreview:S:
0
:
5
:EnablePreviewResumeAttachment:S:
5
:
4
:EnablePreviewBaseInfo:S:
9
:
4
:EnablePreviewDetailInfo:S:
13
:
4
:EnablePreviewJobExp:S:
17
:
5
:EnablePreviewEduExp:S:
22
:
5
:EnablePreviewUserSkill:S:
27
:
5
:EnablePreviewUserLanguage:S:
32
:
5
:EnablePreviewCertificate:S:
37
:
5
:EnablePreviewAdvUser:S:
42
:
5
:EnablePreviewTrainingExperience:S:
47
:
5
:EnablePreviewProjectExperience:S:
52
:
5
:
[PropertyValues]字段的值:
FalseTrueTrueTrueFalseFalseFalseFalseFalseFalseFalseFalse
上面代码中的"S:0:5"表示了对象在字符串中的位置和类型
"S"表示这个对象是一个字符串
"0"表示这个对象在字符串中开始的索引
"5"表示这个对象的长度
根据这些信息,程序或都SQL代码都可以提取出所需要的对象,例如:
EnableAllPreview:S:0:5: 表示EnableAllPreview表示这个对象保存在[PropertyValues]中是一个字符串的形式,并且它的开始位置是从0开始,长度为5个字符,根据这样的条件可以得到对象的值是False
CommunityServer内部已经封装好了一些方法用于方便使用这样的持久化机制.
1.为想使用这种持久化机制的表添加两个字段:
ALTER
TABLE
Job_ResumeState
ADD
PropertyNames
ntext
NULL
ALTER
TABLE
Job_ResumeState
ADD
PropertyValues
ntext
NULL
2.表对应的对象继承于ExtendedAttributes对象,对象中封装了一些方法,让你可以非常方便的添加扩展属性.
[Serializable]
public
class
ExtendedAttributes
{
public
ExtendedAttributes() { }
NameValueCollection extendedAttributes
=
new
NameValueCollection();
///
/
<summary>
///
获取扩展属性
///
</summary>
public
string
GetExtendedAttribute(
string
name)
{
string
returnValue
=
extendedAttributes[name];
if
(returnValue
==
null
)
return
string
.Empty;
else
return
returnValue;
}
///
/
<summary>
///
设置扩展属性
///
</summary>
public
void
SetExtendedAttribute(
string
name,
string
value)
{
if
((value
==
null
)
||
(value
==
string
.Empty))
extendedAttributes.Remove(name);
else
extendedAttributes[name]
=
value;
}
///
/
<summary>
///
扩展属性的数量
///
</summary>
public
int
ExtendedAttributesCount
{
get
{
return
extendedAttributes.Count; }
}
///
/
<summary>
///
获取Bool型的扩展属性
///
</summary>
public
bool
GetBool(
string
name,
bool
defaultValue)
{
string
b
=
GetExtendedAttribute(name);
if
(b
==
null
||
b.Trim().Length
==
0
)
return
defaultValue;
try
{
return
bool
.Parse(b);
}
catch
{ }
return
defaultValue;
}
///
/
<summary>
///
获取整型扩展属性
///
</summary>
public
int
GetInt(
string
name,
int
defaultValue)
{
string
i
=
GetExtendedAttribute(name);
if
(i
==
null
||
i.Trim().Length
==
0
)
return
defaultValue;
return
Int32.Parse(i);
}
///
/
<summary>
///
获取日期型扩展属性
///
</summary>
public
DateTime GetDateTime(
string
name, DateTime defaultValue)
{
string
d
=
GetExtendedAttribute(name);
if
(d
==
null
||
d.Trim().Length
==
0
)
return
defaultValue;
return
DateTime.Parse(d);
}
///
/
<summary>
///
获取字符型扩展属性
///
</summary>
public
string
GetString(
string
name,
string
defaultValue)
{
string
v
=
GetExtendedAttribute(name);
return
(String.IsNullOrEmpty(v))
?
defaultValue : v;
}
Serialization
#region
Serialization
///
/
<summary>
///
获取扩属性的序列化数据
///
</summary>
///
<returns></returns>
public
SerializerData GetSerializerData()
{
SerializerData data
=
new
SerializerData();
//
data.Bytes = Serializer.ConvertToBytes(this.extendedAttributes);
string
keys
=
null
;
string
values
=
null
;
Serializer.ConvertFromNameValueCollection(
this
.extendedAttributes,
ref
keys,
ref
values);
data.Keys
=
keys;
data.Values
=
values;
return
data;
}
///
/
<summary>
///
设置扩属性的序列化数据
///
</summary>
public
void
SetSerializerData(SerializerData data)
{
//
if(data.Bytes != null)
//
{
//
try
//
{
//
extendedAttributes = Serializer.ConvertToObject(data.Bytes) as NameValueCollection;
//
}
//
catch{}
//
}
if
(
this
.extendedAttributes
==
null
||
this
.extendedAttributes.Count
==
0
)
{
this
.extendedAttributes
=
Serializer.ConvertToNameValueCollection(data.Keys, data.Values);
}
if
(
this
.extendedAttributes
==
null
)
extendedAttributes
=
new
NameValueCollection();
}
#endregion
}
其中的public SerializerData GetSerializerData()和public void SetSerializerData(SerializerData data)用于获取和添加序列化数据时使用,在下面的例子中会有更详细的讨论,而其它方法都是为了方便从序列化中获取或者设置对象的助手方法.
看一下继承于此扩展属性类的类:
[Serializable]
public
class
ResumeState : ExtendedAttributes
{
private
Guid resumeid;
public
Guid Resumeid
{
get
{
return
resumeid; }
set
{ resumeid
=
value; }
}
public
bool
EnableAllPreview
{
get
{
return
GetBool(
"
EnableAllPreview
"
,
false
); }
set
{ SetExtendedAttribute(
"
EnableAllPreview
"
, value.ToString()); }
}
public
bool
EnablePreviewBaseInfo
{
get
{
return
GetBool(
"
EnablePreviewBaseInfo
"
,
false
); }
set
{ SetExtendedAttribute(
"
EnablePreviewBaseInfo
"
, value.ToString()); }
}
}
其中的EnableAllPreview和EnablePreviewBaseInfo属性就是一个扩展属性,这属性是使用序列化的机制进行保存的,它伴随着对象进行获取,添加,更新,删除,只要框架定好了添加一个新的扩展属性并不需要任何数据库操作就能够快速的实现.
3.扩展属性是如何加载到对象的,这里看一段代码从数据库返回的IDataReader填充对象的操作:
public
static
ResumeState PopulateResumeStateFromIDataReader(IDataReader dr)
{
ResumeState state
=
new
ResumeState();
SerializerData data
=
new
SerializerData();
state.Resumeid
=
(Guid)dr[
"
Resumeid
"
];
state.ViewType
=
(ViewType)dr[
"
ViewType
"
];
data.Keys
=
ProviderHelper.GetString(dr,
"
PropertyNames
"
,
null
);
data.Values
=
ProviderHelper.GetString(dr,
"
PropertyValues
"
,
null
);
state.SetSerializerData(data);
return
state;
}
SerializerData data = new SerializerData(); 是用来保存对象名值对的自定义对象
public
class
SerializerData
{
public
string
Keys;
public
string
Values;
}
事实上对象的提取就是根据键值进行提取的,这个对象会在state.SetSerializerData(data);方法中填充并反序列化到一个NameValueCollection对象中,这个对象就是保存在ExtendedAttributes类中的NameValueCollection extendedAttributes
有了这个NameValueCollection后就可以根据键来得到其相应的值,例如:
get { return GetBool("EnableAllPreview", false); }就可以获取EnableAllPreview属性的值
4.扩展属性如何保存到数据库字段中?
public
override
int
InsertResumeState(ResumeState resumeState)
{
Database db
=
DatabaseFactory.CreateDatabase();
SerializerData data
=
resumeState.GetSerializerData();
string
sqlCommand
=
"
Job_ResumeState_Insert
"
;
DbCommand dbCommand
=
db.GetStoredProcCommand(sqlCommand);
db.AddInParameter(dbCommand,
"
@Resumeid
"
, DbType.Guid, resumeState.Resumeid);
db.AddInParameter(dbCommand,
"
@ViewType
"
, DbType.Int32, resumeState.ViewType);
db.AddInParameter(dbCommand,
"
@PropertyNames
"
, DbType.String, data.Keys);
db.AddInParameter(dbCommand,
"
@PropertyValues
"
, DbType.String, data.Values);
db.AddOutParameter(dbCommand,
"
@retvar
"
, DbType.Int32,
4
);
try
{
db.ExecuteNonQuery(dbCommand);
return
(
int
)db.GetParameterValue(dbCommand,
"
@retvar
"
);
}
catch
{
return
-
1
;
}
}
如上例子,在添加一新简历状态时只需要使用封装好的方法获取当前对象的扩展属性的SerializerData对象
SerializerData data = resumeState.GetSerializerData();
因为ResumeState是继承于ExtendedAttributes属性的,所以可以直接使用GetSerializerData()方法序列化出一个含有扩展属性的序列化字符串,从而可以保存到数据库:
db.AddInParameter(dbCommand,
"
@PropertyNames
"
, DbType.String, data.Keys);
db.AddInParameter(dbCommand,
"
@PropertyValues
"
, DbType.String, data.Values);
最后贴上一个封装好的序列化和反序化方法:
///
<summary>
///
Creates a NameValueCollection from two string. The first contains the key pattern and the second contains the values
///
spaced according to the kys
///
</summary>
///
<param name="keys">
Keys for the namevalue collection
</param>
///
<param name="values">
Values for the namevalue collection
</param>
///
<returns>
A NVC populated based on the keys and vaules
</returns>
///
<example>
///
string keys = "key1:S:0:3:key2:S:3:2:";
///
string values = "12345";
///
This would result in a NameValueCollection with two keys (Key1 and Key2) with the values 123 and 45
///
</example>
public
static
NameValueCollection ConvertToNameValueCollection(
string
keys,
string
values)
{
NameValueCollection nvc
=
new
NameValueCollection();
if
(keys
!=
null
&&
values
!=
null
&&
keys.Length
>
0
&&
values.Length
>
0
)
{
char
[] splitter
=
new
char
[
1
] {
'
:
'
};
string
[] keyNames
=
keys.Split(splitter);
for
(
int
i
=
0
; i
<
(keyNames.Length
/
4
); i
++
)
{
int
start
=
int
.Parse(keyNames[(i
*
4
)
+
2
], CultureInfo.InvariantCulture);
int
len
=
int
.Parse(keyNames[(i
*
4
)
+
3
], CultureInfo.InvariantCulture);
string
key
=
keyNames[i
*
4
];
//
Future version will support more complex types
if
(((keyNames[(i
*
4
)
+
1
]
==
"
S
"
)
&&
(start
>=
0
))
&&
(len
>
0
)
&&
(values.Length
>=
(start
+
len)))
{
nvc[key]
=
values.Substring(start, len);
}
}
}
return
nvc;
}
///
/
<summary>
///
Creates a the keys and values strings for the simple serialization based on a NameValueCollection
///
</summary>
///
<param name="nvc">
NameValueCollection to convert
</param>
///
<param name="keys">
the ref string will contain the keys based on the key format
</param>
///
<param name="values">
the ref string will contain all the values of the namevaluecollection
</param>
public
static
void
ConvertFromNameValueCollection(NameValueCollection nvc,
ref
string
keys,
ref
string
values)
{
if
(nvc
==
null
||
nvc.Count
==
0
)
return
;
StringBuilder sbKey
=
new
StringBuilder();
StringBuilder sbValue
=
new
StringBuilder();
int
index
=
0
;
foreach
(
string
key
in
nvc.AllKeys)
{
if
(key.IndexOf(
'
:
'
)
!=
-
1
)
throw
new
ArgumentException(
"
ExtendedAttributes Key can not contain the character \
"
:\
""
);
string
v
=
nvc[key];
if
(
!
String.IsNullOrEmpty(v))
{
sbKey.AppendFormat(
"
{0}:S:{1}:{2}:
"
, key, index, v.Length);
sbValue.Append(v);
index
+=
v.Length;
}
}
keys
=
sbKey.ToString();
values
=
sbValue.ToString();
}