最近几天发现我始终不能在博客堂发布带有较多图片的Blog了,无论是Web发布还是Writer发布,都提示SQL错误:
“
发生服务器错误 0
String or binary data would be truncated.
The statement has been terminated. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at SubSonic.SqlDataProvider.ExecuteQuery(QueryCommand qry)
at SubSonic.StoredProcedure.Execute()
at Joycode.Framework.Data.DatabaseObjectProvider.InsertEntry(Entry entry) in D:\Projects\Joycode\Joycode.Blog\Joycode.Framework\Data\DatabaseObjectProvider.cs:line 623
at Joycode.Framework.Data.DatabaseObjectProvider.CreateEntry(Entry entry, Int32[] categoryIds) in D:\Projects\Joycode\Joycode.Blog\Joycode.Framework\Data\DatabaseObjectProvider.cs:line 576
at Joycode.Framework.Entries.Create(Entry entry) in D:\Projects\Joycode\Joycode.Blog\Joycode.Framework\Entries.cs:line 259
at Joycode.Framework.XmlRpc.MetaWeblog.newPost(String blogid, String username, String password, Post post, Boolean publish) in D:\Projects\Joycode\Joycode.Blog\Joycode.Framework\XmlRpc\MetaWeblog.cs:line 274
”
比如这三篇:
Reporting Service Tips 101(#5) - 在报表页面上添加交互式排序
Reporting Service Tips 101(#6) - RS中从一个报表jump到另外一个报表
Reporting Service Tips 101(#7) - 使用RS制作树状可折叠报表
在CnBlogs正常发布,但是博客堂全部失败。
一般来说,我们把报表分为两类,一类是ad-hoc的报表,用于实时查询,客户可以输入特定的参数,得到他们感兴趣的报表,还有一类是scheduling的报表,用于自动生成,一般包括daliy,monthly,quarterly和yearly的报表,这种定制类的报表,可以在指定的时间,生成到指定的目录,他们生成的内容也会提前定制,参数不可更改。一般在报表的需求定义中,客户都会要求报表能够做到自动生成,这也就是我们所说的第二类报表,有时候,客户还会要求能够在自动生成的同时,实现自动打印。下面我们来谈谈如何实现报表的自动生成以及自动打印。
首先来谈谈解决方案,由于RS提供web service式的调用,因此我们可以写一个remoting service或者windows service或者仅仅是一段程序,然后由job之类的调用,来实现报表的自动。在程序中,我们调用RS,来实现报表的生成。需要生成的报表列表,报表的参数(时间参数),导出文件的格式,我们定义在数据库里,解决方案简单的用图表来表现如下:
下面我们来谈具体的步骤
第一步:创建项目,添加web引用(C#项目),URL为http://localhost/ReportServer/ReportService.asmx

第二步,取参数以及定义其他的参数。
参数表的内容为:
ReportName PeriodType PromptName1 Prompttype1 Format
SupplierD D date datetime PDF
SupplierM M date datetime PDF
SupplierQ Q date datetime PDF
SupplierY Y date datetime PDF
ReportName为报表的名称,PeriodType为报表的类别,分别是日报,月报,季度报和年报,PromptName1和Prompttype1为参数的名字和类别,如果有需要,可以加更多的参数。Format为报表导出的格式。(为方便描述,下述代码中的参数都写死,实际操作中,会从数据库或者从注册表中读取。)
定义参数的代码为:
ParameterValue[] parameters = null;
parameters[1] = new ParameterValue();
parameters[1].Name = “date”;
parameters[1].Value =”01/01/2008”;
定义其他参数:
string devInfo = @"<DeviceInfo><Toolbar>False</Toolbar></DeviceInfo>";
string encoding;
string mimeType;
Warning[] warnings = null;
string[] streamIDs = null;
DataSourceCredentials[] credentials = null;
ParameterValue[] used = null;
第三步,生成报表并得到报表的二进制流
ReportingService rep = new ReportingService();
rep.Credentials = rep.Credentials = System.Net.CredentialCache.DefaultCredentials;
byte[] ss = null;
得到报表:
ss = rep.Render(“SupplierD” , “PDF”, null, devInfo, parameters, credentials, null, out encoding, out mimeType, out used, out warnings, out streamIDs);
使用的方法是RS提供的
Render ( Report As string , Format As string , HistoryID As string , DeviceInfo As string , Parameters As ArrayOfParameterValue , Credentials As ArrayOfDataSourceCredentials , ShowHideToggle As string ) As base64Binary
第四步:将生成的报表保存到服务器
FileStream repFile = new FileStream(@“D:\Report\ SupplierD”, FileMode.Create);
repFile.Write(ss, 0, ss.Length);
repFile.Close();
第五步:自动打印生成的报表
System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.Arguments = "/p /h " + @“D:\Report\ SupplierD”;
startInfo.FileName = @"C:\Program Files\Adobe\Reader 8.0\Reader\AcroRd32.exe"; ; //Replace this with the path of the Acrobat Reader executable file, i.e. AcroRd32.exe
startInfo.UseShellExecute = true;
System.Diagnostics.Process process = Process.Start(startInfo);
process.WaitForExit(10000);
if (process.HasExited == false)
{
process.Kill();
}
这里的方法其实是打印已经生成的PDF文件,所以要求该机器必须装有Acrobat Reader。不选用直接打印二进制数据流,是因为这种打印方法无法设置横向打印(Landscape)或者是竖向打印(Portrait),而报表肯定是有横向有竖向的。不选用打印生成html文件的方式,是因为RS不支持导出HTML,只支持MHTML,而MHTML转成HTML太麻烦。
最后,有几点需要注意的是,RS导出的报表必须是已经发布到Report server上的,调用render方法时,第一个参数Report As string必须和reportserver上的路径一致,比如报表的发布路径是Report/SupplierD,那么这个参数的值为”Report/SupplierD”。还有一个要注意的是如果制作报表时,参数设置成下拉框式(如下图),那么传入的值必须是这些值其中之一或者之几(看是否是Multi-value)。

留言请访问如下链接:
Reporting Service Tips 101(#4) - 使用RS实现报表的自动生成以及在程序中调用RS
接上一篇(Reporting Service Tips 101 - 关于使用Sum函数会遇到的问题(1)),谈谈使用sum可能会遇到的另外一个问题。
Dateset的数据如下:
Supplier Revenue status
A 5.00 0
A 6.00 1
A 7.00 0
需求是只用sum算出Supplier A在status为0时候的Revenue总和。单看这个需求,我们其实很容易用在报表中添加分组,或者直接在数据的上一层group来解决,但是在做很多复杂报表的时候,如果能够在计算公式里面去解决一些问题,整个报表的开发过程会简单得多。
报表如下:

计算公式为:=FormatNumber(Sum(Fields!Revenue.Value and Fields!status.Value=0),2)
结果如下:

可以看到,正是我们想要的结果。但是这种方法,在遇到很复杂的报表的时候,有时候会有问题,所以我们还可以用另外一种方法。
报表:

计算公式为:=FormatNumber(Sum(iif(Fields!status.Value=0,Fields!Revenue.Value,0)),2)
结果:

和上一种方法的结果一样,但是这种方法的好处是,基本上无论多复杂的报表,都不会有问题。
留言请访问如下链接:
Reporting Service Tips 101(#3) - 关于使用Sum函数会遇到的问题(2)
用过Reporting service (后面都用RS代替)的人对sum这个函数都不会陌生,这个函数的使用率极高并且非常好用,下面我们就来谈谈使用这个函数可能会遇到的一个问题。
我们先假设报表使用的dataset传过来的数据如下
Supplier Revenue
A 0.00
B 0.00
报表的需求是要算出每个Supplier的Revenue所占的比率。我们在报表中新建一个table,table中新建一个group,group绑定的值为Fields!SupplierName.Value,group的名字为SupplierGroup,该Dataset的名字为Dssup。报表如下:

可以看到比率的计算公式为:
=FormatPercent(Sum(Fields!Revenue.Value,"SupplierGroup")/Sum(Fields!Revenue.Value,"Dssup"),2)
但事实上,由这个计算公式得到的值却是这样:

这个很好理解,是因为我们的计算公式的分母为0,所以出现了NaN这种值,这个时候,按照正常的逻辑,我们都会选择先判断一下分母,改后的计算公式是这样(为了便于查看,先去掉函数FormatPercent):
iif(Sum(Fields!Revenue.Value,"Dssup") = 0, 0.00%, Sum(Fields!Revenue.Value,"SupplierGroup")/Sum(Fields!Revenue.Value,"Dssup"))
这个是说如果分母为0,那么传回0.00%,反之通过公式计算。从这个公式来看,没有问题,但是不幸的是,结果依然是NaN。
那么怎么办呢,继续尝试下去,改计算公式为
iif(Sum(Fields!Revenue.Value,"SupplierGroup")/Sum(Fields!Revenue.Value,"Dssup")=’NaN’, 0.00%, Sum(Fields!Revenue.Value,"SupplierGroup")/Sum(Fields!Revenue.Value,"Dssup"))
结果证明,改成这样,还是不行。
这个时候,考虑到当Sum(Fields!Revenue.Value,"Dssup") = 0 时,Sum(Fields!Revenue.Value,"SupplierGroup")一定也为0,所以改计算公式为:
Sum(Fields!Revenue.Value,"SupplierGroup")/iif(Sum(Fields!Revenue.Value,"Dssup")=0,1,Sum(Fields!Revenue.Value,"Dssup"))
既如果分母为0,那么强制将分母变为1,最后结果为

这是我们想要的结果。最终计算公式是:
=FormatPercent(Sum(Fields!Revenue.Value,"SupplierGroup")/iif(Sum(Fields!Revenue.Value,"Dssup")=0,1,Sum(Fields!Revenue.Value,"Dssup")),2)
留言请访问如下链接: Reporting Service Tips 101(#2) - 关于使用Sum函数会遇到的问题(1)
Reporting Service有三种报表发布方式:
一、是在报表管理器上直接上传报表,创建数据源,这种方式很麻烦,只适用于少量的报表。
二、是在VS.net里的project里面直接发布,这种只适用于开发环境。
三、就是使用RSS脚本进行自动发布。下面我们就来初步了解一下RSS这种发布方式。
默认情况下,安装完Reporting Service,我们可以在Microsoft SQL Server\90\Samples\Reporting Services\Script Samples目录下找到一个名为PublishSampleReports.rss的文件,同时在联机文件中,我们也可以找到该文件。这个RSS文件就是自动发布的脚本,但是内容比较基础简单,下面我们在这个文件基础上进行一些扩展。
1. 这个是一个比较重要的问题,提供的RSS脚本,要求默认windows认证或者允许匿名访问,但是在实际的生产环境中,一般是用的域认证,且出于安全考虑,禁止匿名访问。所以这一块,我们要改变RSS的认证方式。
已有的代码是:
rs.Credentials = System.Net.CredentialCache.DefaultCredentials
改为:
rs.Credentials = new System.Net.NetworkCredential(User_Name,User_Password,User_domin)
然后在命令行里面加上这三个参数即可:
rs -i PublishSampleReports.rss -s http://ReportServerName/reportserver -u UserName -p UserPassword\Userdomin
在指定的服务器上进行发布时,有关权限问题这一块,我们还应当考虑是否拥有报表服务器的访问权限,以及是否对对要访问的报表服务器的根文件夹具有相应的权限。在实际操作中,权限问题是报得最多的error。
2.该脚本必须用Visual Studio VB.NET语言编写,因此也可以用VB.NET进行一些额外的操作,例如判断报表是否已经被发布。
留言请访问:Reporting Service Tips 101(#1) - 使用RSS在指定的服务器上自动进行ReportingService报表发布