Dapper .Net ExecuteNonQuery requires the command to have a transaction - c#

Following code to insert records, first I check to make sure the email address already exists in the DB if it does I return alerting the user otherwise I insert a new record.
using (var sqlCon = new SqlConnection(Context.ReturnDatabaseConnection()))
{
sqlCon.Open();
var emailExists = sqlCon.Query<UserProfile>(#"SELECT UserId FROM User_Profile WHERE EmailAddress = #EmailAddress",
new { EmailAddress = userRegister.EmailAddress.Trim() }).FirstOrDefault();
if (emailExists == null) // No profile exists with the email passed in, so insert the new user.
{
var userProfileEntity = new UserProfileEntity
{
UniqueId = Guid.NewGuid(),
Firstname = userRegister.Firstname,
Surname = userRegister.Surname,
EmailAddress = userRegister.EmailAddress,
Username = CreateUsername(userRegister.Firstname),
Password = EncryptPassword(userRegister.Password),
AcceptedTerms = true,
AcceptedTermsDate = System.DateTime.Now,
AccountActive = true,
CurrentlyOnline = true,
ClosedAccountDate = null,
JoinedDate = System.DateTime.Now
};
userProfile.UserId = SqlMapperExtensions.Insert(sqlCon, userProfileEntity); // Call the Dapper Extension method to insert the new record
userProfile.Firstname = userRegister.Firstname;
userProfile.Username = userProfile.Username;
userProfile.EmailAddress = userProfile.EmailAddress;
Registration.SendWelcomeEmail(userRegister.EmailAddress, userRegister.Firstname); // Send welcome email to new user.
}
}
This line calls the Dapper Extension Class
userProfile.UserId = SqlMapperExtensions.Insert(sqlCon, userProfileEntity); // Call the Dapper Extension method to insert the new record
Calls the following and it is here that I get the error
ExecuteNonQuery requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized
public static long Insert<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
using (var tx = connection.BeginTransaction())
{
var type = typeof(T);
var name = GetTableName(type);
var sb = new StringBuilder(null);
sb.AppendFormat("insert into {0} (", name);
var allProperties = TypePropertiesCache(type);
var keyProperties = KeyPropertiesCache(type);
for (var i = 0; i < allProperties.Count(); i++)
{
var property = allProperties.ElementAt(i);
if (keyProperties.Contains(property)) continue;
sb.Append(property.Name);
if (i < allProperties.Count() - 1)
sb.Append(", ");
}
sb.Append(") values (");
for (var i = 0; i < allProperties.Count(); i++)
{
var property = allProperties.ElementAt(i);
if (keyProperties.Contains(property)) continue;
sb.AppendFormat("#{0}", property.Name);
if (i < allProperties.Count() - 1)
sb.Append(", ");
}
sb.Append(") ");
// here is where the error occures vvvvvvvv
connection.Execute(sb.ToString(), entityToInsert, transaction: transaction, commandTimeout: commandTimeout);
//NOTE: would prefer to use IDENT_CURRENT('tablename') or IDENT_SCOPE but these are not available on SQLCE
var r = connection.Query("select ##IDENTITY id");
tx.Commit();
return (int)r.First().id;
}
}
Question is how do I fix this, I have googled and tried solutions but with no avail.
Thanks

Related

Why is PropertyDataCollection object persisting multiple records to database

