Entity Framework Code First Concurrency Control - entity-framework-4.3

There
I’m using the EF code first to persist the data by following the sequence: filter => remove => add, run the attached sample(two threads run conrrrently), sometime I noticed there was no existing record return after the filtering, sometime there are two records after the filtering, what I thought/expected is - every time the filter should have one existing record return. Also, there are some exceptions raised up during saving the change.
As I known, EF by default uses the Read Committed isolation level to execute the transaction, I think that means during the filtering, the shared lock is put on the resource, but why I can observe that there is not existing record or two existing records after the filtering, the remove and add operation together should be an atomic operation, right? If I’m right, there should be only and just one record after filtering.
Is there anything I missed? How to handle this case correctly?
Please help.
Another question:
Use the LastUpdated column as the concurrency token, how to the following case correctly:
1. If the entity in the database is newer than the entity in the context, start another thread to archive the entity to the history database.
2. If the entity in the database is old than the entity in the context, retry the saving to overwrite the entity in the database.
Am i right to use the code below to handle the case:
internal void SaveChangesEx()
{
bool saveFailed;
do
{
saveFailed = false;
try
{
base.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = false;
ex.Entries.ToList().ForEach(entry =>
{
if (entry.State == System.Data.EntityState.Deleted)
{
var current = base.Set<Customer>().FirstOrDefault();
Customer rfqInDb = (Customer)entry.GetDatabaseValues();
// If the newer one aready exists, start the archive, otherwise, retry by reloading the entity from DB and marking the state as deleted
if (current.LastUpdated < customerInDb.LastUpdated)
{
using (var archiver = new CustomerArchiveDBContext())
{
archiver.RFQS.Add(current);
archiver.SaveChangesEx();
}
saveFailed = false;
}
else
{
//Refresh the context and retry
entry.Reload();
entry.State = System.Data.EntityState.Deleted;
}
}
});
}
} while (saveFailed);
}
Script: ========
CREATE TABLE [dbo].[Customers](
[FirstName] [nvarchar](20) NOT NULL,
[LastName] [nvarchar](60) NULL,
[Company] [nvarchar](250) NULL,
[Telephone] [nvarchar](20) NULL,
[LastUpdated] [datetime] NULL
) ON [PRIMARY]
Code ========
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.Data.Entity.Validation;
using System.Threading.Tasks;
using System.Threading;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace EFOptimisticConcurrency
{
public abstract class Entity
{
public int Id { get; set; }
}
public class Customer
: Entity
{
public string FirstName { get; set; }
/// <summary>
/// Get or set the surname of this customer
/// </summary>
public string LastName { get; set; }
/// <summary>
/// Get or set the telephone
/// </summary>
public string Telephone { get; set; }
/// <summary>
/// Get or set the company name
/// </summary>
public string Company { get; set; }
public DateTime LastUpdated { get; set; }
}
class CustomerEntityConfiguration
: EntityTypeConfiguration<Customer>
{
/// <summary>
/// Create a new instance of customer entity configuration
/// </summary>
public CustomerEntityConfiguration()
{
//configure keys and properties
this.HasKey(c => c.FirstName);
this.Ignore(c => c.Id);
this.Property(c => c.FirstName)
.HasMaxLength(20)
.IsRequired();
this.Property(c => c.LastName)
.HasMaxLength(60)
.IsRequired();
this.Property(c => c.Company)
.HasMaxLength(250);
//this.Property(c => c.LastUpdated).IsConcurrencyToken();
this.Property(c => c.Telephone)
.HasMaxLength(20);
this.ToTable("Customers");
}
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Configurations.Add(new CustomerEntityConfiguration()); ;
}
}
public class Program
{
public static volatile int showStopper = 0;
static void Main(string[] args)
{
var color = Console.ForegroundColor;
EntityFrameworkProfiler.Initialize();
Task.Factory.StartNew(() =>
{
while (true)
{
Customer customer = new Customer();
customer.FirstName = "FirstName";
customer.LastName = "Last " + new Random().Next(0, 10000).ToString();
customer.Telephone = "686868";
customer.Company = "MyCity";
customer.LastUpdated = DateTime.Now;
if (showStopper == 2)
{
Console.ReadLine();
showStopper = 0;
}
try
{
Console.WriteLine("Start the Store => " + customer.LastName + " , " + customer.LastUpdated.ToString());
{
int i = 0;
using (var customerConext = new CustomerContext())
{
Console.WriteLine("Start the filter 1 => " + customer.Telephone + " , " + customer.LastUpdated.ToString());
var matched = customerConext.Customers.Where(c => c.Telephone == "686868" && c.LastUpdated < customer.LastUpdated);
foreach (var hit in matched)
{
i++;
customerConext.Customers.Remove(hit);
}
if (i == 2)
{
Console.WriteLine("1 - 2 exist, has the problem now");
showStopper = 2;
}
else if (i == 0)
{
Console.WriteLine("1 - 0 exist, has the problem now");
showStopper = 2;
}
Console.WriteLine("Start Adding 1 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
try
{
customerConext.Customers.Add(customer);
customerConext.SaveChanges();
Console.WriteLine("SaveChanges 1 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
}
catch (Exception ex)
{
Console.WriteLine("Exception 1 : " + ex.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
if (ex.InnerException != null)
{
Console.WriteLine("Inner Exception 2 : " + ex.InnerException.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception 1 " + ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message);
}
showStopper = 2;
}
}
});
Thread.Sleep(10000);
Task.Factory.StartNew(() =>
{
while (true)
{
Console.ForegroundColor = color;
try
{
Customer customer = new Customer();
customer.FirstName = "FirstName";
customer.LastName = "Last " + new Random().Next(0, 10000).ToString();
customer.Telephone = "686868";
customer.Company = "MyCity2";
customer.LastUpdated = DateTime.Now;
if (showStopper == 3)
{
Console.ReadLine();
showStopper = 0;
}
Console.WriteLine("Start the store 2 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
{
int i = 0;
using (var customerConext = new CustomerContext())
{
Console.WriteLine("Start the filter 2 => " + customer.Telephone + " , " + customer.LastUpdated.ToString());
var matched = customerConext.Customers.Where(c => c.Telephone == "686868" && c.LastUpdated < customer.LastUpdated);
foreach (var hit in matched)
{
i++;
customerConext.Customers.Remove(hit);
}
Console.WriteLine("Start Adding 2 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
try
{
customerConext.Customers.Add(customer);
customerConext.SaveChanges();
Console.WriteLine("SaveChanges 2 => " + customer.LastName + " , " + customer.LastUpdated.ToString());
}
catch (Exception ex)
{
Console.WriteLine("Exception 2 : " + ex.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
if (ex.InnerException != null)
{
Console.WriteLine("Inner Exception 2 : " + ex.InnerException.Message + " => " + customer.LastName + " , " + customer.LastUpdated);
}
showStopper = 2;
}
}
if (i == 2)
{
Console.WriteLine("1 - 2 exist, has the problem now");
showStopper = 2;
}
else if (i == 0)
{
Console.WriteLine("1 - 0 exist, has the problem now");
showStopper = 2;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Exception 2 " + ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message);
}
}
}
});
Console.WriteLine("PRESS ANY KEY TO END");
Console.ReadLine();
}
}
}

Related

How to delete alarge amount of data one by one from a table with their relations using transactional annotation

I have a large amount of data that I want to purge from the database, there are about 6 tables of which 3 have a many to many relationship with cascadeType. All the others are log and history tables independent of the 3 others
i want to purge this data one by one and if any of them have error while deleting i have to undo only the current record and show it in console and keep deleting the others
I am trying to use transactional annotation with springboot but all purging stops if an error occurs
how to manage this kind of need?
here is what i did :
#Transactional
private void purgeCards(List<CardEntity> cardsTobePurge) {
List<Long> nextCardsNumberToUpdate = getNextCardsWhichWillNotBePurge(cardsTobePurge);
TransactionTemplate lTransTemplate = new TransactionTemplate(transactionManager);
lTransTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
lTransTemplate.execute(new TransactionCallback<Object>() {
#Override
public Object doInTransaction(TransactionStatus status) {
cardsTobePurge.forEach(cardTobePurge -> {
Long nextCardNumberOfCurrent = cardTobePurge.getNextCard();
if (nextCardsNumberToUpdate.contains(nextCardNumberOfCurrent)) {
CardEntity cardToUnlik = cardRepository.findByCardNumber(nextCardNumberOfCurrent);
unLink(cardToUnlik);
}
log.info(BATCH_TITLE + " Removing card Number : " + cardTobePurge.getCardNumber() + " with Id : "
+ cardTobePurge.getId());
List<CardHistoryEntity> historyEntitiesOfThisCard = cardHistoryRepository.findByCard(cardTobePurge);
List<LogCreationCardEntity> logCreationEntitiesForThisCard = logCreationCardRepository
.findByCardNumber(cardTobePurge.getCardNumber());
List<LogCustomerMergeEntity> logCustomerMergeEntitiesForThisCard = logCustomerMergeRepository
.findByCard(cardTobePurge);
cardHistoryRepository.deleteAll(historyEntitiesOfThisCard);
logCreationCardRepository.deleteAll(logCreationEntitiesForThisCard);
logCustomerMergeRepository.deleteAll(logCustomerMergeEntitiesForThisCard);
cardRepository.delete(cardTobePurge);
});
return Boolean.TRUE;
}
});
}
As a solution to my question:
I worked with TransactionTemplate to be able to manage transactions manually
so if an exception is raised a rollback will only be applied for the current iteration and will continue to process other cards
private void purgeCards(List<CardEntity> cardsTobePurge) {
int[] counter = { 0 }; //to simulate the exception
List<Long> nextCardsNumberToUpdate = findNextCardsWhichWillNotBePurge(cardsTobePurge);
cardsTobePurge.forEach(cardTobePurge -> {
Long nextCardNumberOfCurrent = cardTobePurge.getNextCard();
CardEntity cardToUnlik = null;
counter[0]++; //to simulate the exception
if (nextCardsNumberToUpdate.contains(nextCardNumberOfCurrent)) {
cardToUnlik = cardRepository.findByCardNumber(nextCardNumberOfCurrent);
}
purgeCard(cardTobePurge, nextCardsNumberToUpdate, cardToUnlik, counter);
});
}
private void purgeCard(#NonNull CardEntity cardToPurge, List<Long> nextCardsNumberToUpdate, CardEntity cardToUnlik,
int[] counter) {
TransactionTemplate lTransTemplate = new TransactionTemplate(transactionManager);
lTransTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
lTransTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
public void doInTransactionWithoutResult(TransactionStatus status) {
try {
if (cardToUnlik != null)
unLink(cardToUnlik);
log.info(BATCH_TITLE + " Removing card Number : " + cardToPurge.getCardNumber() + " with Id : "
+ cardToPurge.getId());
List<CardHistoryEntity> historyEntitiesOfThisCard = cardHistoryRepository.findByCard(cardToPurge);
List<LogCreationCardEntity> logCreationEntitiesForThisCard = logCreationCardRepository
.findByCardNumber(cardToPurge.getCardNumber());
List<LogCustomerMergeEntity> logCustomerMergeEntitiesForThisCard = logCustomerMergeRepository
.findByCard(cardToPurge);
cardHistoryRepository.deleteAll(historyEntitiesOfThisCard);
logCreationCardRepository.deleteAll(logCreationEntitiesForThisCard);
logCustomerMergeRepository.deleteAll(logCustomerMergeEntitiesForThisCard);
cardRepository.delete(cardToPurge);
if (counter[0] == 2)//to simulate the exception
throw new Exception();//to simulate the exception
} catch (Exception e) {
status.setRollbackOnly();
if (cardToPurge != null)
log.error(BATCH_TITLE + " Problem with card Number : " + cardToPurge.getCardNumber()
+ " with Id : " + cardToPurge.getId(), e);
else
log.error(BATCH_TITLE + "Card entity is null", e);
}
}
});
}

Web Crawling with Spring Batch

I have a crawl function that also checks whether the content contains the param. If it contains I will write that to the database. How can I use the following code as a Read Job for the spring batch?
public void crawl(String baseUrl, String url, String postgresParam) {
if (!urls.contains(url) && url.startsWith(baseUrl)) {
// System.out.println(">> count: " + count + " [" + url + "]");
urls.add(url);
try {
Connection connection = Jsoup.connect(url).userAgent(USER_AGENT);
Document htmlDocument = connection.get();
Elements linksOnPage = htmlDocument.select("a[href]");
bodyContent = htmlDocument.body().text();
String title = htmlDocument.title();
searchParameters(url, title);
// count++;
for (Element link : linksOnPage) {
crawl(baseUrl, link.absUrl("href"), postgresParam);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void searchParameters(String URL, String title) {
for (String param : postgresParamArray) {
if (bodyContent.toLowerCase().contains(param.toLowerCase())) {
System.out.println(">>>>>> Found: " + " [" + param + "]" + " [" + URL + "]" + " [" + title + "]");
}
}
}

BackgroundService memory leak SqlDependency

this is my service code
public class Worker : BackgroundService
{
public bool isRegister { get; set; }
public bool checkIp { get; set; }
public long timePass { get; set; }
public Worker()
{
}
public override Task StartAsync(CancellationToken cancellationToken)
{
return base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (isRegister == false)
registerSelect();
if (checkIp == true)
{
checkIp = false;
await SecurityConfig.cacheServices?.BuildServiceProvider()?.GetService<IBlockFirewallIpService>().RegisterInFirewall();
}
timePass += 1000;
if (timePass % 60000 == 0)
await SecurityConfig.cacheServices?.BuildServiceProvider()?.GetService<IBlockFirewallIpService>().RegisterInFirewall();
await Task.Delay(1000, stoppingToken);
}
}
public void registerSelect()
{
isRegister = true;
using (SqlConnection conn = new SqlConnection(GetDbConnection()))
{
conn.Open();
SqlDependency.Start(GetDbConnection());
string commandText = "SELECT [Ip1],[Ip2] ,[Ip3] ,[Ip4] FROM dbo.BlockFirewallIps where IsRead is null";
using (SqlCommand cmd = new SqlCommand(commandText, conn))
{
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(OnDependencyChange);
cmd.ExecuteNonQuery();
}
conn.Close();
}
}
void OnDependencyChange(object sender, SqlNotificationEventArgs e)
{
if (e.Info == SqlNotificationInfo.Insert)
checkIp = true;
SqlDependency temp = sender as SqlDependency;
if (temp != null)
temp.OnChange -= new OnChangeEventHandler(OnDependencyChange);
registerSelect();
}
private string GetDbConnection()
{
return GlobalConfig.Configuration["ConnectionStrings:DefaultConnection"];
}
}
and this is my IBlockFirewallIpService.RegisterInFirewall() code
public async Task RegisterInFirewall()
{
var allBlockIps = await db.BlockFirewallIps.Where(t => t.IsRead == null).ToListAsync();
foreach (var ip in allBlockIps)
{
BanIP("OjeFirTCP" + ip.Ip1 + "_" + ip.Ip2 + "_" + ip.Ip3 + "_" + ip.Ip4, ip.Ip1 + "." + ip.Ip2 + "." + ip.Ip3 + "." + ip.Ip4, "Any", "TCP");
BanIP("OjeFirUDP" + ip.Ip1 + "_" + ip.Ip2 + "_" + ip.Ip3 + "_" + ip.Ip4, ip.Ip1 + "." + ip.Ip2 + "." + ip.Ip3 + "." + ip.Ip4, "Any", "UDP");
ip.IsRead = true;
db.SaveChanges();
}
}
void BanIP(string RuleName, string IPAddress, string Port, string Protocol)
{
if (OperatingSystem.IsWindows())
{
if (!string.IsNullOrEmpty(RuleName) && !string.IsNullOrEmpty(IPAddress) && !string.IsNullOrEmpty(Port) && !string.IsNullOrEmpty(Protocol) && new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
{
using (Process RunCmd = new Process())
{
RunCmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
RunCmd.StartInfo.FileName = "cmd.exe";
RunCmd.StartInfo.Arguments = "/C netsh advfirewall firewall add rule name=\"" + RuleName + "\" dir=in action=block remoteip=" + IPAddress + " remoteport=" + Port + " protocol=" + Protocol;
RunCmd.Start();
}
}
}
}
this is progeram.cs
IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "OjeFirewall";
})
.ConfigureServices((hostContext, services) =>
{
GlobalConfig.Configuration = hostContext.Configuration;
services.AddScoped<IHttpContextAccessor, FakeIHttpContextAccessor>();
SecurityConfig.Config(services);
services.AddHostedService<Worker>();
})
.Build();
await host.RunAsync();
this is SecurityConfig.Config codes
services.AddDbContext<SecurityDBContext>(options =>
options.UseSqlServer(GlobalConfig.Configuration["ConnectionStrings:DefaultConnection"],
b => b.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
, ServiceLifetime.Singleton
);
services.AddSingleton<IIpLimitationWhiteListService, IpLimitationWhiteListService>();
services.AddSingleton<IIpLimitationBlackListService, IpLimitationBlackListService>();
services.AddSingleton<IFileAccessRoleService, FileAccessRoleService>();
services.AddSingleton<IRoleService, RoleService>();
services.AddSingleton<IBlockClientConfigService, BlockClientConfigService>();
services.AddSingleton<IBlockAutoIpService, BlockAutoIpService>();
services.AddSingleton<IBlockFirewallIpService, BlockFirewallIpService>();
the problem :
this code using too much memory after 3 day
starting ram usage is 20mb after first call (OnDependencyChange) it use 47mb
after 3 day it use 178mb
where i am doing wrong ?!
i found problem
await SecurityConfig.cacheServices?.BuildServiceProvider()?.GetService<IBlockFirewallIpService>().RegisterInFirewall();
this line code was the problem after change it to normal injection ram usage is stable now, but i can not understand whay !?

pull images from twitter API on processing 3

I am quite new to this so please bear with me. I am trying to use a keyword search to pull images from twitter using twitter4j. I want it to show the images positioned randomly on the screen in a loop.
This code I have below is a combination of different ones I have found online. Its currently finding tweets using these keywords and showing them in the console, however, it is not displaying them on the screen and I’m not sure why…
Another thing is that I think it is doing a live stream from twitter so pulling tweets immediately at the time the code is run, so I am not getting lots of results when I put in an obscure keyword, I want it to search for the last 100 tweets with images using the keyword so that I get more results
I’d really appreciate all the help I can get! thank you
///////////////////////////// Config your setup here! ////////////////////////////
// { site, parse token }
String imageService[][] = {
{
"http://twitpic.com",
"<img class=\"photo\" id=\"photo-display\" src=\""
},
{
"http://twitpic.com",
"<img class=\"photo\" id=\"photo-display\" src=\""
},
{
"http://img.ly",
"<img alt=\"\" id=\"the-image\" src=\""
},
{
"http://lockerz.com/",
"<img id=\"photo\" src=\""
},
{
"http://instagr.am/",
"<meta property=\"og:image\" content=\""
} {
"http://pic.twitter.com",
"<img id
};
// This is where you enter your Oauth info
static String OAuthConsumerKey = KEY_HERE;
static String OAuthConsumerSecret = SECRET_HERE;
static String AccessToken = TOKEN_HERE;
static String AccessTokenSecret = TOKEN_SECRET_HERE;
cb.setIncludeEntitiesEnabled(true);
Twitter twitterInstance = new TwitterFactory(cb.build()).getInstance();
// if you enter keywords here it will filter, otherwise it will sample
String keywords[] = {
"#hashtag" //sample keyword!!!!!!!!
};
///////////////////////////// End Variable Config ////////////////////////////
TwitterStream twitter = new TwitterStreamFactory().getInstance();
PImage img;
boolean imageLoaded;
void setup() {
frameRate(10);
size(800, 600);
noStroke();
imageMode(CENTER);
connectTwitter();
twitter.addListener(listener);
if (keywords.length == 0) twitter.sample();
else twitter.filter(new FilterQuery().track(keywords));
}
void draw() {
background(0);
if (imageLoaded) image(img, width / 5, height / 5);
//image(loadImage((status.getUser().getImage())), (int)random(width*.45), height-(int)random(height*.4));
}
// Initial connection
void connectTwitter() {
twitter.setOAuthConsumer(OAuthConsumerKey, OAuthConsumerSecret);
AccessToken accessToken = loadAccessToken();
twitter.setOAuthAccessToken(accessToken);
}
// Loading up the access token
private static AccessToken loadAccessToken() {
return new AccessToken(AccessToken, AccessTokenSecret);
}
// This listens for new tweet
StatusListener listener = new StatusListener() {
//#Override
public void onStatus(Status status) {
System.out.println("#" + status.getUser().getScreenName() + " - " + status.getText());
}
//#Override
public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
System.out.println("Got a status deletion notice id:" + statusDeletionNotice.getStatusId());
}
//#Override
public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
System.out.println("Got track limitation notice:" + numberOfLimitedStatuses);
}
//#Override
public void onScrubGeo(long userId, long upToStatusId) {
System.out.println("Got scrub_geo event userId:" + userId + " upToStatusId:" + upToStatusId);
}
//#Override
public void onStallWarning(StallWarning warning) {
System.out.println("Got stall warning:" + warning);
}
//#Override
public void onException(Exception ex) {
ex.printStackTrace();
}
};
public void onStatus(Status status) {
String imgUrl = null;
String imgPage = null;
// Checks for images posted using twitter API
if (status.getMediaEntities() != null) {
imgUrl = status.getMediaEntities()[0].getMediaURL().toString();
}
// Checks for images posted using other APIs
else {
if (status.getURLEntities().length > 0) {
if (status.getURLEntities()[0].getExpandedURL() != null) {
imgPage = status.getURLEntities()[0].getExpandedURL().toString();
} else {
if (status.getURLEntities()[0].getDisplayURL() != null) {
imgPage = status.getURLEntities()[0].getDisplayURL().toString();
}
}
}
if (imgPage != null) imgUrl = parseTwitterImg(imgPage);
}
if (imgUrl != null) {
println("found image: " + imgUrl);
// hacks to make image load correctly
if (imgUrl.startsWith("//")) {
println("s3 weirdness");
imgUrl = "http:" + imgUrl;
}
if (!imgUrl.endsWith(".jpg")) {
byte[] imgBytes = loadBytes(imgUrl);
saveBytes("tempImage.jpg", imgBytes);
imgUrl = "tempImage.jpg";
}
println("loading " + imgUrl);
img = loadImage(imgUrl);
imageLoaded = true;
}
}
public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
System.out.println("Got a status deletion notice id:" + statusDeletionNotice.getStatusId());
}
public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
System.out.println("Got track limitation notice:" + numberOfLimitedStatuses);
}
public void onScrubGeo(long userId, long upToStatusId) {
System.out.println("Got scrub_geo event userId:" + userId + " upToStatusId:" + upToStatusId);
}
public void onException(Exception ex) {
ex.printStackTrace();
}
// Twitter doesn't recognize images from other sites as media, so must be parsed manually
// You can add more services at the top if something is missing
String parseTwitterImg(String pageUrl) {
for (int i = 0; i < imageService.length; i++) {
if (pageUrl.startsWith(imageService[i][0])) {
String fullPage = ""; // container for html
String lines[] = loadStrings(pageUrl); // load html into an array, then move to container
for (int j = 0; j < lines.length; j++) {
fullPage += lines[j] + "\n";
}
String[] pieces = split(fullPage, imageService[i][1]);
pieces = split(pieces[1], "\"");
return (pieces[0]);
}
}
return (null);
}

Bot Framework with LUIS - Issue with opening Form one after another

My bot is supposed to help delete appointment.
A prompt for user's nric will be done (in RetrieveAppt.cs)
Subsequently, if there is such user in my database, it should go on to prompt user to enter the apptId which he/she wants to delete (as there may be multiple appointments made by same person) (in DeleteAppt.cs)
Issue Description
Exception thrown: 'Microsoft.Bot.Builder.Internals.Fibers.InvalidNeedException' in Microsoft.Bot.Builder.dll
Code Example
RetrieveAppt.cs
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace Bot.Models
{
[Serializable]
public class RetrieveAppt
{
[Prompt("Please provide your NRIC:")]
public string Nric { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendFormat(Nric);
return builder.ToString();
}
}
}
DeleteAppt.cs
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace Bot.Models
{
[Serializable]
public class DeleteAppt
{
[Prompt("Please enter the appointment id that you wish to delete/cancel :")]
public string apptId { get; set; }
public override string ToString()
{
var builder = new StringBuilder();
builder.AppendFormat(apptId);
return builder.ToString();
}
}
}
ApptLuisDialog.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;
using Bot.Models;
using System.Data.SqlClient;
using System.Globalization;
namespace Bot.Dialogs
{
[LuisModel("I have my own key", "I have my own key")]
[Serializable]
class ApptLuisDialog : LuisDialog<ApptLuisDialog>
{
String sql = #"Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=Temp.DB; User Id = (insert your username here); Password = (insert your password here); Integrated Security=true;MultipleActiveResultSets = true";
private static IForm<RetrieveAppt> BuildRetrieveForm()
{
var builder = new FormBuilder<RetrieveAppt>();
return builder.AddRemainingFields().Build();
}
private static IForm<DeleteAppt> BuildDeleteForm()
{
var builder = new FormBuilder<DeleteAppt>();
return builder.AddRemainingFields().Build();
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
System.Diagnostics.Debug.WriteLine("Entered here: B");
await context.PostAsync("I'm sorry I don't understand you. However, I can help you to: \n\n" + "1) Retrieve Appointment \n\n" + "2) Create Appointment \n\n" + "3) Delete Appointment \n\n" + "4) Edit Appointment");
context.Wait(MessageReceived);
}
[LuisIntent("RetrieveAppointment")]
public async Task RetrieveAppointment(IDialogContext context, LuisResult result)
{
System.Diagnostics.Debug.WriteLine("Entered here: C");
var form = new RetrieveAppt();
var entities = new List<EntityRecommendation>(result.Entities);
var retrieveAppt = new FormDialog<RetrieveAppt>(form, BuildRetrieveForm, FormOptions.PromptInStart);
context.Call(retrieveAppt, RetrieveComplete);
}
private async Task RetrieveComplete(IDialogContext context, IAwaitable<RetrieveAppt> result)
{
RetrieveAppt appt = null;
try
{
appt = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("You cancelled the form!");
return;
}
if (appt != null)
{
//getting user's input value
String nric = appt.Nric.ToString();
List<string> apptInfo = new List<string>();
//Create connection
SqlConnection con = new SqlConnection(sql);
//SQL Command
SqlCommand cmd = new SqlCommand("SELECT * FROM Appointment a WHERE a.Nric ='" + nric + "'", con);
//Open sql connection
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
String date = dr["AptDate"].ToString();
String[] temp = date.Split(null);
apptInfo.Add("Appointment ID: " + dr["ApptId"].ToString() + "\n\n"
+ "Nric: " + dr["Nric"].ToString() + "\n\n"
+ "Date: " + temp[0] + "\n\n"
+ "Time: " + dr["AptStartTime"].ToString() + "\n\n"
+ "Location: " + dr["Location"].ToString() + "\n\n"
+ "Purpose: " + dr["Purpose"].ToString());
}
//Close sql connection
dr.Close();
con.Close();
if (apptInfo.Count == 0)
{
await context.PostAsync("You do not have an appointment/no such NRIC");
}
else
{
for (int i = 0; i < apptInfo.Count(); i++)
{
await context.PostAsync("Your Appointment Info is: " + "\n\n" + apptInfo[i]);
}
}
}
else
{
await context.PostAsync("Form returned empty response!");
}
context.Wait(MessageReceived);
}
[LuisIntent("DeleteAppointment")]
public async Task DeleteAppointment(IDialogContext context, LuisResult result)
{
System.Diagnostics.Debug.WriteLine("Entered here: A");
var form = new RetrieveAppt();
var retrieveAppt = new FormDialog<RetrieveAppt>(form, BuildRetrieveForm, FormOptions.PromptInStart);
context.Call(retrieveAppt, Delete);
}
private async Task Delete(IDialogContext context, IAwaitable<RetrieveAppt> result)
{
RetrieveAppt appt = null;
try
{
appt = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("You cancelled the form!");
return;
}
if (appt != null)
{
//getting user's input value
String nric = appt.Nric.ToString().ToUpper();
List<string> apptInfo = new List<string>();
//SqlAdapter for inserting new records
SqlDataAdapter sda = new SqlDataAdapter();
//Create connection
SqlConnection con = new SqlConnection(sql);
//SQL Command to check existing patient
SqlCommand cmd = new SqlCommand("SELECT * FROM Appointment a WHERE a.Nric ='" + nric + "'", con);
//Open sql connection
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
String date = dr["AptDate"].ToString();
String[] temp = date.Split(null);
apptInfo.Add("Appointment ID: " + dr["ApptId"].ToString() + "\n\n"
+ "Nric: " + dr["Nric"].ToString() + "\n\n"
+ "Date: " + temp[0] + "\n\n"
+ "Time: " + dr["AptStartTime"].ToString() + "\n\n"
+ "Location: " + dr["Location"].ToString() + "\n\n"
+ "Purpose: " + dr["Purpose"].ToString());
}
if (apptInfo.Count != 0)
{
**//this is the part that has error, i can't prompt for the appointment id that user wants to delete**
System.Diagnostics.Debug.WriteLine("Entered here: AA");
var form = new DeleteAppt();
var deleteAppt = new FormDialog<DeleteAppt>(form, BuildDeleteForm, FormOptions.PromptInStart);
context.Call(deleteAppt, DeleteComplete);
}
else
{
//Close sql connection
dr.Close();
con.Close();
await context.PostAsync("Invalid NRIC/No current appointment");
}
}
else
{
await context.PostAsync("Form returned empty response!");
}
context.Wait(MessageReceived);
}
private async Task DeleteComplete(IDialogContext context, IAwaitable<DeleteAppt> result)
{
DeleteAppt appt = null;
try
{
appt = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("You canceled the form!");
return;
}
if (appt != null)
{
//getting user's input value
String apptId = appt.apptId.ToString();
List<string> newApptInfo = new List<string>();
//SqlAdapter for inserting new records
SqlDataAdapter sda = new SqlDataAdapter();
//Create connection
SqlConnection con = new SqlConnection(sql);
//SQL Command to check existing patient
String cmd = "DELETE FROM Appointment a WHERE a.ApptId ='" + apptId + "'";
//Open sql connection
con.Open();
try
{
sda.InsertCommand = new SqlCommand(cmd, con);
sda.InsertCommand.ExecuteNonQuery();
//Close sql connection
con.Close();
await context.PostAsync("Appointment " + apptId + " cancelled successfully.");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception caught: " + ex);
}
}
else
{
await context.PostAsync("Form returned empty response!");
}
context.Wait(MessageReceived);
}
}
}
Expected Behavior
For example, after bot prompts user to input NRIC, user inputs "123456". So let's say, there are 3 appointments linked to NRIC "123456". So it will show all 3 appointments (with the following details: apptId, apptDate, apptTime, locatoin) first.
Next, I want the bot to prompt the user for the appointment that he/she wants to delete base on the apptId. (But this prompt is not showing)
Actual Results
Exception thrown: 'Microsoft.Bot.Builder.Internals.Fibers.InvalidNeedException' in Microsoft.Bot.Builder.dll
Help needed here definitely
adding a "return" statement would solve it.
When making the call to context.Call(deleteAppt, DeleteComplete); there should not follow a call to context.Wait(MessageReceived). So add a return statement after context.Call(deleteAppt, DeleteComplete);
if (apptInfo.Count != 0)
{
//this is the part that has error, i can't prompt for the appointment id that user wants to delete
System.Diagnostics.Debug.WriteLine("Entered here: AA");
var form = new DeleteAppt();
var deleteAppt = new FormDialog<DeleteAppt>(form, BuildDeleteForm, FormOptions.PromptInStart);
context.Call(deleteAppt, DeleteComplete);
return;
}

Resources