1. Trang chủ
  2. » Công Nghệ Thông Tin

Microsoft SQL Server 2008 R2 Unleashed- P207 pps

10 46 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 180,25 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Inside the method body of a managed trigger, you need to get a reference to the execu-tion context of the trigger so you can find out what Data Manipulaexecu-tion Language DML statement

Trang 1

IsNullIfEmpty—Tells SQL Server that the UDA will return null if its aggregated

value is empty (that is, if its value is 0, or the empty string ””, and so on)

Name—Tells the deployment routine what to call the UDA when it is created in

the database

MaxByteSize—Tells SQL Server not to allow more than the specified number of

bytes to be held in an instance of the UDA You must specify this when using

Format.UserDefined

For this example, you implement a very simple UDA that sums values in an integer

column, but only if they are prime Listing 46.8 shows the code to do this

LISTING 46.8 A UDA That Sums Prime Numbers

using System;

using System.Data;

using System.Data.Sql;

using System.Data.SqlTypes;

using Microsoft.SqlServer.Server;

[Serializable]

[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(

Format.Native,

IsInvariantToDuplicates=false,

IsInvariantToNulls=true,

IsInvariantToOrder=true,

IsNullIfEmpty=true

)]

public struct SumPrime

{

SqlInt64 Sum;

private bool IsPrime(SqlInt64 Number)

{

for (int i = 2; i < Number; i++)

{

if (Number % i == 0)

{

return false;

}

}

return true;

}

public void Init()

{

Sum = 0;

Trang 2

}

public void Accumulate(SqlInt64 Value)

{

if (!Value.IsNull && IsPrime(Value) && Value > 1)

Sum += Value;

}

public void Merge(SumPrime Prime)

{

Sum += Prime.Sum;

}

public SqlInt64 Terminate()

{

return Sum;

}

}

In this code, SQL Server first calls Init(), initializing the private Sumdata field to 0

For each column value passed to the aggregate, the Accumulate()method is called,

whereinSumis increased by the value of the column, if it is prime

When multiple threads converge, Merge()is called, adding the values stored in each

instance (as the Primeparameter) to Sum

When the rowset has been completely parsed, SQL Server calls Terminate(), wherein the

accumulated value Sumis returned

Following are the results of testing SumPrimeonProduction.Product(an existing

AdventureWorks2008table):

SELECT TOP 10 dbo.SumPrime(p.ProductId) AS PrimeSum, p.Name

FROM Production.Product p

JOIN Production.WorkOrder o ON

o.ProductId = p.ProductId

WHERE Name LIKE ‘%Frame%’

GROUP BY p.ProductId, p.Name

ORDER BY PrimeSum DESC

go

PrimeSum Name

-360355 HL Mountain Frame - Black, 42

338462 HL Mountain Frame - Silver, 42

266030 HL Road Frame - Red, 48

214784 HL Road Frame - Black, 48

Trang 3

133937 HL Touring Frame - Yellow, 46

68338 LL Road Frame - Red, 52

54221 LL Mountain Frame - Silver, 48

15393 ML Road Frame - Red, 52

0 HL Mountain Frame - Black, 38

0 HL Road Frame - Black, 44

(10 row(s) affected.)

Following is the DDL syntax for this UDA:

CREATE AGGREGATE SumPrime(@Number bigint)

RETURNS bigint

EXTERNAL NAME SQLCLR.SumPrime

As with UDTs, with UDAs there is no ALTER AGGREGATE, but you can use DROP AGGREGATE

to drop them

Developing Managed Triggers

Managed triggers are static methods of a NET class decorated with the SqlTrigger

attribute.SqlTriggerhas three named parameters:

Event—A required string-valued parameter that tells SQL Server which type of trigger

you’re defining, as is done when defining T-SQL triggers

Target—A required string-valued parameter that tells SQL Server which schema and

table you’re attaching the trigger to

Name—An optional string parameter that tells the deployment routine what to call the

trigger when it is created in the database

The implementation contract for a managed trigger is only that it be a static method that

returnsvoid

Inside the method body of a managed trigger, you need to get a reference to the

execu-tion context of the trigger so you can find out what Data Manipulaexecu-tion Language (DML)

