设为首页 收藏本站
查看: 1076|回复: 0

[经验分享] Linq to SharePoint,看上去很美

[复制链接]

尚未签到

发表于 2015-9-24 08:10:54 | 显示全部楼层 |阅读模式
  Linq to SharePoint是SharePoint 2010引入的一组新API,在这之前,如果我们想要按照条件过滤SharePoint列表中的数据,只能通过CAML。
  但使用CAML并不是件令人身心愉悦的事情,至少我是这么认为的。我觉得在代码中嵌入一块冗长的XML字符串非常破坏美感,我尤其喜欢强类型,所以一直很难接受SPListItem用字符串作为键值去获取Field值的方式,更别提这些值都是Object类型,还得再经过一次转换。
  所以我比较喜欢将SPListItem转换成实体类来使用,只不过一直以来的做法都是自己写实体类和转换方法。而Linq to SharePoint则可以自动将列表映射为实体类,并且可以使用Linq语句来进行查询,看上去很美!
  那么Linq to SharePoint能不能帮我彻底摆脱CAML呢,趁着重构代码的机会研究了一下,在这里简单总结一下。
  前面说过Linq to SharePoint可以自动生成列表的实体类,这是通过一个叫做SPMetal的工具来实现的,具体的用法请查阅这里。
  SPMetal会根据实际的列名来生成实体类中的属性名,所以如果你的列名是中文的话(譬如你安装了中文版SharePoint),你会得到一份非常诡异且冗长的代码文件。
  当然,如果你能接受中英文混排的代码的话,这倒也不是什么问题。
  SPMetal生成的属性大多是下面这个样子的:



[ColumnAttribute(Name = "Body", Storage = "_body", FieldType = "Note")]
public string Body
{
get
{
return this._body;
}
set
{
if ((value != this._body))
{
this.OnPropertyChanging("Body", this._body);
this._body = value;
this.OnPropertyChanged("Body");
}
}
}
protected string _body;
  Body属性被附加了一个ColumnAttribute,它的作用是将属性和SharePoint中的某一列关联起来。在它的命名参数中,Name表示的就是SharePoint中的列名,FieldType指列的类型,Storage表示的是实体类中用来存放列值的变量,可以看到这里为它指定的是一个变量,而不是Body属性,也就是说,在初始化这一实体的时候,该列的值会直接赋给_body变量,而不经过Body属性。那么Body属性的set访问器又是用来干什么的呢?实际上它的作用只是为了提供一种更改列值的机制,这一点从它复杂的内部流程也能看出端倪。
  如果你只是为了查询方便,并不需要修改和提交数据的话,完全可以使用下面的只读版本:



[Column(Name = "Body", Storage = "_body", FieldType = "Note")]
public string Body
{
get
{
return this._body;
}
}
protected string _body;
  此外,如果列表的包含一些设置为可空值的列的话,它们会被映射成一个Nullable<T>类型,如下所示:



[ColumnAttribute(Name="RatingCount", Storage="_ratingCount", FieldType="Number")]
public System.Nullable<double> RatingCount{
get {
return this._ratingCount;
}
set {
if ((value != this._ratingCount)) {
this.OnPropertyChanging("RatingCount", this._ratingCount);
this._ratingCount= value;
this.OnPropertyChanged("RatingCount");
}
}
}
private System.Nullable<double> _ratingCount;
  虽然可以理解这么做的原因,但是却很难接受这种代码。尤其是在HTML中做绑定时,你不得不针对Nullable属性额外写一些代码来处理它的非空情况。
  好在我们可以将属性本身改成非空的类型,然后在get访问器里根据情况返回真实的值或者默认值:



[Column(Name="RatingCount", Storage="_ratingCount", FieldType="Counter")]
public double RatingCount{
get {
return this._ratingCount ?? 0;
}
}
private double? _ratingCount;
  但要注意Storage指向的变量还得是Nullable类型,以保存列的真实的值(包括空值);属性的类型虽然可以改为非空类型,但要注意类型一定要和对应的变量相同,因为ColumnAttribute会在初始化时检查属性的实际类型。我曾尝试写过下面这样的属性,结果只收获了一个异常:



[Column(Name="RatingCount", Storage="_ratingCount", FieldType="Counter")]
public int RatingCount{
get {
return this._id ? (int)this._id.Value : 0;
}
}
private double? _ratingCount;
  为什么想要这么做呢?因为我实在想不出投票总数为什么会是一个小数?
  如果你刚巧需要使用Linq语句查询列表,而且查询条件刚巧也是一个包含可空值的列的话,就不能用上面提到的方法来修改属性的类型了,否则Linq to SharePoint将无法生成CAML,结果也是以异常告终。
  如果想让可空值列的映射属性既能在Linq语句里作为条件,又能让调用者方便使用的话,只能像下面这样定义它们,对,是它们:



[ColumnAttribute(Name = "RatingCount", Storage = "_ratingCount", FieldType = "Number")]
public double? RatingCountField
{
get { return _ratingCount; }
}
protected double? _ratingCount;
public int RatingCount { get { return this._ratingCount.HasValue ? (int)this._ratingCount.Value : 0; } }
  平常使用int类型的RatingCount,在Linq语句里查询时使用double?类型的RatingCountField。
  坦白说,我很讨厌这样的代码,两个含义相同的属性必然会让其他阅读者感到困惑。
  此外,如果列是一个查阅项(譬如Author列),我们可以做到映射这个查阅项的完整字符串(譬如“12;#windstyle\chai”),或者查阅项的ID(譬如“12”),或者查阅项的值(譬如“windstyle\chai”),所做的仅仅是在ColumnAttribute里指定IsLookupId或IsLookupValue(如果要拿到完整字符串,则什么都别指定):



[Column(Name = "Author", Storage = "_authorId", FieldType = "Text", IsLookupId = true)]
public int AuthorId
{
get { return _authorId; }
}
protected int _authorId;
  而且如果指定了IsLookupId,就可以在Linq语句中使用这一属性来做查询了。
  以上提到的都是关于列与属性的映射,然而有一些列很难通过简单的映射变成属性,那就需要另外一种机制:自定义映射。
  自定义映射需要实体类实现ICustomMapping接口,并实现它的三个成员方法MapFrom、MapTo和Resolve,我们这里只讨论只读实体类的情况,只需实现MapFrom即可:



[CustomMapping(Columns = new string[] { Attachments" })]
public override void MapFrom(object listItem)
{
var item = listItem as Microsoft.SharePoint.SPListItem;
this.IsRootPost = item["IsRootPost"].ToString();
if (this.IsRootPost == "1")
this.Url = item.Web.Url + "/" + item.Folder.Url;
else
this.Url = new Uri(new Uri(item.Web.Url), item.ParentList.DefaultDisplayFormUrl + "?id=" + item.ID).ToString();
}
  MapFrom方法包含一个listItem参数,可以通过它来拿到SPListItem的列值,具体能拿到哪些列,需要在修饰MapFrom的CustomMappingAttribute中指定。
  在使用Lambda表达式进行查询时,CustomMappingAttribute中指定的列名以及之前属性映射时指定的列名都会成为ViewFields的一员。
  但需要注意的是,如果你在Linq语句中使用了通过MapFrom映射而来的属性,那么它将不会出现在CAML的Query语句中,Linq to SharePoint采取的方法是把所有SPlistItem都获取并转换成实体类,然后通过Linq to Objects来进行第二次查询(而普通的映射属性则不存在这个问题)。
  这当然是极大的性能隐患,然而在Linq to SharePoint中,类似的性能隐患还不止这一处,而且稍不注意就会中招。
  譬如根据ID来查找某一Item,我们通常会写出这样的代码:



var item = list.First(i => i.Id == root.ID);
  或者



var item = list.Where(i => i.Id == root.ID).First();
  或者



var item = list.Where(i => i.Id == root.ID).Single();
  这三行代码看起来没有任何问题,而且最终也会被翻译成一模一样的CAML(我省去了ViewFields):



<View>
<Query>
<Where>
<Eq>
<FieldRef Name="ID" />
<Value Type="Counter">16</Value>
</Eq>
</Where>
</Query>
<RowLimit Paged="TRUE">2147483647</RowLimit>
</View>
  注意RowLimit,它的值居然是2147483647,这表示查询会返回列表中的所有条目,并将它们都转换成实体类,然后再使用Linq to Objects来进行查询。
  MSDN的这篇文档中的“Additional Performance Considerations”一节虽然明确了哪些方法会导致这种行为,但First和Single这两个方法居然都被标记为Efficient。
  那么正确的获取单个条目的查询表达式该怎么写呢?使用Take方法,只有Take方法才会正确的翻译成RowLimit:



this.Root = list.Where(i => i.Id == root.ID).Take(1).Single();
  它会被翻译成(同样省去了ViewFields):



<View>
<Query>
<Where>
<Eq>
<FieldRef Name="ID" />
<Value Type="Counter">16</Value>
</Eq>
</Where>
</Query>
<RowLimit Paged="TRUE">1</RowLimit>
</View>
  同样的试验还可以参考这里。
  此外,我们知道Linq可以使用Take和Skip方法来进行数据分页,但在Linq to SharePoint中,Skip方法并不会翻译成CAML的分页语句,它还是会拿到所有条目。
  写到这里,基本上已经把我所遇到的所有难以接受的部分都介绍完了,其实前面几条,若不是有严重的代码洁癖的话,也可以不必在意。
  最后提到的性能问题才是关键所在,而且你很难通过阅读代码来发现问题所在,微软的官方文档对性能问题的解释也模棱两可,一会儿说性能很棒,一会儿又说可能会导致极差的性能。所以最好还是检查一下每一条查询语句生成的CAML是否有问题(DataContext的Log属性会输出所有翻译后的CAML)。
  现在你应该明白标题的含义了。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-117898-1-1.html 上篇帖子: SharePoint 2010 MasterPage去Ribbon 的方法 下篇帖子: SharePoint 2010 文档管理之点击次数
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表