I have records in a SQL Server database table that need to be transmitted to a payment processing API. Only table records where Transmitted = 0 are to be sent to the API. I found this question which helps me to understand the error at hand.
I want to read the records from my database table into a C# class, parse into JSON, and send via API call. I've tried using Entity Framework Core but receive Null Reference exception when I called the stored procedure and add the results to my model class as follows:
public async Task<IEnumerator<Onboarding>> GetOnboardingList()
{
try
{
using (var context = new SOBOContext())
{
var ob = await context.Onboarding
//.Where(o => o.Transmitted == false)
.FromSql("Execute GetUnsentOnboardingRecords_sp")
.ToListAsync();
Console.WriteLine(ob.ToArray());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return new OnboardingList().GetEnumerator();
}
When attempting to use Entity Framework Core, I created the OnboardingList class as below:
public class OnboardingList : IEnumerable<Onboarding>
{
private readonly List<Onboarding> _onboarding;
public IEnumerator<Onboarding> GetEnumerator()
{
return _onboarding?.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _onboarding.GetEnumerator();
}
}
I then reverted to SqlDataReader that calls the stored procedure. The method, in part, is as follows:
var listOnboardingModel = new List<Onboarding>();
try
{
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
SqlCommand cmd = new SqlCommand("GetUnsentOnboardingRecords_sp", connection)
{
CommandType = CommandType.StoredProcedure
};
connection.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
listOnboardingModel.Add(new Onboarding
{
OnboardingId = reader[1] as int? ?? 0,
UserId = reader[2] as int?,
UserName = reader[3].ToString(),
FirstName = reader[4].ToString(),
MiddleInitial = reader[5].ToString(),
Lastname = reader[6].ToString(),
DateOfBirth = reader[7].ToString(),
Ssn = reader[8].ToString(),
Email = reader[9].ToString(),
Address1Line1 = reader[10].ToString(),
Address1Line2 = reader[11].ToString(),
Address1ApartmentNumber = reader[12].ToString(),
Address1City = reader[13].ToString(),
Address1State = reader[14].ToString(),
Address1ZipCode = reader[15].ToString(),
Address1Country = reader[16].ToString(),
DayPhone = reader[17].ToString(),
EveningPhone = reader[18].ToString(),
PhonePin = reader[19].ToString(),
MerchantSourceIp = reader[20].ToString(),
ThreatMetrixPolicy = reader[21].ToString(),
SessionId = reader[22].ToString(),
BankAccount1CountryCode = reader[23].ToString(),
BankAccount1Name = reader[24].ToString(),
BankAccount1Description = reader[25].ToString(),
BankAccount1Number = reader[26].ToString(),
BankAccount1OwnershipType = reader[27].ToString(),
BankAccount1Type = reader[28].ToString(),
BankAccount1BankName = reader[29].ToString(),
BankAccount1RoutingNumber = reader[30].ToString(),
BankAccount2CountryCode = reader[31].ToString(),
BankAccount2Name = reader[32].ToString(),
BankAccount2Number = reader[33].ToString(),
BankAccount2OwnershipType = reader[34].ToString(),
BankAccount2Type = reader[35].ToString(),
BankAccount2BankName = reader[36].ToString(),
BankAccount2Description = reader[37].ToString(),
BankAccount2RoutingNumber = reader[38].ToString(),
AuthSginerFirstName = reader[39].ToString(),
AuthSignerLastName = reader[40].ToString(),
AuthSignerTitle = reader[41].ToString(),
AverageTicket = reader[42] as int? ?? 0,
BusinessLegalName = reader[43].ToString(),
BusinessApartmentNumber = reader[44].ToString(),
BusinessAddressLine1 = reader[45].ToString(),
BusinessAddressLine2 = reader[46].ToString(),
BusinessCity = reader[47].ToString(),
BusinessState = reader[48].ToString(),
BusinessZipCode = reader[49].ToString(),
BusinessCountry = reader[50].ToString(),
BusinessDescription = reader[51].ToString(),
DoingBusinessAs = reader[52].ToString(),
Ein = reader[53].ToString(),
HighestTicket = reader[54] as int? ?? 0,
MerchantCategoryCode = reader[55].ToString(),
MonthlyBankCardVolume = reader[56] as int? ?? 0,
OwnerFirstName = reader[57].ToString(),
OwnerLastName = reader[58].ToString(),
OwnerSsn = reader[59].ToString(),
OwnerDob = reader[60].ToString(),
OwnerApartmentNumber = reader[61].ToString(),
OwnerAddress = reader[62].ToString(),
OwnerAddress2 = reader[63].ToString(),
OwnerCity = reader[64].ToString(),
OwnerRegion = reader[65].ToString(),
OwnerZipCode = reader[66].ToString(),
OwnerCountry = reader[67].ToString(),
OwnerTitle = reader[68].ToString(),
OwnerPercentage = reader[69].ToString(),
BusinessUrl = reader[70].ToString(),
CreditCardNumber = reader[71].ToString(),
ExpirationDate = reader[72].ToString(),
NameOnCard = reader[73].ToString(),
PaymentMethodId = reader[74].ToString(),
PaymentBankAccountNumber = reader[75].ToString(),
PaymentBankRoutingNumber = reader[76].ToString(),
PaymentBankAccountType = reader[77].ToString(),
Transmitted = reader[78] as bool?,
TransmitDate = reader[79].ToString(),
InternationalId = reader[80].ToString(),
DriversLicenseVersion = reader[81].ToString(),
DocumentType = reader[82].ToString(),
DocumentExpDate = reader[83].ToString(),
DocumentIssuingState = reader[84].ToString(),
MedicareReferenceNumber = reader[85].ToString(),
MedicareCardColor = reader[86].ToString(),
PaymentBankAccountName = reader[87].ToString(),
PaymentBankAccountDescription = reader[88].ToString(),
PaymentBankCountryCode = reader[89].ToString(),
PaymentBankName = reader[90].ToString(),
PaymentBankOwnershipType = reader[91].ToString()
});
}
}
connection.Close();
}
Console.WriteLine(listOnboardingModel);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return listOnboardingModel;
When I run the console application, I receive the Error Exception thrown: 'System.IndexOutOfRangeException' in System.Data.SqlClient.dll
Index was outside the bounds of the array.
How do I prevent the above out of range exception and also ensure that all qualifying records from my database table are included for transmission?
You appear to have two distinct issues:
You .ToString() a null value, which causes null reference exception.
An ordinal is likely out of bounds, a column does not match the numeric value provided causing it to be out of bounds.
Check for DBNull.Value otherwise bullet point one will occur.
My assumption would be that you start with one, while index's are zero based. So the first column would start at zero not one.
You should lookup Dapper or some built in utilities for SqlDataReader to leverage cleaner code calls to build your object. Dapper would condense the code by simply doing:
IEnumerable<Sample> GetSamples() => dbConnection.Query<Sample>(...);
As long as the column name matches the property name, it'll populate. Based on the entity provided it looks pretty simple, but checkout the documentation.
Also you should wrap command in using statement like, that way you do not have someone accidentally call a command.Dispose() in the middle of your connection block on accident:
using(var connection = new SqlConnection(...))
using(var command = new SqlCommand(..., ...))
{
connection.Open();
...
using(var reader = command.ExecuteReader())
while(reader.Read())
{
}
}
I'm making a library for connecting an Informix database with a C# extension for use in Outsystems.
So, now I've hit a wall. I need to receive a list/multiset from the db. How can I do that? I'm using the IfxDataReader to receive the data. But I see no method that could work.
Here is how we can send a list/multiset input parameter. But I need to receive it from a result set.
EDIT: Seeing as this was frowned upon by someone I'll provide some code and my try at it to see if you think it's correct (can't test it right now as I don't yet have data on the database... I'll test it in the end):
ssBensAndFotos = new RLBensAndFotos_BemRecordList();
DatabaseConnection dc = new DatabaseConnection(ssDatabase, ssHost, ssServer, ssService, ssProtocol, ssUserID, ssPassword);
try
{
dc.Conn = new IfxConnection(dc.ConnectionString);
dc.Cmd = new IfxCommand("get_bens_and_fotos_by_id_diligencia", dc.Conn);
dc.Cmd.CommandType = CommandType.StoredProcedure;
dc.Cmd.Parameters.Add(new IfxParameter("pi_id_diligencia", IfxType.Integer) { Direction = ParameterDirection.Input, Value = sspi_id_diligencia });
dc.Conn.Open();
using (IfxDataReader reader = dc.Cmd.ExecuteReader())
if (reader.HasRows)
while (reader.Read())
{
var bensAndFotos = new STBensAndFotos_BemStructure()
{
ssid_bem = reader.GetInt32(0),
ssnumero = reader.GetInt32(1),
ssespecie = reader.GetString(2),
ssbem = reader.GetString(3),
ssvalor = reader.GetDecimal(4),
sscomum = reader.GetInt32(5),
ssvoice_record = AuxiliaryMethods.CreateByteArrayFromIfxBlob(reader.GetIfxBlob(6))
};
// Here I get the string, split it and check to see which members of the array are integers, since in this case I'll be getting a multiset of int's
var multisetString = reader.GetString(7).Split('\'');
int n;
foreach (var item in multisetString)
if (int.TryParse(item, out n))
bensAndFotos.ssfotos.Add(new STBensAndFotos_FotoStructure() { ssfoto = n });
ssBensAndFotos.Add(bensAndFotos);
}
dc.Conn.Close();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
LIST, MULTISET and SET are mapped to String. You should be able to use IfxDataReader.GetString() to get the data.
This seems to be a problem either with my computer or the azure sql servers. The problem I have was not a problem before but the code all of a sudden acting strange. Here is my code that is used to work. Connection opens successfully, my executenonquery works fine but reader acts strange.
public static List<ClientDto> GetClientInformation()
{
List<ClientDto> results = new List<ClientDto>();
try
{
using (var sqlConnection =
new SqlConnection(ConfigurationManager.ConnectionStrings["ClientData"].ConnectionString))
{
using (var command = new SqlCommand(null, sqlConnection))
{
sqlConnection.Open();
command.CommandType = CommandType.StoredProcedure;
//running the GetClientInformation stored procedure in DB
command.CommandText = Constants.GetClientInformation;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
results.Add(new ClientDto()
{
Id = Convert.ToInt32(reader["Id"].ToString()),
DealerName = reader["DealerName"].ToString(),
GroupID = reader["GroupID"].ToString(),
DealerId = reader["DealerId"].ToString(),
DealerFolderName = reader["DealerFolder"].ToString(),
DMSType = reader["DMSType"].ToString(),
MAIsActive = Convert.ToBoolean(Convert.ToInt32(reader["MAIsActive"])),
SalesIsActive = Convert.ToBoolean(Convert.ToInt32(reader["SalesIsActive"])),
SalesSource = reader["SalesSource"].ToString(),
InventoryIsActive = Convert.ToBoolean(Convert.ToInt32(reader["InventoryIsActive"])),
InventorySource = reader["InventorySource"].ToString(),
AppointmentsIsActive =
Convert.ToBoolean(Convert.ToInt32(reader["AppointmentsIsActive"])),
AppointmentsSource = reader["AppointmentsSource"].ToString(),
LeadsIsActive = Convert.ToBoolean(Convert.ToInt32(reader["LeadsIsActive"])),
LeadsSource = reader["LeadsSource"].ToString(),
ServiceIsActive = Convert.ToBoolean(Convert.ToInt32(reader["ServiceIsActive"])),
ServiceSource = reader["ServiceSource"].ToString(),
SalesIsDelete = Convert.ToBoolean(Convert.ToInt32(reader["SalesIsDelete"])),
SalesDeleteRange = reader["SalesDeleteRange"].ToString(),
InventoryIsDelete = Convert.ToBoolean(Convert.ToInt32(reader["InventoryIsDelete"])),
InventoryDeleteRange = reader["InventoryDeleteRange"].ToString(),
AppointmentsIsDelete =
Convert.ToBoolean(Convert.ToInt32(reader["AppointmentsIsDelete"])),
AppointmentsDeleteRange = reader["AppointmentsDeleteRange"].ToString(),
LeadsIsDelete = Convert.ToBoolean(Convert.ToInt32(reader["LeadsIsDelete"])),
LeadsDeleteRange = reader["LeadsDeleteRange"].ToString(),
ServiceIsDelete = Convert.ToBoolean(Convert.ToInt32(reader["ServiceIsDelete"])),
ServiceDeleteRange = reader["ServiceDeleteRange"].ToString(),
IsActive = Convert.ToBoolean(Convert.ToInt32(reader["IsActive"])),
UserDefinedName = reader["UserDefinedName"].ToString()
});
}
}
}
return results;
}
catch (Exception ex)
{
//If an exception happens it will insert the error to errorlog table and to the log file
Logger.WriteError(new ErrorDto()
{
Source = "GetClientInformation",
Message = ex.Message,
StackTrace = ex.StackTrace
});
Console.WriteLine(string.Format("Error - {0} - {1} ", "GetClientInformation", ex.Message));
return results;
}
}
There is data in the sql, the executable of my application works fine on our virtual machine (Windows Server).
Only strange thing I see in the code is that SQLConnection have this innerexception (it is not stopping the code to run though)
ServerVersionNormalized = 'sqlConnection.InnerConnection.ServerVersionNormalized' threw an exception of type 'System.NotSupportedException'
I am not sure if this was an issue before or not.
This picture shows that Enamration yielded no result
This picture shows that reader has rows
I am reinstalling my visual studio now to see if it will fix the issue.
This issue is happening in both Visual Studio 2015 and 2017 for me.
This is my connection string format:
Server=.database.windows.net,1433;Database=; User ID=;Password=;Trusted_Connection=False; Encrypt=True;
It looks like it is working fine now. To me it sounds like since I had breakpoint in the code, it might have caused the issue because of the longer time between connecting and running the query.
I'm using Entity Framework v6 and I'm trying to make sure that I can perform an atomic Insert or Select the record if it doesn't already exist so that in a farm of servers (or multiple threads) I can guarantee that I don't get a unique key constraint violation.
I have a simple example with a table like this and a corresponding simple Model with the 2 properties.
CREATE TABLE [dbo].[NewItem]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](40) NOT NULL,
CONSTRAINT [PK_NewItem] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [IX_NewItem] UNIQUE NONCLUSTERED ( [Name] ASC)
)
When I write my Entity Framework code, I cannot guarantee that the object doesn't get inserted after the .Any returns false.
using (var myContext = new MyContext())
{
using (var transaction = myContext.Database.BeginTransaction())
{
if (!myContext.NewItems.Any(item => item.Name == identifier))
{
var newItem = new NewItem { Name = identifier };
myContext.NewItems.Add(newItem);
try
{
var result = myContext.SaveChanges();
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
throw;
}
}
transaction.Commit();
}
}
If I was to perform this same code using a direct SQL statement via a SqlCommand object, this is what I would use and as far as I can tell it always works.
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
{
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = "IF (NOT EXISTS(SELECT ID FROM NewItem WHERE Name = #name)) " +
" BEGIN " +
" INSERT INTO NewItem VALUES (#name) " +
" SELECT SCOPE_IDENTITY() AS ID " +
" END " +
" ELSE " +
" BEGIN " +
" SELECT ID FROM NewItem WHERE Name = #name " +
" END";
var nameParam = new SqlParameter("#name", System.Data.SqlDbType.VarChar, 40);
nameParam.Value = Name;
cmd.Parameters.Add(nameParam);
connection.Open();
var result = cmd.ExecuteScalar();
connection.Close();
Id = Convert.ToInt32(result);
}
}
Is there some way with Entity Framework that I can perform the same operation as my raw SqlCommand so that if the record doesn't exist it gets inserted and if it already exists, I just get the one that is there in an atomic operation?
To demonstrate this is some of the threading code that I've used to simulate multiple servers.
private static ExecutionMode mode;
private static ManualResetEventSlim wait;
private static string identifier;
private enum ExecutionMode
{
None = 0,
Entityframework,
RawSql
}
static void Main(string[] args)
{
//Comment out the relevant item to test
//mode = ExecutionMode.RawSql;
mode = ExecutionMode.Entityframework;
wait = new ManualResetEventSlim(false);
identifier = Guid.NewGuid().ToString();
var threads = new List<Thread>();
for (var i = 0; i < 20; i++)
{
var t = new Thread(RunCreate);
threads.Add(t);
t.Start();
}
wait.Set();
for (var i = 0; i < 20; i++)
{
var t = threads[i];
t.Join();
}
}
private static void RunCreate()
{
wait.Wait();
switch (mode)
{
case ExecutionMode.Entityframework:
{
//perform the Entityframework code above
}
break;
case ExecutionMode.RawSql:
{
var i = new NewItem { Name = identifier };
i.InsertOrSelect(); //This is just using the code for the raw SQL Statements
}
break;
}
}
Exceptions experienced with the Entityframework, displaying ex.Message looping through the exception->innerexception until innerexception is null
With Default Transaction Isolation Level
BeginTransaction()
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException'
occurred in EntityFramework.dll
An error occurred while updating the entries.
See the inner exception for details.
An error occurred while updating the entries.
See the inner exception for details.
Violation of UNIQUE KEY constraint 'IX_NewItem'. Cannot insert duplicate key in object 'dbo.NewItem'.
The duplicate key value is (f32c6a59-1462-49c3-85e2-5126c96ad484).
With Serializable tranaction Isolation
BeginTransaction(System.Data.IsolationLevel.Serializable)
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException'
occurred in EntityFramework.dll
Transaction (Process ID 59) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Neither of these are guaranteed to work. The piece that you are missing is Transaction Isolation Level.
The SQL Server default isolation level is READ COMMITTED. This has only minimal locking protections. It means that it is possible for another command to insert the new item after your IF condition is evaluated but before the new row is inserted. The raw SQL server command is much less likely to encounter this scenario because it is processed without a round-trip between the commands: there is less time for another server to interfere.
If you set the isolation level to Serializable then you are guaranteed to either insert the item or select the existing item. In Entity Framework your code will look like this:
using (var myContext = new MyContext())
{
using (var transaction = myContext.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
if (!myContext.NewItems.Any(item => item.Name == identifier))
{
var newItem = new NewItem { Name = identifier };
myContext.NewItems.Add(newItem);
try
{
var result = myContext.SaveChanges();
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
throw;
}
}
transaction.Commit();
}
}
ServiceStack aficionados, hello!
We are legion (I hope so), so please help a brother out :)
I am trying to populate two collections with one SQL Server 2008 stored procedure call that return two resultsets.
I have "MultipleActiveResultSets=True" in my connection string but I am still getting this error:
'r.NextResult()' threw an exception of type
'System.InvalidOperationException'
Here is my code:
IList<ProjectMember> projectMembers = null;
IList<Project> projects = DbFactory.Run(dbCnx =>
{
using (var dbCmd = dbCnx.CreateCommand())
{
dbCmd.CommandType = CommandType.StoredProcedure;
dbCmd.CommandText = "mySchema.myStoredProc";
dbCmd.Parameters.Add(new SqlParameter("#categoryId", categoryId));
using (profiler.Step("ProjectService.myStoredProc"))
{
var r = dbCmd.ExecuteReader();
projectMembers = r.ConvertToList<ProjectMember>();
return r.NextResult() ? r.ConvertToList<Project>() : null;
}
}
});
Is this possible? If so, can someone please show me an example on how to do that?
Thanks,
Samir
I've found a way but I had to replace ormLite with Dapper:
using(var cnx = DbFactory.CreateConnection(Global.ConnectionString))
{
using (var multi = cnx.QueryMultiple("mySchema.myStoredProc", new { communityId, categoryId }, commandType: CommandType.StoredProcedure))
{
var projectMembers = multi.Read<ProjectMember>().ToList();
var projects = multi.Read<Project>().ToList();
BindProjectMembers(projects, projectMembers);
return projects;
}
}
It works perfectly and is twice as fast (from 40 ms to 20 ms) than before the MARS amelioration.