statement the trigger is responding to and which columns have been updated You do

this by using theSqlContext.TriggerContextobject of typeSqlTriggerContext (Note

that this object is null when used in nontrigger contexts.) It has the following members:

ColumnCount—An integer property that indicates how many columns were affected

by the operation

IsUpdatedColumn—A Boolean method that indicates whether the column at a

specific position was updated during the operation

TriggerAction—An enum that indicates which operation caused the trigger to fire

For DML triggers, this is either TriggerAction.Insert,TriggerAction.Update, or

TriggerAction.Delete For DDL triggers, the list is quite a bit longer Refer to MSDN

to see all the possible values of the TriggerActionenumeration

Trang 4

EventData—In the case of a DDL trigger, an object of type SqlXmlthat contains an XML

document whose content explains the DDL that just fired (The XML content model for

this object is the same as that returned by the EVENTDATA()built-in function.)

Have you ever wanted to be notified by email that some important column value in your

tables has been created or updated? There are many ways to do this, including using

Query Notifications You can also accomplish this by writing a managed trigger that calls a

web service, which in turn sends an email

Up until now, you haven’t had to decrease the runtime safety of your assembly But because

certain aspects of web services use theSynchronizedattribute (which means they do thread

synchronization), we have to change our SQLCLR assembly’s permission set toUNSAFE

CAUTION

Only the sysadminrole can upload an UNSAFEassembly to SQL Server You should

allow this uploading only when you know the code being uploaded doesn’t do anything

that might compromise the integrity of the data, the server, or your job

First, you need to create a simple web service routine that sends your email To do this

using Visual Studio 2008, you create a new local IIS website called photoserveand add to

it a new web service called PhotoService.asmx Then you replace the entire body of

PhotoService.cswith the following C# code:

using System;

using System.Web.Services;

using System.Net.Mail;

using System.Configuration;

[WebService(Namespace = “urn:www-samspublishing-com:examples:sqlclr:triggers”)]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

public class PhotoService : System.Web.Services.WebService

{

[WebMethod]

public void PhotoUpdateNotify(int ProductPhotoId)

{

MailMessage m = new MailMessage();

m.Subject = “New Photo: “ + ProductPhotoId.ToString();

m.From = new MailAddress(“ProductPhotoService@localservername”);

m.Body = “http://localhost:1347/photoserve/getphoto.aspx?ppid=” +

ProductPhotoId.ToString();

m.To.Add(new MailAddress(“PhotoAdmin@ localservername “));

SmtpClient s = new SmtpClient(“localservername”, 25);

s.Send(m);

}

}

Trang 5

Of course, you need to have SMTP and IIS correctly configured on your server for this

example to work completely You also need to replace localhostandlocalservername

and the email account names shown in the code with values that work for you

Next, you should add a new web form called getphoto.aspxto the site You replace the

entire contents of getphoto.aspx.cswith the code in Listing 46.9

LISTING 46.9 A Web Form That Retrieves Photos from SQL Server

using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Data.SqlClient;

using System.IO;

public partial class getphoto : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

if (Request.QueryString[“ppid”] != null)

{

string ppid = Request.QueryString[“ppid”].ToString();

string FileName = “photos/” + ppid + “.jpeg”;

string MappedFileName = Server.MapPath(FileName);

using (SqlConnection c =

new SqlConnection(

“Data Source=(local);Initial Catalog=AdventureWorks2008;

Integrated Security=True”

)

)

{

using (SqlCommand s = new SqlCommand(

@”SELECT LargePhoto

FROM Production.ProductPhoto

WHERE ProductPhotoId = “ + ppid, c))

{

c.Open();

using (SqlDataAdapter a = new SqlDataAdapter(s))

{

using (DataSet d = new DataSet())

{

a.Fill(d);

if (d.Tables.Count == 1 && d.Tables[0].Rows.Count == 1)

{

Trang 6

byte[] BigImg = (byte[])d.Tables[0].Rows[0][“LargePhoto”];

FileStream f = new FileStream(

MappedFileName, FileMode.Create, FileAccess.Write);

f.Write(BigImg, 0, BigImg.GetUpperBound(0));

f.Close();

Response.Redirect(FileName, false);

}

else

{

Response.Write(“<H2>Sorry, ProductPhotoId “ + ppid + “ was not found.</H2>”);

}

}

}

}

}

}

