Introduction
Email Sender Utility is a .NET application that depicts the better use of SQLDependency in combination with XML for bulk updates, to send emails , detecting candidate email[s] automatically using SQL Server Broker Service, using XML for single DB round trip, Its way you make your application, aware of database changes. Like it fires the event "void OnDependencyChange(object sender, SqlNotificationEventArgs e)
" if any record found which satisfy WHERE CLAUSE . We can assossciate command object using WHERE CLAUSE with SQLDependency object.
Background
Lot of developers are trying to find a way to implement push model to avoid opening DB conenctions, Some are using timer to call periodically, SQL Dependency solves this problem.
Using the code
First need to start "SqlDependency
" object as show below. Below connectionString
is connection string of target database. It will throw exception, if SQL Broker Service is not enabled,
STEP #1- Start SqlDependency from C# code.
SqlDependency.Start(<TARGET_DB_CONNECTION_STRING>);
How to enable / disable broker service, helping T-SQL queries , below can be used.
To enable Service Broker run:
ALTER DATABASE [Database_name] SET ENABLE_BROKER;
If SQL Server broker service is not enabled, SQLDependency.start()
will throw exception, so Broker service is mandatory for SQLDependency auto change detection.
Step# 2 Initialize SQLDependency object. here is how to initialize sqldependency
SqlConnection SqlConnection =new SqlConnection(<TARGET_DB_CONNECTION_STRING>);
SqlConnection.Open();
SqlCommand command = new SqlCommand();
command.Connection = SqlConnection;
command.CommandType = CommandType.Text;
command.CommandText = " SELECT [PatientCode] ,[EmailAddress] , SentTime , NotificationStatus FROM [dbo].[EmailNotificationHistory] where [SentTime] is null ";
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);
command.ExecuteReader();
Note: For Above queries there is part of Stored procedures and schema script. Refer "Database Schema" Folder
STEP # 3 - Prepare some sample data, Sample one candidate email will be added from script. Reference "Data Scripts"
In code, smtpclient library is used for sending email.
string to = EmailEntity.RecipientEmailAddress;
string from = SmtpClientEntity.SenderEmailAddress;
MailMessage message = new MailMessage(from, to);
message.Subject = SmtpClientEntity.EmailSubject;
message.Body = EmailEntity.EmailBody;
System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient(SmtpClientEntity.SMTPAddress, Convert.ToInt16(SmtpClientEntity.Port));
System.Net.NetworkCredential basicCredential = new System.Net.NetworkCredential(SmtpClientEntity.UserName, SmtpClientEntity.Password);
client.UseDefaultCredentials = false;
client.Credentials = basicCredential;
try
{
client.Send(message);
EmailEntity.SentStatus = true;
}
catch (Exception ex)
{
AppendControlText(this.txtStatus, "Email sending process failed , Error" + ex.ToString() + " at " + DateTime.Now.ToString());
DbManager.LogFile(ex.Message, "SendAnEmail", this.FindForm().Text);
throw;
}
Sample Email Entity class for attributed data transfer to UI and Data Access layer, also from Data Access layer same entity is used to generate XML for bulk Update, in single database round trip.
class EmailEntity
{
public string CaseNumber { get; set; }
public string RecipientEmailAddress { get; set; }
public string PatientID { get; set; }
public string NotificationID { get; set; }
public string PatientName { get; set; }
public string PatientAge { get; set; }
public string EmailSubject { get; set; }
public string PatientStatus { get; set; }
public DateTime CaseDate { get; set; }
public object Attachment { get; set; }
public string EmailBody { get; set; }
public double Sender { get; set; }
public string PatientColorCode { get; set; }
public string Priority { get; set; }
public Boolean SentStatus { get; set; }
}
STEP # 4 For email , You need smtp configurations
smtpserver : <Mail Server SMTP address>
EmailUserName : <Sender Email user>
EmailPwd : <Sender Email password>
SenderEmailAddress : <Sender Email address>
SmtpServerPort :<SMTP Port>
EmailSubject : <Email Subject>
EmailBody <Email Body>
Need to udpate these settings, in table "[GeneralConfigurations
]" OR comment line of code where sending email
Finalization: Core Technical Areas
Using SqlDependency for detecting changes , once we assosciate our command with SqlDependency, database using broker service, automatically detect changes/ updates and fires OnDependencyChange
event.
To make isolated multiple Database operations, using ADO.NET distributed transaction using TransactionScope.
using (TransactionScope scope = new TransactionScope())
{
using (TransactionScope scope = new TransactionScope())
{
EmailEntityList = DbManager.GetCandidateForEmail();
foreach (EmailEntity EmailEntity in EmailEntityList)
{
if (SendAnEmail(EmailEntity))
{
AppendControlText(this.txtStatus,"Email Sent to " + EmailEntity.RecipientEmailAddress + " at " + DateTime.Now.ToString());
}
else {
AppendControlText(this.txtStatus, "Email sending process failed " + EmailEntity.RecipientEmailAddress + " at " + DateTime.Now.ToString());
}
}
if (EmailEntityList != null && EmailEntityList.Count > 0) {
DbManager.UpdateEmailSentStatus(EmailEntityList);
}
scope.Complete();
}
Step # 5 - To reduce round trips and avoid openning database connection multiple time, if required, we can use XML and LINQ, using LINQ make XML and pass to stored procedure, Code Reference Class DBManager "spUpdateEmailSentStatusAndArchiveXML
", Sample XML as output is as given below.
LINQ is used to generate XML for Database operations., Code snippet i
var xEle = new XElement("EmailList",
from emp in EmailList
select new XElement("EmailList",
new XElement("NotificationID", emp.NotificationID),
new XElement("RecipientEmailAddress", emp.RecipientEmailAddress),
new XElement("SentStatus", emp.SentStatus)
));
Out put of LINQ Query in XML
<EmailList>
<EmailList>
<NotificationID>10011</NotificationID>
<RecipientEmailAddress>xxxx@hot.com</RecipientEmailAddress>
<SentStatus>false</SentStatus>
</EmailList>
<EmailList>
<NotificationID>10012</NotificationID>
<RecipientEmailAddress>abc@hotmail.com</RecipientEmailAddress>
<SentStatus>false</SentStatus>
</EmailList>
</EmailList>
We can update all candidate records in round trip using XML. Sample TSQL code snippet as below. Folder "Stored Procedures"
ALTER PROC [dbo].[spUpdateEmailSentStatusAndArchiveXML](
@XML xml
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @EmailNotificationUpdate TABLE
(
NotificationID [bigint],
RecipientEmailAddress nvarchar(50),
SentStatus [bit] default(0),
[NeedArchive] int null ,
[SentTime] datetime null
)
Insert into @EmailNotificationUpdate(NotificationID,RecipientEmailAddress,SentStatus, [SentTime])
SELECT Emails.Contact.value('(./NotificationID)[1]','bigint') NotificationID
, Emails.Contact.value('(./RecipientEmailAddress)[1]', 'nvarchar(50)') RecipientEmailAddress
, Emails.Contact.value('(./SentStatus)[1]', 'bit') SentStatus
,Getdate() [SentTime]
FROM @XML.nodes('/EmailList/EmailList') AS Emails(Contact)
UPDATE ENH
SET
ENH.[SentTime] = Case when SentStatus = 1 then VENU.[SentTime] else ENH.[SentTime] end
,ENH.[NotificationStatus] = Case when SentStatus = 1 then 1 else ENH.[NotificationStatus] end
,ENH.[AuditActionCode] = Case when SentStatus = 1 then 3 else ENH.[AuditActionCode] end
,ENH.[IsActive] = Case when SentStatus = 1 then 0 else ENH.[IsActive] end
,ENH.TimesTryToSend = isnull(ENH.TimesTryToSend,0) +1
,[ModifiedOn] = getdate()
from [dbo].[EmailNotificationHistory] ENH inner join @EmailNotificationUpdate VENU on VENU.NotificationID = ENH.NotificationID
and ENH.EmailAddress = VENU.RecipientEmailAddress
where ENH.[SentTime] is null
END
Step # 6 - How to perform testing, there are two approaches.
1- Add one candidate row in table "EmailNotificationHistory
" , its value must need to fullfill WHERE CLAUSE criteria in query associated with Command object. below query need to select some record.
SELECT [PatientCode] ,[EmailAddress] , SentTime , NotificationStatus FROM [dbo].[EmailNotificationHistory] where [SentTime] is null
2- You can update manually in table EmailAddress
, SentTime
, PatientCode
all OR either, but SentTime
should not be null because where clause will ristrict it. If meets their criteria after update commit, This event will be automatically fired.
void OnDependencyChange(object sender, SqlNotificationEventArgs e){
}
Points of Interest
SQLDependency is enough helpfull for auto detecing changes from database, Developer need not to query periodically if there is some update for me, but SQLDependency using command detecting and updates back to through event fire
XML comes to help us perform mutliple record database operation in atomic. Although we can also use datatable for the same purpose as alternative.