This question might be asked but I could not find the answer for now so please help me with this. I'm trying to add a new row into my SQL database through EF model. Is there anything wrong because it looks okay but actually does not insert anything to the table.
private void Bsubmit_Click(object sender, EventArgs e)
{
using(var db = new Models.CompanyDBEntities())
{
db.Clients.Add(new Client
{
name = nameTB.Text,
age = Convert.ToInt32(ageTB.Text)
});
db.SaveChanges();
}
}
EDIT** So now I'm adding a Change Tracker to see what's going on:
if (db.ChangeTracker.HasChanges()){
db.SaveChanges();
var clients = (from c in db.Clients select c).ToList();
string updated = "";
foreach (var c in clients)
{
updated += c.Name;
}
MessageBox.Show(updated);
}
The message box does show up the name of current clients in the data together with the name I just added. I think the problem here is .SaveChanges() it's not working. I did not find the solution yet.
Try add Parameterless Constructor to your class and try again, just guess:
public class Client
{
public Client()
{
}
public string name ...
public int age ...
...
}
and use parentheses when new: 'new Client () {'
db.Clients.Add(new Client()
{
name = nameTB.Text,
age = Convert.ToInt32(ageTB.Text)
});
Try to run Sql Server Profiler, and inspect the Query executed against your statement, most probably you will find your answer.
Related
I work with C# in Visual Studio 2019. My database is in SQLite using Dapper.
Here is what I am struggling with.
I have 2 tables in my database that are connected.
The parent, tbClient. And the child table, tbProject.
tbProject has a field to ClientId.
I use a ComboBox to WireUpp the Data from the database to my form. I have a form to Client, and a form for the Project, in this form I chose a CLient in a ComboBox, and save its ID in my tbProjet.
The idea is simple, but I am struggling because I am using an example that was made in Windows (WPF), and my application is in Windows Forms. I noticed that the Properties of the ComboBox are not the same, then I am having some trouble accessing the correct Project field when I want to open the Project of a specific Client.
Let´s show how it's done in the WPF app and in my WinForm app. I think is going to be more clear to get some help.
The codes of the Project Form are below: the first is the WPF app, and the second one is the WinForm where I was not able to make work yet.
WPF Application:
// WPF Application:
namespace MyApp.Controls
{
public ProjectControls()
{
InitializeComponent();
InitializeClientList();
WireUpDropDowns();
}
private void WireUpDropDowns()
{
clientDropDown.ItemsSource = clients;
clientDropDown.DisplayMemberPath = "Name";
clientDropDown.SelectedValuePath = "Id";
projectDropDown.ItemsSource = projects;
projectDropDown.DisplayMemberPath = "DisplayValue";
projectDropDown.SelectedValuePath = "Id";
}
private void InitializeClientList()
{
string sql = "select * from Client order by Name";
var clientList = SqliteDataAccess.LoadData<ClientModel>(sql, new Dictionary<string, object>());
clientList.ForEach(x => clients.Add(x));
}
private void clientDropDown_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadProjectDropDown();
}
private void LoadProjectDropDown()
{
string sql = "select * from tbProject where ClientId = #ClientId";
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{ "#ClientId", clientDropDown.SelectedValue }
};
var records = SqliteDataAccess.LoadData<ProjectsModel>(sql, parameters);
projects.Clear();
records.ForEach(x => projects.Add(x));
}
}
Windows Form Application:
// Windows Forms Application:
namespace MyApp.Controls
{
public ProjectControls()
{
InitializeComponent();
InitializeClientList();
WireUpDropDowns();
}
private void WireUpDropDowns()
{
clientDropDown.DataSource = null;
clientDropDown.DataSource = clients;
clientDropDown.DisplayMember= "Name";
clientDropDown.ValueMember = "Id";
projectDropDown.DataSource = null;
projectDropDown.DataSource= projects;
projectDropDown.DisplayMember = "DisplayValue";
projectDropDown.ValueMember= "Id";
}
private void InitializeClientList()
{
string sql = "select * from Client order by Name";
var clientList = SqliteDataAccess.LoadData<ClientModel>(sql, new Dictionary<string, object>());
clientList.ForEach(x => clients.Add(x));
}
private void clientDropDown_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadProjectDropDown();
}
private void LoadProjectDropDown()
{
string sql = "select * from tbProject where ClientId = #ClientId";
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{ "#ClientId", clientDropDown.SelectedValue }
// --> I think the problem is here, I am passing an object to the database where ClientId is an integer type. I tried to use SelectedIndex instead, but with this property, I do not get the correct Project from the table
};
var records = SqliteDataAccess.LoadData<ProjectsModel>(sql, parameters);
projects.Clear();
records.ForEach(x => projects.Add(x));
}
}
In the Windows Form Application I get this Error Message from my AccesDataBase Routine:
System.NotSupportedException: 'The member ClientId of type AppLibrary.Models.ClientModel cannot be used as a parameter value'
So I think the basic question here is Am I using the ComboBOx Properties correct? What I am missing?
Thank you in advance for any help received.
Verônica.
I found out about my mistake.
The property of the Combobox is correct.
I was passing the wrong parameter to the ClientDropDown.SelectedValue in other parts of the code that I haven´t shared here.
I was trying to select a specific client in the ClientDropDown through code but I was passing SelectedIndex to the SelectedValue.
I used the Breakpoints and was able to find the error, and now is working with this code that I share here in the question, it is correct.
I'm completely new to C# programming and I'm trying to learn on my own. Currently I'm building a mini-project to exercise.
I understand that the user layer should not have any data query for security reasons perhaps?
So I have created a separate Data Access class to retrieve data. This is what my data access class looks like(I'll be using stored procedures for better security once I learn how to use it):
public class DataAccess
{
public List<Customer> FilteredCustomersList(string name)
{
using (IDbConnection connection = new MySql.Data.MySqlClient.MySqlConnection(Helper.CnnVal("FineCreteDB")))
{
var output = connection.Query<Customer>($"SELECT * from `Customers` WHERE `Cust_Name` LIKE '{name}'").ToList();
return output;
}
}
Basically I send over a string from the user form to query the database, the data is retrieved and stored in a list. User form:
private void RetrieveData()
{
try
{
DataAccess db = new DataAccess();
filteredcustomers = db.FilteredCustomersList(CustomerNameTxtBox_AutoComplete.Text);
ntn_num = filteredcustomers.Select(x => x.Cust_NTN).ElementAt(0);
strn_num = filteredcustomers.Select(x => x.Cust_STRN).ElementAt(0);
address = filteredcustomers.Select(x => x.Cust_Address).ElementAt(0);
phone_num = filteredcustomers.Select(x => x.Cust_Phone).ElementAt(0);
id_num = filteredcustomers.Select(x => x.Cust_ID).ElementAt(0);
}
catch (Exception)
{
MessageBox.Show("Customer not found. If customer was recently added, try updating DB.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
DataAccess db = new DataAccess();
filteredcustomers = db.AllCustomersList();
ntn_num = "";
strn_num = "";
address = "";
phone_num = "";
}
}
On the user form side, "filteredcustomers" holds the list of data sent back, now here is the problem: I use the filteredcustomers list to extract the different column values like so:
address = filteredcustomers.Select(x => x.Cust_Address).ElementAt(0);
and then use them to populate the respective textboxes like:
Address_TxtBox.Text = address;
Everything works fine, but I don't want the userform to have these queries for all individual columns, because from what I've understood so far, this is bad programming and bad for security as well.
Can anyone guide me how I can keep the values in Data Access layer and just call them into my form?
I'm sorry if this is a long post, I'm just learning and wanted to be as detailed as possible.
You're already doing everything reasonably correctly as per how Dapper is to be used. Dapper doesn't maintain a local graph of entities from the db, track changes to it and automatically save them. If you want that, use something like EF
For dapper you retrieve data with a SELECT and send it back with an UPDATE
If you're only expecting one Customer for the name, do this:
var output = connection.QueryFirstOrDefault<Customer>($"SELECT * from `Customers` WHERE `Cust_Name` LIKE #n", new { n = name });
https://dapper-tutorial.net/queryfirst
This will return just one customer instance (or null; check it!) meaning you can tidy up your form code to:
c = db.FilteredCustomer(CustomerNameTxtBox_AutoComplete.Text);
ntn_num = c?.Cust_NTN;
strn_num = c?.Cust_STRN;
And so on
Your "if customer was recently added try updating db" doesn't really make sense- the query is done live, so the db is about as up to date as it can be
I am having difficulties UPDATING the databes via LINQ to SQL, inserting a new record works fine.
The code correctly inserts a new row and adds a primary key, the issue I am having is when I go to update (chnage a value that is already in the database) that same row the database is not updating, it is the else part of the code that does not work correctly. This is strange b/c the DB is properly connected and functioning through the fact that the DataContext inserts a new row with no issues. Checking the database confirms this.
This is the code,
using System;
using System.Collections.Generic;
using System.Linq;
using Cost = Invoices.Tenant_Cost_TBL;
namespace Invoices
{
class CollectionGridEvents
{
static string conn = Settings.Default.Invoice_DbConnectionString;
public static void CostDataGridCellEditing(DataGridRowEditEndingEventArgs e)
{
using (DatabaseDataContext DataContext = new DatabaseDataContext(conn))
{
var sDselectedRow = e.Row.Item as Cost;
if (sDselectedRow == null) return;
if (sDselectedRow.ID == 0)
{
sDselectedRow.ID = DateTime.UtcNow.Ticks;
DataContext.Tenant_Cost_TBLs.InsertOnSubmit(sDselectedRow);
}
else
{
// these two lines are just for debuging
long lineToUpdateID = 636154619329526649; // this is the line to be updated primary key
long id = sDselectedRow.ID; // this is to check the primary key on selected line is same
// these 3 lines are to ensure I am entering actual data into the DB
int? amount = sDselectedRow.Cost_Amount;
string name = sDselectedRow.Cost_Name;
int? quantity = sDselectedRow.Cost_Quantity;
sDselectedRow.Cost_Amount = amount;
sDselectedRow.Cost_Name = name;
sDselectedRow.Cost_Quantity = quantity;
}
try
{
DataContext.SubmitChanges();
}
catch (Exception ex)
{
Alert.Error("Did not save", "Error", ex);
}
}
}
}
}
And I am calling the method from this,
private void CostDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
CollectionGridEvents.CostDataGridCellEditing(e);
}
The lineToUpdateID is copied dirrectly from the database and is just there to check against the currently selected rows primary key is the same, so I know I am trying to update the same row.
I have looked through as many of the same type of issues here on SO , such as this one Linq-to-Sql SubmitChanges not updating fields … why?. But still no closer to finding out what is going wrong.
Any ideas would be much appreciated.
EDIT: Cost is just short hand of this using Cost = Invoices.Tenant_Cost_TBL;
You cannot do that. You need to get the record out of the database and then update that record. Then save it back. Like this:
else
{
// first get it
var query =
from ord in DataContext.Tenant_Cost_TBLs
where ord.lineToUpdateID = 636154619329526649
select ord;
// then update it
// Most likely you will have one record here
foreach (Tenant_Cost_TBLs ord in query)
{
ord.Cost_Amount = sDselectedRow.Cost_Amount;
// ... and the rest
// Insert any additional changes to column values.
}
}
try
{
DataContext.SubmitChanges();
}
catch (Exception ex)
{
Alert.Error("Did not save", "Error", ex);
}
Here is an example you can follow.
Or you can use a direct query if you do not want to select first.
DataContext.ExecuteCommand("update Tenant_Cost_TBLs set Cost_Amount =0 where ...", null);
Your object (Cost) is not attached to DB context. You should attach it then save changes. Check solution here
We have an email queue table in the database. It holds the subject, HTML body, to address, from address etc.
In Global.asax every interval, the Process() function is called which despatches a set number of emails. Here's the code:
namespace v2.Email.Queue
{
public class Settings
{
// How often process() should be called in seconds
public const int PROCESS_BATCH_EVERY_SECONDS = 1;
// How many emails should be sent in each batch. Consult SES send rates.
public const int EMAILS_PER_BATCH = 20;
}
public class Functions
{
private static Object QueueLock = new Object();
/// <summary>
/// Process the queue
/// </summary>
public static void Process()
{
lock (QueueLock)
{
using (var db = new MainContext())
{
var emails = db.v2EmailQueues.OrderBy(c => c.ID).Take(Settings.EMAILS_PER_BATCH);
foreach (var email in emails)
{
var sent = Amazon.Emailer.SendEmail(email.FromAddress, email.ToAddress, email.Subject,
email.HTML);
if (sent)
db.ExecuteCommand("DELETE FROM v2EmailQueue WHERE ID = " + email.ID);
else
db.ExecuteCommand("UPDATE v2EmailQueue Set FailCount = FailCount + 1 WHERE ID = " + email.ID);
}
}
}
}
The problem is that every now and then it's sending one email twice.
Is there any reason from the code above that could explain this double sending?
Small test as per Matthews suggestion
const int testRecordID = 8296;
using (var db = new MainContext())
{
context.Response.Write(db.tblLogs.SingleOrDefault(c => c.ID == testRecordID) == null ? "Not Found\n\n" : "Found\n\n");
db.ExecuteCommand("DELETE FROM tblLogs WHERE ID = " + testRecordID);
context.Response.Write(db.tblLogs.SingleOrDefault(c => c.ID == testRecordID) == null ? "Not Found\n\n" : "Found\n\n");
}
using (var db = new MainContext())
{
context.Response.Write(db.tblLogs.SingleOrDefault(c => c.ID == testRecordID) == null ? "Not Found\n\n" : "Found\n\n");
}
Returns when there is a record:
Found
Found
Not Found
If I use this method to clear the context cache after the delete sql query it returns:
Found
Not Found
Not Found
However still not sure if it's the root cause of the problem though. I would of thought the locking would definitely stop double sends.
The issue that your having is due to the way Entity Framework does its internal cache.
In order to increase performance, Entity Framework will cache entities to avoid doing a database hit.
Entity Framework will update its cache when you are doing certain operations on DbSet.
Entity Framework does not understand that your "DELETE FROM ... WHERE ..." statement should invalidate the cache because EF is not an SQL engine (and does not know the meaning of the statement you wrote). Thus, to allow EF to do its job, you should use the DbSet methods that EF understands.
for (var email in db.v2EmailQueues.OrderBy(c => c.ID).Take(Settings.EMAILS_PER_BATCH))
{
// whatever your amazon code was...
if (sent)
{
db.v2EmailQueues.Remove(email);
}
else
{
email.FailCount++;
}
}
// this will update the database, and its internal cache.
db.SaveChanges();
On a side note, you should leverage the ORM as much as possible, not only will it save time debugging, it makes your code easier to understand.
I have a Silverlight app using mvvm with ria services. I have a textbox on the view that the user puts in a Job Number and clicks find. This find button is using the ICommand in xaml to go here..
public ICommand FindJob
{
get
{
return new DelegateCommand(BeginFindJob, (o) => true);
}
}
public void BeginFindJob(object o)
{
if (!IsDesignTime)
{
IsLoading = true;
string jobnum = o.ToString();
OnPropertyChanged("IsLoading");
LoadOperation<Job> loadOp = _context.Load<Job>(_context.GetJobsByJobNumQuery(jobnum));
loadOp.Completed += new EventHandler(loadOp_Completed);
}
}
It uses the GetJobsByJobNumQuery in my ria service like so..
public IQueryable<Job> GetJobsByJobNum(string JobNum)
{
var query = ((from j in this.ObjectContext.Jobs
where j.JobNumber == JobNum
select j) as ObjectQuery<Job>).Include("JobHeadings").Include("JobContracts").Include("JobTags").Include("JobMarket");
return query;
}
Im wanting it to return all the information about the job, so i wrote the query above to include all those relationships. Putting a breakpoint on the linq query and looking at the results, it has exactly what I wont. All the fields, JobHeadings and Contracts are working and bringing back all the bindings to that job. So now I bring that query back in my viewmodel and populate the fields, like so..
void loadOp_Completed(object sender, EventArgs e)
{
try
{
LoadOperation<Job> loadOp = sender as LoadOperation<Job>;
if (!loadOp.HasError)
{
_job = loadOp.Entities.FirstOrDefault<Job>();
base.IsLoading = false;
base.ProgressBarVisibility = Visibility.Collapsed;
base.OnPropertyChanged("IsLoading");
base.OnPropertyChanged("ProgressBarVisibility");
base.OnPropertyChanged("CurrentJob");
}
}
catch (Exception ex)
{
}
}
My problem is, that no relationship data is coming back. All the basic Job info is coming back from the Job table in my db, but none of the info from the related tables is coming back in my viewmodel. Putting a bp in and looking at _job, which should contain everything, all the relationship tables JobHeading/JobContract say 'Enumeration yielded no results.'
So how is it not making its way back through to the viewmodel? What can i do to get the full query results put into the view/viewmodel so I can make changes?
Should the Includes not be higher up on the ObjectSet? like this:
public IQueryable<Job> GetJobsByJobNum(string JobNum)
{
var query = ((from j in this.ObjectContext.Jobs.Include("JobHeadings").Include("JobContracts").Include("JobTags").Include("JobMarket")
where j.JobNumber == JobNum
select j) as ObjectQuery<Job>);
return query;
}
This was a problem with the metadata service not picking up my latest table associations. After i went through that mess of regenerating the metadata, it was a simple matter of including and associating the correct keys. Thank you Quinton Bernhardt for your effort!