else

{

Response.Write(“<H2>A querystring value for ppid is required.</H2>”);

}

}

}

Next, you add a subfolder to the site called photos This is the place where the web form

will save product photos as JPEG files and redirect the email recipient The main body of

the code in Listing 46.9 illustrates how to save LOB values to file in a succinct manner, so

it may prove useful for your other applications

You either need to give your ASP.NET user file I/O permissions on photosor have the web

application impersonate a user who has those permissions

To recap, the website code so far consists of the following: a web service

(PhotoService.asmx) that generates notification emails containing URLs These URLs in

turn point to a web form (getphoto.aspx) that saves the varbinaryvalue of

Production.ProductPhoto.LargePhoto(given a particular ProductPhotoId) to the photos

folder as [ProductPhotoId].jpeg

The last item you need is the reason you’re writing this code in the first place: a managed

trigger that invokes the web service to kick off the whole process To add this, you

right-click the SQLCLR project and then select Add, Trigger Name this new trigger class

Triggers.cs(the default) Then replace the entire content of Triggers.cswith the code

in Listing 46.10

Trang 7

LISTING 46.10 A Managed Trigger That Invokes a Web Service

using System;

using System.Data;

using Microsoft.SqlServer.Server;

using System.Data.SqlClient;

using SQLCLR.photoserve;

public partial class Triggers

{

[Microsoft.SqlServer.Server.SqlTrigger(

Event = “FOR UPDATE”,

Name = “Production.PhotoUpdateTrigger”,

Target = “Production.ProductPhoto”

)]

public static void PhotoUpdateTrigger()

{

SqlTriggerContext stc = SqlContext.TriggerContext;

if (stc.TriggerAction == TriggerAction.Update)

{

if (stc.IsUpdatedColumn(3)) //The LargePhoto varbinary(max) column

{

using (SqlCommand s = new SqlCommand(

“SELECT DISTINCT ProductPhotoId FROM INSERTED”,

new SqlConnection(“context connection=true”)))

{

s.Connection.Open();

using (SqlDataReader r =

s.ExecuteReader(CommandBehavior.CloseConnection))

{

PhotoService p = new PhotoService();

while (r.Read())

{

SqlContext.Pipe.Send(

“Notifying Web Service of Update for PPID: “ + r.GetInt32(0).ToString());

p.PhotoUpdateNotify(r.GetInt32(0));

}

}

}

}

}

}

}

Trang 8

Now that all the code is in place, all that’s left is an explanation of the code of

PhotoUpdateTrigger()and a test case

In the code in Listing 46.10, you check to see whether the current TriggerActionis

TriggerAction.Update, meaning that the trigger is firing due to an update You declare

this to be trueby using the Eventnamed parameter of the SqlTriggerattribute

Next, you select the ProductPhotoIdof the updated row from the INSERTEDtable and

connect to the database by using the context connection

You execute the command and get your SqlDataReader(r); then you instantiate the

PhotoServiceweb service Using the overloaded method of the Pipeobject, you send a

string literal informational message (equivalent to T-SQL’s printfunction), which tells any

clients what is about to happen You call the PhotoUpdateNotifymethod of the web

service and pass in the ProductPhotoId, which in turn sends the email containing the link

back to getphoto.aspx, which generates the photo JPEG for that ProductPhotoId

To make the test case work, you need to make your local machine’s Network Service user a

SQL Server login and a user in AdventureWorks2008with at least db_datareaderaccess In

addition, you might need to use the Visual Studio sgen.exetool to create a serialization

assembly for SQL2008SQLCLR.dll(whichsgen.exewould, by default, name

SQL2008SQLCLR.XmlSerializers.dll)

You need to load this serialization assembly into AdventureWorks2008before loading the

main assembly (using CREATE ASSEMBLY) (At the time of this writing, it was necessary to

also load System.Web.dlland its dependencies into AdventureWorks2008before loading

the application assemblies.)