I have a utility that reads the status of MicrosoftBizTalk Server resources .. specifically the ReceiveLocation component. My problem is that the program is submitting multiple entries of each item i.e each item in the data returned is being multiplied by 25 such that instead of persisting only 5 rows the data being persisted is 125. So for example instead of having just 1 row for my first row returned i have 25.
This is my program :
public List<BizTalk> GetBizTalkServicesStatistics()
{
List<BizTalk> model = new List<BizTalk>();
try
{
//Create the WMI search object.
ManagementObjectSearcher Searcher = new ManagementObjectSearcher();
ConnectionOptions options = new ConnectionOptions
{
Username = "+username+",
Password = "+password+",
Authority = "+domain+"
};
var server = "+server+";
// create the scope node so we can set the WMI root node correctly.
ManagementScope Scope = new ManagementScope("\\\\" + server + "\\root\\MicrosoftBizTalkServer", options);
Searcher.Scope = Scope;
// Build a Query to enumerate the MSBTS_ReceiveLocation instances if an argument
// is supplied use it to select only the matching RL.
//if (args.Length == 0)
SelectQuery Query = new SelectQuery();
Query.QueryString = "SELECT * FROM MSBTS_ReceiveLocation";
// else
//Query.QueryString = "SELECT * FROM MSBTS_ReceiveLocation WHERE Name = '" + args[0] + "'";
// Set the query for the searcher.
Searcher.Query = Query;
// Execute the query and determine if any results were obtained.
ManagementObjectCollection QueryCol = Searcher.Get();
// Use a bool to tell if we enter the for loop
// below because Count property is not supported
bool ReceiveLocationFound = false;
// Enumerate all properties.
foreach (ManagementBaseObject envVar in QueryCol)
{
// There is at least one Receive Location
ReceiveLocationFound = true;
PropertyDataCollection envVarProperties = envVar.Properties;
foreach (PropertyData envVarProperty in envVarProperties)
{
BizTalk bizTalk = new BizTalk();
bizTalk.Name = Convert.ToString(envVar["Name"]);
bizTalk.TransportType = Convert.ToString(envVar["AdapterName"]);
bizTalk.Uri = Convert.ToString(envVar["InboundTransportURL"]);
bizTalk.Status = Convert.ToString(envVar["Name"]);
bizTalk.ReceiveHandler = Convert.ToString(envVar["HostName"]);
bizTalk.ReceivePort = Convert.ToString(envVar["ReceivePortName"]);
bizTalk.RunDate = DateTime.Now;
bizTalk.ApplicationId = 24;
bizTalk.ServerId = 8;
bizTalk.InstanceName = "FBCZOP";
model.Add(bizTalk);
}
}
if (!ReceiveLocationFound)
{
Console.WriteLine("No receive locations found matching the specified name.");
}
}
catch (Exception excep)
{
ExceptionLogger.SendErrorToText(excep);
}
return model;
}
Save Function
public void SaveStatistics(BizTalk entity)
{
List<BizTalk> ServerInfo = new List<BizTalk>();
ServerInfo = GetBizTalkServicesStatistics();
foreach (var di in ServerInfo)
{
entity.RunDate = di.RunDate;
entity.Name = di.Name;
entity.Status = di.Status;
entity.Uri = di.Uri;
entity.InstanceName = di.InstanceName;
entity.ReceivePort = di.ReceivePort;
entity.TransportType= di.TransportType;
entity.RunDate = DateTime.Now;
entity.ReceiveHandler = di.ReceiveHandler;
entity.ServerId = entity.ServerId;
entity.ApplicationId = entity.ApplicationId;
appEntities.BizTalk.Add(entity);
appEntities.SaveChanges();
}
}
When i step through the code variable envVarProperties shows record count as 125 under envVarProperties << ResultsView :
Link 1
whilst QueryCol variable shows count of 5 :
Link 2
It looks like you're iterating an extra time in your GetBizTalkServicesStatistics() method.
Remove the foreach loop that starts with foreach (PropertyData envVarProperty in envVarProperties). This is looping through each property the object has (All 25 properties) for each instance (5 instances)... 25 * 5 = 125 values you are retrieving. You only want to iterate through your instances and pull the properties you want. That way you end up with 5 objects in your model object.
I'd suggest maybe something like this (untested because I don't have BizTalk)
public List<BizTalk> GetBizTalkServicesStatistics()
{
List<BizTalk> model = new List<BizTalk>();
try
{
//Create the WMI search object.
ConnectionOptions options = new ConnectionOptions
{
Username = "+username+",
Password = "+password+",
Authority = "+domain+"
};
var server = "+server+";
// create the scope node so we can set the WMI root node correctly.
ManagementScope Scope = new ManagementScope("\\\\" + server + "\\root\\MicrosoftBizTalkServer", options);
ManagementObjectSearcher Searcher = new ManagementObjectSearcher(Scope, new ObjectQuery("SELECT * FROM MSBTS_ReceiveLocation"));
// Enumerate all properties.
foreach (ManagementObject instance in Searcher.Get())
{
{
BizTalk bizTalk = new BizTalk();
bizTalk.Name = instance.Properties["Name"]?.Value?.ToString();
bizTalk.TransportType = instance.Properties["AdapterName"]?.Value?.ToString();
bizTalk.Uri = instance.Properties["InboundTransportURL"]?.Value?.ToString();
bizTalk.Status = instance.Properties["Name"]?.Value?.ToString();
bizTalk.ReceiveHandler = instance.Properties["HostName"]?.Value?.ToString();
bizTalk.ReceivePort = instance.Properties["ReceivePortName"]?.Value?.ToString();
bizTalk.RunDate = DateTime.Now;
bizTalk.ApplicationId = 24;
bizTalk.ServerId = 8;
bizTalk.InstanceName = "FBCZOP";
model.Add(bizTalk);
}
}
// Determine
if (model.Count == 0)
{
Console.WriteLine("No receive locations found matching the specified name.");
}
}
catch (Exception excep)
{
ExceptionLogger.SendErrorToText(excep);
}
return model;
}
Also, this can be simplified more if you remove the connectionoptions (unless you are hard coding credentials which is highly advised against). If you are just using the identity of the executing user, that data is not needed.
-Paul
You are adding the same entity 25 times and overwrite its properties by reference. You need to initialize a new entity inside your loop:
foreach (var di in ServerInfo)
{
var entity = new BizTalk();
entity.RunDate = di.RunDate;
entity.Name = di.Name;
entity.Status = di.Status;
entity.Uri = di.Uri;
entity.InstanceName = di.InstanceName;
entity.ReceivePort = di.ReceivePort;
entity.TransportType= di.TransportType;
entity.RunDate = DateTime.Now;
entity.ReceiveHandler = di.ReceiveHandler;
entity.ServerId = entity.ServerId;
entity.ApplicationId = entity.ApplicationId;
appEntities.BizTalk.Add(entity);
appEn.SaveChanges();
}
}
As you don't show the code where "SaveStatistics" is called it's not sure this will fix your complete problem, but it's at least one method that does not do what you expect it to do.

