Tuesday, 9 August 2011

Change the data source of a Crystal Report at Run-time using C#

It’s a known fact, that in Crystal Reports, if you try to display details, using data in a SQL Server Database other than the one that you’ve used to design the report, either you have to set the database location (or refresh the report). This is a common issue that development team face when the reports are being deployed to the production environment.

But this can be prevented using the following method.

Following namespaces are required.

using CrystalDecisions.CrystalReports.Engine;

using CrystalDecisions.Shared;



And we have to assign the required SQL server information to each table/view of the report, sub reports and the report viewer.

Use the following code:


SQLReport report = new SQLReport();

//Get SQL Server Details
string zServer = @"SERVER_NAME";
string zDatabase = @"DATABASE";
string zUsername = @"USER";
string zPassword = @"PASSWORD";

ConnectionInfo ciReportConnection = new ConnectionInfo();

ciReportConnection.ServerName = zServer;
ciReportConnection.DatabaseName = zDatabase;
ciReportConnection.UserID = zUsername;
ciReportConnection.Password = zPassword;

//Assign data source details to tables

foreach (Table table in report.Database.Tables) {
table.LogOnInfo.ConnectionInfo = ciReportConnection;
table.ApplyLogOnInfo(table.LogOnInfo);
}

foreach (ReportDocument subrep in report.Subreports) {
foreach (Table table in subrep.Database.Tables) {
table.LogOnInfo.ConnectionInfo = ciReportConnection;
table.ApplyLogOnInfo(table.LogOnInfo);
}
}

//Assign data source details to the report viewer
if (this.crystalReportViewer1.LogOnInfo != null) {
TableLogOnInfos tlInfo = this.crystalReportViewer1.LogOnInfo;
foreach (TableLogOnInfo tbloginfo in tlInfo) {
tbloginfo.ConnectionInfo = ciReportConnection;
}
}


crystalReportViewer1.ReportSource = report;
crystalReportViewer1.Refresh();

Monday, 8 August 2011

Invoke a custom method when Crystal Report Viewers’ print button is clicked / Add custom button to Crystal Report Viewer Toolbar

If you are developing applications which includes reporting with Crystal Reports, you may have noticed that it’s not possible to invoke a custom method, when the user prints the report. However this was something which was easily implemented in Crystal Reports 8/8.5 but removed from latter versions.

But there’s a workaround for this. In this example I will show you how to invoke a method in our client application, when the print button of the report viewer is clicked.

In order to do that we have to add our custom method to the report viewers’ print buttons’ print action.

Create a new windows application.

Add another form to the project and name it as ‘CustomReportViewer.cs’.

Add a Crystal Report viewer to a newly created form. (If the crystal report viewer is not available in the toolbox, please add it to the toolbox first)

img_scr_001_a

img_scr_002

Add new report to the project and name it as ‘SampleReport.rpt’.

img_scr_003

Now add the following code to the ‘CustomReportViewer’ class

public delegate void CustomPrintDelegate();


Add the following property.


public Delegate CustomPrintMethod { get; set; }


Add this additional code to the initialization method.


foreach (Control control in crystalReportViewer1.Controls) {
if (control is System.Windows.Forms.ToolStrip) {

//Default Print Button
ToolStripItem tsItem = ((ToolStrip)control).Items[1];
tsItem.Click += new EventHandler(tsItem_Click);

//Custom Button
ToolStripItem tsNewItem = ((ToolStrip)control).Items.Add("");
tsNewItem.ToolTipText = "Custom Print Button";
tsNewItem.Image = Resources.CustomButton;
tsNewItem.Tag = "99";
((ToolStrip)control).Items.Insert(0, tsNewItem);
tsNewItem.Click += new EventHandler(tsNewItem_Click);
}
}


Using the above coding we can find out the print button of the report viewers’ too strip. And the 1st item is for the print button. (I have found this out from its ToolTipText).


How ever you can add your own button if you like or you can use the existing print button. Both options are illustrated.