To test the trigger, you simply update a value of Production.ProductPhoto.LargePhoto:

UPDATE Production.ProductPhoto

SET LargePhoto = LargePhoto

WHERE ProductPhotoId = 69

go

Notifying Web Service of Update for PPID: 69

(1 row(s) affected.)

If you get an email in your test inbox, you’ve done everything right If not, don’t fret; this

is a challenging example developed mainly to show the power of managed code

Using Transactions

When you are writing managed objects, just as with T-SQL, it’s important to be aware of the

current transaction context under which your code may be running

Managed database objects have the option of making use of the classes in the new

System.Transactionsnamespace to control transactions Following are the main objects

you use to do this:

Transaction.Current—This is a static object of type Transactionthat represents the

current transaction You use this object to explicitly roll back the current transaction

Trang 9

(usingRollback()) It contains an IsolationLevelproperty that indicates the

current transaction isolation level, as well as a TransactionCompletedevent that

your objects may subscribe to and a TransactionInformationproperty that indicates

TransactionStatusand other attributes of the transaction You can also use this

object to manually enlist additional objects in the current transaction

TransactionScope—This object represents a transactional scope that is used to wrap

managed code Note that transactions automatically roll back unless they are

explic-itly committed using this object’sComplete()method It is enough to merely

instantiate this object at the beginning of the managed code: If a current transaction

is active, the instantiated object assumes that transaction; if not, a new transaction

is initiated

Note that it is not necessary to explicitly declare or even use transactions: if your managed

code is already running in the scope of a transaction, it automatically participates in that

transaction (To turn off this behavior, you append ”enlist=false”to your connection

string.) In fact, even if your code opens additional connections on additional servers, the

transaction context is not only preserved but is automatically promoted to a distributed

transaction that enlists all the connections involved (The MSDTC service must be

running for distributed transactions to work.)

One thing you cannot do with managed transactions that you can with T-SQL is begin a

new transaction and then just leave it open

The code example in Listing 46.11 illustrates the use of theSystem.Transactionsobjects

in a managed stored procedure You need to add a new managed stored procedure to the

SQLCLR project and call itSPTrans Then you need to add theusingstatementusing

System.Transactions;and replace the autogenerated method with the code from

Listing 46.11

LISTING 46.11 Using Transactions in a Managed Stored Procedure

[SqlProcedure]

public static void SpTrans()

{

TransactionScope ts = null;

try

{

SqlContext.Pipe.Send(“Proc Started”);

if (Transaction.Current != null)

{

SqlContext.Pipe.Send(“A) Current tran is not null.”);

SqlContext.Pipe.Send(“A) About to rollback current tran ”);

Transaction.Current.Rollback(

new ApplicationException(“I wanted to do this.”));

SqlContext.Pipe.Send(“A) Rollback Complete.”);

}

else

Trang 10

{

SqlContext.Pipe.Send(“A) Current tran is null.”);

}

ts = new System.Transactions.TransactionScope();

SqlContext.Pipe.Send(“New Tran Started”);

if (Transaction.Current != null)

SqlContext.Pipe.Send(“B) Current tran is not null.”);

else

SqlContext.Pipe.Send(“B) Current tran is null.”);

if (ts != null)

ts.Complete();

SqlContext.Pipe.Send(“B) Complete() is Complete.”);

}

finally

{

if (ts != null)

ts.Dispose();

SqlContext.Pipe.Send(“Proc Complete”);

}

}

To test this code, you simply run the stored procedure from a query window (or use

sqlcmd.exe) inside and outside a transactional scope and watch the results Here’s an

example:

BEGIN TRAN

EXEC dbo.SpTrans

ROLLBACK TRAN

EXEC dbo.SPTrans

Using the Related System Catalogs

As with other database objects, SQL Server provides catalog views that enable you to view

loaded managed assemblies, routines, and types The base view for finding these objects is

sys.assemblies

To see which assemblies have been loaded (including the one you created in this

chapter), you use the following query:

SELECT TOP 5

name,

assembly_id,

permission_set_desc as permission_set

FROM sys.assemblies

Ngày đăng: 05/07/2014, 02:20

TỪ KHÓA LIÊN QUAN