Query taking longer than expected odbc sage

I am doing a simple select with a date filter on it with a months range where only 32 records are present however its taking 15 seconds to query and return the data I am using sage 50 as you can probally tell and c#. I am using odbc to create the query the seem speeds can be found if i use the odbc query tool.
This is for a stright forward select and it should not be taking that long to return the data through odbc.
String SQL = string.Format("SELECT 'ORDER_NUMBER', 'ORDER_OR_QUOTE',
'ANALYSIS_1','ACCOUNT_REF','ORDER_DATE','NAME',
'COURIER_NUMBER','COURIER_NAME','CUST_TEL_NUMBER'
,'DESPATCH_DATE','ACCOUNT_REF', 'DEL_NAME', 'DEL_ADDRESS_1',
'DEL_ADDRESS_2', 'DEL_ADDRESS_3', 'DEL_ADDRESS_4', 'DEL_ADDRESS_5',
'INVOICE_NUMBER','ORDER_DATE','INVOICE_NUMBER_NUMERIC',
'CONTACT_NAME','CONSIGNMENT', 'NOTES_1', 'ITEMS_NET'
,'ITEMS_GROSS','QUOTE_STATUS' FROM SALES_ORDER WHERE ORDER_DATE
='{0}' and ORDER_DATE <='{1}'", fromD, toD);
public List<SalesOrders> GetSalesOrders()
{
List<SalesOrders> _salesOrdersList = new List<SalesOrders>();
try
{
string sageDsn = ConfigurationManager.AppSettings["SageDSN"];
string sageUsername = ConfigurationManager.AppSettings["SageUsername"];
string sagePassword = ConfigurationManager.AppSettings["SagePassword"];
//int totalRecords = GetSalesOrdersount();
int counter = 0;
//using (var connection = new OdbcConnection("DSN=SageLine50v24;Uid=Manager;Pwd=;"))
using (var connection = new OdbcConnection(String.Format("DSN={0};Uid={1};Pwd={2};", sageDsn, sageUsername, sagePassword)))
{
connection.Open();
//string sql = string.Format(getInvoiceSql, customerCode, DateTime.Today.AddMonths(-1).ToString("yyyy-MM-dd"));
string fromD = dtpFrom.Value.ToString("yyyy-MM-dd");
string toD = dtpTo.Value.ToString("yyyy-MM-dd");
String SQL = string.Format("SELECT 'ORDER_NUMBER', 'ORDER_OR_QUOTE', 'ANALYSIS_1','ACCOUNT_REF','ORDER_DATE','NAME', 'COURIER_NUMBER','COURIER_NAME','CUST_TEL_NUMBER' ,'DESPATCH_DATE','ACCOUNT_REF', 'DEL_NAME', 'DEL_ADDRESS_1', 'DEL_ADDRESS_2', 'DEL_ADDRESS_3', 'DEL_ADDRESS_4', 'DEL_ADDRESS_5', 'INVOICE_NUMBER','ORDER_DATE','INVOICE_NUMBER_NUMERIC', 'CONTACT_NAME','CONSIGNMENT', 'NOTES_1', 'ITEMS_NET' ,'ITEMS_GROSS','QUOTE_STATUS' FROM SALES_ORDER WHERE ORDER_DATE >='{0}' and ORDER_DATE <='{1}'", fromD, toD);
using (var command = new OdbcCommand(SQL, connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
counter++;
backgroundWorker1.ReportProgress(counter);
var salesOrders = new SalesOrders();
salesOrders.ACCOUNT_REF = Convert.ToString(reader["ACCOUNT_REF"]);
salesOrders.RecordIdentifier = "";
salesOrders.ShipmmentId = Convert.ToString(reader["ORDER_NUMBER"]);
salesOrders.OrderDate = Convert.ToDateTime(reader["ORDER_DATE"]);
salesOrders.OrderNumber = Convert.ToString(reader["ORDER_NUMBER"]);
salesOrders.Company = "";
salesOrders.Carrier = Convert.ToString(reader["COURIER_NUMBER"]);
salesOrders.CarrierService = Convert.ToString(reader["COURIER_NAME"]);
salesOrders.CustomerName = Convert.ToString(reader["NAME"]);
salesOrders.ShipToAddress1 = Convert.ToString(reader["DEL_ADDRESS_1"]);
salesOrders.ShipToAddress2 = Convert.ToString(reader["DEL_ADDRESS_2"]);
salesOrders.ShipToAddress3 = Convert.ToString(reader["DEL_ADDRESS_3"]);
salesOrders.ShipToAddress4 = Convert.ToString(reader["DEL_ADDRESS_4"]);
salesOrders.ShipToAddress5 = Convert.ToString(reader["DEL_ADDRESS_5"]);
salesOrders.ShiptoAttention = Convert.ToString(reader["DEL_NAME"]);
salesOrders.ShiptoPhoneNo = Convert.ToString(reader["CUST_TEL_NUMBER"]);
salesOrders.Country = Convert.ToString(reader["ANALYSIS_1"]);
salesOrders.ShiptoEmail = "";
salesOrders.MakeAddressDefault = "Y";
bool isProcessed = _sqlManager.hasbeenProcessed(salesOrders.OrderNumber);
if (isProcessed == true)
salesOrders.Exported = true;
_salesOrdersList.Add(salesOrders);
}
}
}
}
return _salesOrdersList.OrderByDescending(o => o.OrderDate).ToList();
}
don't use {0}, {1} for embedding values in strings... ADD via Parameters
String SQL =
#"SELECT
ORDER_NUMBER,
ORDER_OR_QUOTE,
ANALYSIS_1,
ACCOUNT_REF,
ORDER_DATE,
`NAME`,
COURIER_NUMBER,'
OURIER_NAME,
CUST_TEL_NUMBER,
DESPATCH_DATE,
ACCOUNT_REF,
DEL_NAME,
DEL_ADDRESS_1,
DEL_ADDRESS_2,
DEL_ADDRESS_3,
DEL_ADDRESS_4,
DEL_ADDRESS_5,
INVOICE_NUMBER,
ORDER_DATE,
INVOICE_NUMBER_NUMERIC,
CONTACT_NAME,
CONSIGNMENT,
NOTES_1,
ITEMS_NET,
ITEMS_GROSS,
QUOTE_STATUS
FROM
SALES_ORDER
WHERE
ORDER_DATE >= ?
and ORDER_DATE <= ?
ORDER BY
ORDER_DATE DESC";
using (var command = new OdbcCommand(SQL, connection))
{
// assuming fields are actually date data types fields
command.Parameters.Add( "parmFromDate", fromD );
command.Parameters.Add( "parmToDate", toD );
The "?" in the query are place-holders for the parameter values which are handled by the ODBC process. The Parameters being added within the using() portion are added in the same ordinal position as their respective place-holder parts. I just assigned the parameter name to give context to whoever is looking at it after.
The query itself SHOULD be very quick depending on the date range you are pulling. Even added the SQL Order by descending order so it is pre-pulled down in the order you intended it too.

Create seed data from sql script

I'm using ORM in my project. Currently seed data is taken from sql scripts but I would like to create seed data basing on my c# code. For example, I have sql:
SET IDENTITY_INSERT [dbo].[State] ON
INSERT INTO [dbo].[State] ([Id], [Code], [Name]) VALUES (1, N'AL', N'Alabama')
INSERT INTO [dbo].[State] ([Id], [Code], [Name]) VALUES (2, N'AK', N'Alaska')
SET IDENTITY_INSERT [dbo].[State] OFF
And instead of it I want to have a string:
new List<State>
{
new State { Id = 1, Code = "AL", Name = "Alabama" },
new State { Id = 2, Code = "AK", Name = "Alaska" }
};
How can I achieve it?
For INSERT statements (as you said you need seed) you can create helper method like this:
public static List<State> ParseSqlScript(string sqlScriptPath)
{
using (var reader = new StreamReader(sqlScriptPath))
{
var sqlScript = reader.ReadToEnd();
var pattern = #"INSERT INTO \[dbo\].\[State\] \(\[Id\], \[Code\], \[Name\]\) VALUES (\(.*?\))";
var regex = new Regex(pattern);
var matches = regex.Matches(sqlScript);
var states = new List<State>();
foreach (Match match in matches)
{
var values = match.Groups[1].Value.Split(new [] { '(', ',',' ',')' }, StringSplitOptions.RemoveEmptyEntries);
var id = int.Parse(values[0]);
var code = values[1].Substring(2, values[1].Length - 3);
var name = values[2].Substring(2, values[2].Length - 3);
foreach (var value in values)
{
var state = new State() { Id = id, Code = code, Name = name };
states.Add(state);
}
}
return states;
}
}
If you also need other CRUD statements you will probably need to get acquainted with some SQL Parser, maybe the Microsoft.SqlServer.SMO.
Try to add the following code, I assume you are working with entity framework:
List<State> states = new List<State>()
{
new State { Id = 1, Code = "AL", Name = "Alabama" },
new State { Id = 2, Code = "AK", Name = "Alaska" }
};
StateDBEntities context = new StateDBEntities();
foreach (State state in states)
{
context.State.Add(state);
}
context.SaveChanges();
List<State> states = new List<State>();
using (SqlConnection connection = new SqlConnection("conn_string"))
{
string query = "SELECT Id, Code, Name FROM State";
using (SqlCommand command = new SqlCommand(query, connection))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
State state = new State { Id = (int)reader["Id"], Code = reader["Code"].ToString(), Name = reader["Name"].ToString() };
states.Add(state);
}
}
}
}
Call the select query(here I'm writing the query, but it should be avoided, you can use Stored Procedure). Get all columns using ExecuteReader and add all the rows to list one by one.

Index was outside the bounds of the array when adding class to list C#

Exception: Index was outside the bounds of the array.
First off, I'm familiar with this exception and I have fixed it before but I'm getting this exception at a very strange line in my code. It is being thrown when I'm adding a user created class to a list of classes in my code. I'm completely lost as to why it is throwing this exception and how to fix it.
public static async Task getData()
{
// initialize everything
List<StockData> stockData = new List<StockData>();
List<StockMarketCompare> stockCompareData = new List<StockMarketCompare>();
List<StockData> sandpInfo = new List<StockData>();
List<StockData> sandpDateInfo = new List<StockData>();
List<StockData> amexList = new List<StockData>();
List<DateTime> completedDates = new List<DateTime>();
SymbolInfo symbolClass = new SymbolInfo();
List<SymbolInfo> ratingSymbols = new List<SymbolInfo>();
List<StockRating> ratingList = new List<StockRating>();
bool isGoodToGo = false;
string symbol, market;
int activeSymbolsCount = 0;
int rowCount = 0, completedRowCount = 0;
DateTime date = new DateTime();
DateTime searchDate = new DateTime();
using (SqlConnection connection = new SqlConnection("connectionstring"))
using (SqlCommand sandpCommand = new SqlCommand("select * from dbo.DailyGlobalData where Symbol='" + Calculations.sp500 + "'", connection))
using (SqlDataAdapter sandpAdapter = new SqlDataAdapter(sandpCommand))
using (DataTable sandpTable = new DataTable("sandp"))
using (SqlCommand stockRatingsCommand = new SqlCommand("select * from dbo.StockRatings", connection))
using (SqlDataAdapter stockRatingsAdapter = new SqlDataAdapter(stockRatingsCommand))
using (DataTable stockRatingsTable = new DataTable("stockratings"))
{
try
{
// fill the sandptable
sandpAdapter.Fill(sandpTable);
if (sandpTable != null)
{
var sandpQuery = from c in sandpTable.AsEnumerable()
select new StockData { Close = c.Field<decimal>("Close"), Date = c.Field<DateTime>("Date"), High = c.Field<decimal>("High"), Low = c.Field<decimal>("Low"), Volume = c.Field<Int64>("Volume") };
sandpInfo = sandpQuery.AsParallel().ToList();
}
// fill the stockratingstable
stockRatingsAdapter.Fill(stockRatingsTable);
if (stockRatingsTable != null)
{
activeSymbolsCount = stockRatingsTable.Rows.Count;
var symbolsAmountQuery = from c in stockRatingsTable.AsEnumerable()
select new SymbolInfo { Symbol = c.Field<string>("Symbol"), Market = c.Field<string>("Market") };
ratingSymbols = symbolsAmountQuery.AsParallel().ToList();
}
for (int i = 0; i < activeSymbolsCount; i++)
{
symbol = ratingSymbols.AsParallel().ElementAtOrDefault(i).Symbol;
market = ratingSymbols.AsParallel().ElementAtOrDefault(i).Market;
ratingList = new List<StockRating>();
using (SqlCommand historicalRatingsCommand = new SqlCommand("select * from dbo.OldStockRatings where Symbol='" + symbol + "' and Market='" + market + "'", connection))
using (SqlDataAdapter historicalRatingsAdapter = new SqlDataAdapter(historicalRatingsCommand))
using (DataTable historicalRatingsTable = new DataTable("historicalratings"))
{
// fill the historical ratings table
historicalRatingsAdapter.Fill(historicalRatingsTable);
if (historicalRatingsTable != null)
{
completedRowCount = historicalRatingsTable.AsEnumerable().AsParallel().Count();
completedDates = historicalRatingsTable.AsEnumerable().AsParallel().Select(d => d.Field<DateTime>("Date")).ToList();
}
}
using (SqlCommand amexCommand = new SqlCommand("select * from dbo.DailyAmexData where Symbol='" + symbol + "'", connection))
using (SqlDataAdapter amexAdapter = new SqlDataAdapter(amexCommand))
using (DataTable amexTable = new DataTable("amexdata"))
{
// fill the amex data table
amexAdapter.Fill(amexTable);
if (amexTable != null)
{
var amexFillQuery = from c in amexTable.AsEnumerable()
select new StockData { Close = c.Field<decimal>("Close"), Date = c.Field<DateTime>("Date"), High = c.Field<decimal>("High"), Low = c.Field<decimal>("Low"), Volume = c.Field<Int64>("Volume") };
amexList = amexFillQuery.AsParallel().ToList();
rowCount = amexList.AsParallel().Count();
}
}
Parallel.For(0, rowCount - 30, new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount
}, async j =>
{
if (amexList.AsParallel().Count() > 0)
{
date = amexList.AsParallel().ElementAtOrDefault(j).Date;
searchDate = date.Subtract(TimeSpan.FromDays(60));
if (completedDates.Contains(date) == false)
{
var amexQuery = from c in sandpInfo
where c.Date >= searchDate && c.Date <= date
join d in amexList on c.Date equals d.Date
select new StockMarketCompare { stockClose = d.Close, marketClose = c.Close };
var amexStockDataQuery = from c in amexList
where c.Date >= searchDate && c.Date <= date
select new StockData { Close = c.Close, High = c.High, Low = c.Low, Volume = c.Volume, Date = c.Date };
stockCompareData = amexQuery.AsParallel().ToList();
stockData = amexStockDataQuery.AsParallel().ToList();
isGoodToGo = true;
}
else
{
isGoodToGo = false;
}
}
if (completedDates.Contains(date) == false)
{
var sandpDateQuery = from c in sandpInfo
where c.Date >= searchDate && c.Date <= date
select c;
sandpDateInfo = sandpDateQuery.AsParallel().ToList();
symbolClass = new SymbolInfo() { Symbol = symbol, Market = market };
isGoodToGo = true;
}
else
{
isGoodToGo = false;
}
if (isGoodToGo)
{
StockRating rating = performCalculations(symbolClass, date, sandpInfo, stockData, stockCompareData);
if (rating != null)
{
**ratingList.Add(rating);** // getting the exception thrown here
}
}
});
// now save the results to the table outside the parallel for loop
ratingList.RemoveAll(item => item == null);
List<StockRating> masterList = ratingList.DistinctBy(j => j.date).ToList();
saveToTable(masterList, symbol, market);
// close the connection
connection.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// close the connection
connection.Close();
}
}
}
List<T> is not thread safe and you are calling .Add from inside your Parallel.For. You either need to lock on the Add or use a threadsafe collection in the System.Collections.Concurrent namespace.
This is not your only threading error you have, for example also inside your Parallel.For you assign several variables that are all declared in the scope outside of the loop. Your various threads are going to be writing over each other assigning those values.
ratingsList is not thread safe because List<T> is not guaranteed to be thread safe (except for static methods), yet you are modifying it from multiple threads.
Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it’s being read. To ensure thread safety, lock the collection during a read or write operation. To enable a collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization. For collections with built-in synchronization, see the classes in the System.Collections.Concurrent namespace. For an inherently thread–safe alternative, see the ImmutableList class.
https://msdn.microsoft.com/en-us/library/6sh2ey19%28v=vs.110%29.aspx
Use a thread safe collection instead.

Any suggestions for better function to get data from DataBase

To get my data(users and books) from Firebird base I am using the following methods:
public static Dictionary<string, Users> getUsersFromDB()
{
Dictionary<string, Users> users_list = new Dictionary<string, Users>();
string reqestCommand = "SELECT id, user_type, user_nick, user_pass, user_fname, user_lname, user_identify, e_mail,address, registered FROM USERS";
string connectionString = connectionPath();
using (FbConnection fbDB = new FbConnection(connectionString))
{
fbDB.Open();
FbCommand reqest = new FbCommand(reqestCommand, fbDB);
using (FbDataReader reader = reqest.ExecuteReader())
{
while (reader.Read())
{
string key;
Users user = new Users();
user.ID = reader.GetInt32(0);
user.type = reader.GetString(1).Trim();
key = reader.GetString(2).Trim();
user.nick = key;
user.password = reader.GetString(3).Trim();
user.fName = reader.GetString(4).Trim();
user.lName = reader.GetString(5).Trim();
user.identify = reader.GetString(6).Trim();
user.email = reader.GetString(7).Trim();
user.address = reader.GetString(8).Trim();
user.registerDate = reader.GetString(9).Trim();
user.localOrInDb = "inDatabase";
users_list.Add(key, user);
}
}
fbDB.Close();
}
return users_list;
}
public static Dictionary<Guid, Books> getBooksFromDB()
{
Dictionary<Guid, Books> books_list = new Dictionary<Guid, Books>();
string reqestCommand = "SELECT book_id, book_title, book_author, book_genre, book_language, book_rel_date, book_page_number, book_available_amount, book_type FROM BOOKS";
string connectionString = connectionPath();
using (FbConnection fbDB = new FbConnection(connectionString))
{
fbDB.Open();
FbCommand reqest = new FbCommand(reqestCommand, fbDB);
using (FbDataReader reader = reqest.ExecuteReader())
{
while (reader.Read())
{
Guid key;
Books book = new Books();
Guid theGuid = new Guid(reader.GetString(0).Trim());
key = book.ID = theGuid;
book.title = reader.GetString(1).Trim();
book.author = reader.GetString(2).Trim();
book.genre = reader.GetString(3).Trim();
book.language = reader.GetString(4).Trim();
book.rel_date = reader.GetString(5).Trim();
book.page_number = reader.GetInt32(6);
book.amount = reader.GetInt32(7);
book.type = reader.GetString(8).Trim();
book.localOrInDb = "inDatabase";
books_list.Add(key, book);
}
}
fbDB.Close();
}
return books_list;
}
How can we see, they are almost same, so question:
Is it possible to make from them one function? Or would it be better to leave them separate?
You can pass a parameter that makes an instance of your target class from Reader:
public static Dictionary<K,T> GetFromDb<K,T>(Func<Reader,KeyValuePair<K,T> maker) {
var res = new Dictionary<K,T>();
// ... your code ...
while (reader.Read()) {
res.Add(maker(reader));
}
return res;
}
Now you can call your GetFromDb like this:
var users = GetFromDb<string,User>(reader => {
string key;
Users user = new Users();
user.ID = reader.GetInt32(0);
user.type = reader.GetString(1).Trim();
key = reader.GetString(2).Trim();
user.nick = key;
user.password = reader.GetString(3).Trim();
user.fName = reader.GetString(4).Trim();
user.lName = reader.GetString(5).Trim();
user.identify = reader.GetString(6).Trim();
user.email = reader.GetString(7).Trim();
user.address = reader.GetString(8).Trim();
user.registerDate = reader.GetString(9).Trim();
user.localOrInDb = "inDatabase";
return new KeyValuePair<string,User>(key, user);
});
Check this post
https://stackoverflow.com/a/1464929/988830
You can abstract the reader using extension method.
However you have to create the FBconnection everytime. If you try to put it in a function and access it from another one then you will get exception that reader is closed.
using uses IDisposible http://msdn.microsoft.com/en-us/library/system.idisposable.aspx
which closes the connection.
Another option
Start using orm like Entity framework or NHibernate which will map the table with your object.

Categories

Resources