Add the following methods.



void tsNewItem_Click(object sender, EventArgs e) {
if (CustomPrintMethod != null) {
CustomPrintMethod.DynamicInvoke(null);
}
}

void tsItem_Click(object sender, EventArgs e) {
if (CustomPrintMethod != null) {
CustomPrintMethod.DynamicInvoke(null);
}
}


Here is the complete coding of the ‘CustomReportViewer’ class.


using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;

namespace PrintDelegateMethod {
public partial class CustomReportViewer : Form {

public delegate void CustomPrintDelegate();

public Delegate CustomPrintMethod { get; set; }

public CustomReportViewer() {
InitializeComponent();

foreach (Control control in crystalReportViewer1.Controls) {
if (control is System.Windows.Forms.ToolStrip) {

//Default Print Button
ToolStripItem tsItem = ((ToolStrip)control).Items[1];
tsItem.Click += new EventHandler(tsItem_Click);

//Custom Button
ToolStripItem tsNewItem = ((ToolStrip)control).Items.Add("");
tsNewItem.ToolTipText = "Custom Print Button";
tsNewItem.Image = Resources.CustomButton;
tsNewItem.Tag = "99";
((ToolStrip)control).Items.Insert(0, tsNewItem);
tsNewItem.Click += new EventHandler(tsNewItem_Click);
}
}
}

void tsNewItem_Click(object sender, EventArgs e) {
if (CustomPrintMethod != null) {
CustomPrintMethod.DynamicInvoke(null);
}
}

void tsItem_Click(object sender, EventArgs e) {
if (CustomPrintMethod != null) {
CustomPrintMethod.DynamicInvoke(null);
}
}

private void CustomReportViewer_Load(object sender, EventArgs e) {
SampleReport report = new SampleReport();
crystalReportViewer1.ReportSource = report;
crystalReportViewer1.Refresh();
}
}
}


 


Add the following delegate to your calling class


public delegate void PrintDelegate();


Add the following method. This is the method that we want to invoke when the print button or the custom button is clicked.


private void CustomPrintMethod() {
MessageBox.Show("Custom Print Method");
}


And a button and the following click event code.


private void button1_Click(object sender, EventArgs e) {
CustomReportViewer viewer = new CustomReportViewer();
PrintDelegate mymethod = new PrintDelegate(CustomPrintMethod);
viewer.CustomPrintMethod = mymethod;
viewer.Show();

}


The complete source of the calling form:


using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace PrintDelegateMethod {
public partial class Form1 : Form {
public delegate void PrintDelegate();

public Form1() {
InitializeComponent();

}

private void CustomPrintMethod() {
MessageBox.Show("Custom Print Method");
}


private void button1_Click(object sender, EventArgs e) {
CustomReportViewer viewer = new CustomReportViewer();
PrintDelegate mymethod = new PrintDelegate(CustomPrintMethod);
viewer.CustomPrintMethod = mymethod;
viewer.Show();

}


}
}


Now if you run the project, you can get a similar screen shown below. And please note that I have added a resource file named ‘Resources’ and added an image named ‘CustomButton’

img_scr_011_a

And if you click either of the buttons, your custom method will be invoked. The default print method will be executed only when the print button is clicked.

img_scr_012

Friday, 5 August 2011

How to insert data using SQL Views created using multiple tables

A view can be defined as a virtual table or a stored query and the data accessible through a view is not stored in the database as a distinct object. Only the select statement is stored on the database instead.

How ever views can be used and perform DML operations (Insert, Update & Delete) also.

Consider the following two tables.

CREATE TABLE STUDENT(
STD_ID INT,
STD_FNAME VARCHAR(20),
STD_LNAME VARCHAR(20)
)


CREATE TABLE STUDENT_PAYMENT(
STD_ID INT,
PAY_AMT MONEY,
PAY_DATE DATETIME
)



Now create the following views.


CREATE VIEW VW_STUDENT
AS
SELECT
STD_ID,
STD_FNAME,
STD_LNAME
FROM
STUDENT


CREATE VIEW VW_STUDENT_PAYMENT
AS
SELECT
STD_ID,
PAY_AMT,
PAY_DATE
FROM
STUDENT_PAYMENT


You can insert data to the above tables using the views we have just created. And it is the same syntax that we use to insert data to tables.


INSERT INTO VW_STUDENT
SELECT 1,'Peter','Parker' UNION
SELECT 2,'James', 'Watson'


INSERT INTO VW_STUDENT_PAYMENT
SELECT 1,1000,'01/01/2011' UNION
SELECT 1,1100,'01/02/2011' UNION
SELECT 1,1200,'01/03/2011' UNION
SELECT 1,1250,'01/04/2011' UNION
SELECT 1,1375,'01/05/2011' UNION
SELECT 2,750,'01/03/2011' UNION
SELECT 2,850,'01/04/2011' UNION
SELECT 2,950,'01/05/2011'

And if you query the tables you can see that the records have inserted correctly.

img_scr_009


Now we will create the following view. This time we will join two tables and create a somewhat complex query.


CREATE VIEW VW_LAST_PAYMENT_DETAILS AS
WITH CTE_STD (STD_ID,MAX_PAYDATE) AS (
SELECT SP.STD_ID, MAX(SP.PAY_DATE) AS MAX_PAYDATE
FROM STUDENT_PAYMENT AS SP
GROUP BY SP.STD_ID
)
SELECT S.STD_ID,S.STD_FNAME,S.STD_LNAME, P.PAY_AMT,P.PAY_DATE
FROM STUDENT AS S
JOIN STUDENT_PAYMENT AS P ON S.STD_ID = P.STD_ID
JOIN CTE_STD AS Q ON P.STD_ID = Q.STD_ID AND P.PAY_DATE = Q.MAX_PAYDATE
GROUP BY S.STD_ID,S.STD_FNAME,S.STD_LNAME, P.PAY_AMT,P.PAY_DATE


Using the above created view we can list the last payment details of each student.


img_scr_004


So if we required to insert last payment details using this view how shall we do it ? If you use the simple insert statements similar to the ones, we used earlier, you have could ended up with the following error.


INSERT INTO VW_LAST_PAYMENT_DETAILS (STD_ID,PAY_AMT,PAY_DATE)
SELECT 1,4440,GETDATE()


img_scr_007


In order to insert (update & delete) data to views created using multiple tables, you need to use an ‘Instead of trigger’.


**Please note that ‘After Triggers’ cannot be created for views.


Let’s create an instead of trigger using the following syntax.


CREATE TRIGGER TRGI_VW_PAYMENT ON VW_LAST_PAYMENT_DETAILS
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO STUDENT_PAYMENT
SELECT STD_ID,PAY_AMT,PAY_DATE
FROM INSERTED
END


Now using the above insert syntax, you can insert data without getting any error. If you inspect the ‘STUDENT_PAYMENT’ table you can see that the data  has been inserted successfully.


img_scr_010

Sunday, 31 July 2011

Using MERGE statement in SQL Server 2008

‘MERGE’ statement is a new feature in SQL Server 2008. It can be used to perform insert, update and delete operation on a destination table simultaneously based on the results of a join with a source table. Well, it sounds like a bit confusing, but let's see an example on how it can help us.

Assume we have following two tables.

  • STUDENT_A
  • STUDENT_B

Both table are identical in structure (Structure does not need to be identical).

STUDENT_A

img_scr_001

 

STUDENT_B

img_scr_002

 

And we have to update the ‘STUDENT_A’ with the details at ‘STUDENT_B’. We need to compare and if student ID’s are matched, ‘A’ table should be updated with the ‘B’ table. And if the ID’s in ‘B’ Table are new then we have to insert those to the ‘A’ table.

img_scr_003

 

So using the ‘MERGE’ statement we can achieve this in one execution.

Syntax:

MERGE  <Target> [AS T]
USING <Source> [AS S]
ON <Condition>
[WHEN MATCHED THEN <Execution>]
[WHEN NOT MATCHED BY TARGET <Execution>]
[WHEN NOT MATCHED BY SOURCE <Execution>]


And to do the above operation use the following code:


MERGE STUDENT_A AS T
USING STUDENT_B AS S
ON T.ID = S.ID
WHEN MATCHED THEN UPDATE SET T.AGE = S.AGE
WHEN NOT MATCHED THEN INSERT (ID, FNAME, LNAME, AGE) VALUES(S.ID,S.FNAME,S.LNAME,S.AGE);



**Please note that semicolon ‘;’ is mandatory.


So after executing the above code, and if you inspect the Table ‘A’, you can see that it’s updated the way we wanted.


img_scr_005



 


And also you can use additional rules other than your condition. To illustrate that, first we insert a record to both the tables.


insert into STUDENT_A 
select 10, 'John','Doe',30

insert into STUDENT_B
select 10, 'John','Doe',30



And using the following code you can remove the record with matches the condition and have the value 10.


MERGE STUDENT_A AS T
USING STUDENT_B AS S
ON T.ID = S.ID
WHEN MATCHED and S.ID < 5 THEN UPDATE SET T.AGE = S.AGE
WHEN MATCHED and S.ID = 10 THEN DELETE
WHEN NOT MATCHED BY TARGET THEN INSERT (ID, FNAME, LNAME, AGE) VALUES(S.ID,S.FNAME,S.LNAME,S.AGE);



And if you inspect the table A, you can see that it has the same following results:


img_scr_005

Thursday, 28 July 2011

How to Use Update Cursors in SQL Server

There can be a situation where you have to use a cursor, even though the experts say not to use cursors or to avoid them as much as possible. But if you look closely, most of the time we use cursors to iterate through a row collection and update the same table.

In these type of situations it is ideal to use a Update Cursor, than using the default read only one.

Consider the following table :

CREATE TABLE [dbo].[SAMPLE_EMPLOYEE](
[EMP_ID] [int] NOT NULL,
[RANDOM_GEN_NO] [VARCHAR](50) NULL
) ON [PRIMARY]


Insert few records to the above table using the following script :


SET NOCOUNT ON
DECLARE @REC_ID AS INT

SET @REC_ID = 1

WHILE (@REC_ID <= 1000)
BEGIN
INSERT INTO SAMPLE_EMPLOYEE
SELECT @REC_ID,NULL

IF(@REC_ID <= 1000)
BEGIN
SET @REC_ID = @REC_ID + 1
CONTINUE
END

ELSE
BEGIN
BREAK
END
END
SET NOCOUNT OFF


Next we will add a Primary Key using the below script (Or you can use the table designer) :


ALTER TABLE [dbo].[SAMPLE_EMPLOYEE] ADD  CONSTRAINT [PK_SAMPLE_EMPLOYEE] PRIMARY KEY CLUSTERED 
(
[EMP_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]


** Please note: A primary key should be there if we are to use an update cursor. Otherwise the cursor will be read only.


Here is how you use the Update Cursor. I have highlighted the areas which is differ from compared with a normal cursor. You have to mention which column you are going to update (or all columns in your selection will be updatable) and you have to use ‘where current of <cursor>’ in your update statement.


img_scr_001_a


SET NOCOUNT ON
DECLARE
@EMP_ID AS INT,
@RANDOM_GEN_NO AS VARCHAR(50),
@TEMP AS VARCHAR(50)



DECLARE EMP_CURSOR CURSOR FOR
SELECT EMP_ID, RANDOM_GEN_NO FROM SAMPLE_EMPLOYEE FOR UPDATE OF RANDOM_GEN_NO
OPEN EMP_CURSOR
FETCH NEXT FROM EMP_CURSOR
INTO @EMP_ID, @RANDOM_GEN_NO

WHILE (@@FETCH_STATUS = 0)
BEGIN
SELECT @TEMP = FLOOR(RAND()*10000000000000)
UPDATE SAMPLE_EMPLOYEE SET RANDOM_GEN_NO = @TEMP WHERE CURRENT OF EMP_CURSOR

FETCH NEXT FROM EMP_CURSOR
INTO @EMP_ID, @RANDOM_GEN_NO
END

CLOSE EMP_CURSOR
DEALLOCATE EMP_CURSOR

SET NOCOUNT OFF

Tuesday, 26 July 2011

Fixed ~ Error installing SQL 2008 Business Intelligence Studio and Related Features

 

 

If you try to install/add feature SQL 2008 Business Intelligence Studio, to a machine with VS2008 having installed to a different location than the default, you will most probably get the following error :

img_screen_01

In order to install BI Studio 2008 properly, please do the following :

Find the folder which contains 'devenv.exe' (I have installed Visual Studio in 'D:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE')

Copy and paste the 'devenv.exe.config' file to the following location:

64-Bit
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE

32-Bit
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE

And now you proceed with the installation without any issue.

After installation, copy everything from 'C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE' (where you copied the 'devenv.exe.config') and paste it to the directory where you have VS2008 (In my machine it's  'D:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE')

And if you run Visual Studio 2008 you can see that the Business Intelligence Projects are now available.

img_screen_02_a

Sometimes you will be prompted to locate the 'devenv.exe' when opening the Business Intelligence Development Studio. Please adjust the shortcut or you can use the 'Visual Studio 2008' IDE.

img_screen_03

Thursday, 21 July 2011

[Resolved] - AdventureWorks2008 sample database not available after installation

 

Many of you who have tried installing AdventureWorks2008 sample database, might have come across with an issue, which after installing the database package (using the executable), 'AdventureWorks2008' is missing from the database list. This is not a bug in SQL server or any error in the installer.
 
img_screen_04_a
 
In order to get the 'AdventureWorks2008' installed following should be fulfilled:
 
1.          Full-text search should be installed and running
 
img_screen_01_a
 
2.          FILESTREAM should be enabled and running
 
img_screen_03_a
 
Enabling FILESTREAM:

  1. Go to SQL Server Configuration Manager
  2. Click on SQL Server Services (left pane)
  3. Right click on the correct SQL Server Service (if you have more than one instance)
  4. Select properties from the menu
  5. Go to FILESTREAM tab and check the ‘Enable FILESTREAM for Transact-SQL access’ check box.
  6. Apply changes.

And if you still don’t see it in your database list, please reinstall the package.

img_screen_05_a

Thursday, 14 July 2011

Extension Methods in C#

                                            
As name implies extension methods allow existing classes to be extended without using inheritance or changing class’s source code.

Some important points when using extension methods:

•    You cannot use extension methods to override existing methods
•    An extension method with the same name and signature as an instance method must not be called
•    The concept must not be applied to fields, properties or events
•    Do not overuse

As an example, we’ll create an extension method to existing ‘String’ class. We’ll implement the functionality of converting a string to a proper case. (Any given string will be converted to lower case, having the first letter of each word in upper case. E.g.: ‘this is sample text’ will be converted to ‘This Is Sample Text’)

Add a class and add the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace ExtensionMethods {
public static class SampleExtensionMethods {
public static string ToProper(this string zSource) {
StringBuilder sb = new StringBuilder();

string zTemp = zSource.ToLower();
List<string> data = zTemp.Split(new char[] { ' ' }).ToList<string>();
if (data.Count > 0) {
foreach (string element in data) {
if (element.Trim().Length > 0) {
sb.Append(element.Trim().Substring(0, 1).ToUpper() + element.Trim().Substring(1, element.Trim().Length - 1) + " ");
}
}
}

return sb.ToString().Trim();
}
}
}



please note that the class that the class should be static and the method should be public (Unless you are writing it within the same calss which you intend to use it). And also in input the parameter I have mentioned 'this'. This will tell the compiler to add the following method to the 'String' class.


Before using include the namspace. (I have used 'ExtensionMethods' as the namespace)


using ExtensionMethods;


And you can use the method we created using this syntax :


SomeStringVar.ToProper()


E.g:


using System;
using System.Text;
using ExtensionMethods;

namespace SampleConsoleApplicationA {
class SampleExtMethod {
static void Main(string[] args) {
string zSample = "this is sample text";
Console.WriteLine("Input : " + zSample);
Console.WriteLine("Output : " + zSample.ToProper());
Console.ReadLine();


}
}
}



And if you run the application you can get the below output (I have used a console application to illustrate this)


Extension Method

Delegates in C#

                                            
What's a delegate

A delegate is a type that safely wraps/encapsulate a  method. It is similar to 'function pointers' in C/C++, but it's type safe.

 

When to use a delegate

Use a delegate when :

  • An eventing design pattern is used.
  • It is desirable to encapsulate a static method.
  • The caller has no need access other properties, methods, or interfaces on the object implementing the method.
  • Easy composition is desired.
  • A class may need more than one implementation of the method.

There was a good example given on a book that I recently referenced.
'Consider your will—your last will and testament. It’s a set of instructions—“pay the bills, make a donation to charity, leave the rest of my estate to the cat,” for instance. You write it before your death, and leave it in an appropriately safe place. After your death, your attorney will (you hope!) act on those instructions.'

In order to a delegate to proceed, following four things need to happen:
* The delegate type needs to be declared.
* There must be a method containing the code to execute.
* A delegate instance must be created.
* The delegate instance must be invoked.

Declaring delegate type
Declaration of a delegate type specifies what kind of action can be represented by instances of the type.

E.g :

delegate void MySampleDelegate(string zParameter)



If we are creating an instance of the above delegate, we should have a method with one string parameter and void return type.



Method containing the code to execute
There should be a method which matches the signature of the delegate. Method can be either static or non static. I will take two examples to illustrate a static method and a non static method. I will use the following two methods in the 'DelegateClass' class.


E.g :


public class DelegateClass {
public static void MySampleDelegateMethodStatic(string zParameter) {
Console.WriteLine("From static method. Parameter : {0}", zParameter);
}

void MySampleDelegateMethodNonStatic(string zParameter) {
Console.WriteLine("From non static method. Parameter : {0}", zParameter);
}
}



Creating a delegate instance
When creating delegate instances static methods can  be passed directly. But for non static methods an instance should be created and passed that to the delegate.


E.g :    [Static Method]


MySampleDelegate delMethodA = new MySampleDelegate(DelegateClass.MySampleDelegateMethodStatic);


E.g :    [Non Static Method]


DelegateClass deligateClass = new DelegateClass();
MySampleDelegate delMethodB = new MySampleDelegate(deligateClass.MySampleDelegateMethodNonStatic);


Or


MySampleDelegate delMethodB = new MySampleDelegate(new DelegateClass().MySampleDelegateMethodNonStatic);


Invoking delegate method
It's a matter of calling the Invoke method on the delegate instance.


E.g :


delMethodA.Invoke("Parameter A");
delMethodB.Invoke("Parameter B");



Here is the complete source code for the above example. I have used a console application to illustrate this :


using System;
using System.Text;


namespace SampleConsoleApplicationA {

delegate void MySampleDelegate(string zParameter);

class Program {
static void Main(string[] args) {

MySampleDelegate delMethodA = new MySampleDelegate(DelegateClass.MySampleDelegateMethodStatic);
MySampleDelegate delMethodB = new MySampleDelegate(new DelegateClass().MySampleDelegateMethodNonStatic);

delMethodA.Invoke("Parameter A");
delMethodB.Invoke("Parameter B");

Console.ReadLine();
}
}

public class DelegateClass {
public static void MySampleDelegateMethodStatic(string zParameter) {
Console.WriteLine("From static method. Parameter : {0}", zParameter);
}

public void MySampleDelegateMethodNonStatic(string zParameter) {
Console.WriteLine("From non static method. Parameter : {0}", zParameter);
}
}
}






And when you run your application you will get the following output:


Delegate Sample


Instead of using separate delegate instances, you can combine and execute delegates. We have to use the 'Combine' method in Delegate class. To illustrate this I will alter my 'DelegateClass' as shown below :


public class DelegateClass {

public void MySampleMethodA(string zParameter) {
Console.WriteLine("MySampleMethodA. Parameter : {0}", zParameter);
}

public void MySampleMethodB(string zParameter) {
Console.WriteLine("MySampleMethodB. Parameter : {0}", zParameter);
}

public void MySampleMethodC(string zParameter) {
Console.WriteLine("MySampleMethodC. Parameter : {0}", zParameter);
}

}



To create instances and invoke :



MySampleDelegate[] mySampleDelegates = new MySampleDelegate[]{
new MySampleDelegate(new DelegateClass().MySampleMethodA),
new MySampleDelegate(new DelegateClass().MySampleMethodB),
new MySampleDelegate(new DelegateClass().MySampleMethodC)
};

MySampleDelegate sample = (MySampleDelegate)Delegate.Combine(mySampleDelegates);
sample.Invoke("Parameter X");



Or

MySampleDelegate D, deligateA, deligateB, deligateC;

deligateA = new DelegateClass().MySampleMethodA;
deligateB = new DelegateClass().MySampleMethodB;
deligateC = new DelegateClass().MySampleMethodC;

D = deligateA + deligateB + deligateC;

D("Parameter X");



 


The complete source code :


using System;
using System.Text;


namespace SampleConsoleApplicationA {

delegate void MySampleDelegate(string zParameter);

class Program {
static void Main(string[] args) {

MySampleDelegate[] mySampleDelegates = new MySampleDelegate[]{
new MySampleDelegate(new DelegateClass().MySampleMethodA),
new MySampleDelegate(new DelegateClass().MySampleMethodB),
new MySampleDelegate(new DelegateClass().MySampleMethodC)
};

MySampleDelegate sample = (MySampleDelegate)Delegate.Combine(mySampleDelegates);
sample.Invoke("Parameter X");


/*
MySampleDelegate D, deligateA, deligateB, deligateC;

deligateA = new DelegateClass().MySampleMethodA;
deligateB = new DelegateClass().MySampleMethodB;
deligateC = new DelegateClass().MySampleMethodC;

D = deligateA + deligateB + deligateC;

D("Parameter X");

*/

Console.ReadLine();
}
}

public class DelegateClass {

public void MySampleMethodA(string zParameter) {
Console.WriteLine("MySampleMethodA. Parameter : {0}", zParameter);
}

public void MySampleMethodB(string zParameter) {
Console.WriteLine("MySampleMethodB. Parameter : {0}", zParameter);
}

public void MySampleMethodC(string zParameter) {
Console.WriteLine("MySampleMethodC. Parameter : {0}", zParameter);
}

}
}



 


An when you run the application, you will get the following output:


Delegate Sample B

Wednesday, 4 May 2011

Fixed: SharePoint Designer Error - Unexpected error on server associating the workflow

 

When publishing a SPD workflow which contains more than 6 Approval process steps, it gives the following error :

screen_01

How ever if you try 5-6 times, you might end up publishing it. But sometimes it won’t work.

Even though Microsoft had mentioned that their 'February 2011 CU for the SharePoint 2010 Product Family' would fix most of the errors, this was not included among them.

But there’s a work around to fix the above issue.

Open the ‘web.config’ which associate with the site collection that you are publishing the workflow. (E.g: ‘C:\inetpub\wwwroot\wss\VirtualDirectories\<Port>’)

Find the following entry :

<httpRuntime maxRequestLength="51200" />


And change it to :


<httpRuntime maxRequestLength="51200" executionTimeout="300" />



Save the ‘web.config’ file and restart the web site from IIS.


Now you can publish the workflow without any issue.