I am learning ADO.NET entity framework. This is my first example. The datagrid seems to take the length of customers and not the actual column that I want to display. The following is the code.
public partial class NewCustomerForm : Form
{
AFApp_H1Entities entities = new AFApp_H1Entities();
public NewCustomerForm()
{
InitializeComponent();
}
private void NewCustomerForm_Load(object sender, EventArgs e)
{
var query = from c in entities.Customers
where c.ZIP == "77080"
select c.CustomerName;
this.Controls.Add(dataGridView1);
dataGridView1.DataSource = query;
}
}
I was assuming I ll get some data which I am getting when running the same query in console application but in the grid its giving me the length of the names of customers. ?
This code will give you an entry for every row returned by your query which is query.length() if you require something different you need to filter your results given this code.
edit
OK so this was bothering me so I wrote a small test case, I am guessing you are trying to use the entity framework (if not please post back), you do say ADO but your variable naming does indicate this may not be just plain old ADO.
So Here is my database table create script.
USE [Customers]
GO
/****** Object: Table [dbo].[Customer] Script Date: 04/02/2012 11:16:02 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Customer](
[id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Zip] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Now in my appication is have this
public partial class Form1 : Form
{
private readonly CustomersEntities _entities =
new CustomersEntities(ConfigurationManager.ConnectionStrings["CustomersEntities"].ConnectionString);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var query =
_entities.Customers.Where(c => c.Zip == "12345").Select(
c => new CustomerDataBinding {Name = c.Name, Zip = c.Zip});
Controls.Add(dataGridView1);
dataGridView1.DataSource = query;
}
}
public class CustomerDataBinding
{
public string Zip { get; set; }
public string Name { get; set; }
}
I have three records in that database two with the same ZIP (it doesn't matter its not a real zip format) and one without (insert your own data with sql).
If i run this code I get two customers returned into the grid and not the three as is expected.
Just a note, this is not exactly the best way to acheive this but works for a basic example.
The app by the way looks on run
Related
I am trying to have SQLite create automatic timestamps with CURRENT_TIMESTAMP.
I took the liberty of using Google's code:
// roomVersion = '2.2.2'
#Entity
public class Playlist {
#PrimaryKey(autoGenerate = true)
long playlistId;
String name;
#Nullable
String description;
#ColumnInfo(defaultValue = "normal")
String category;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String createdTime;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String lastModifiedTime;
}
#Dao
interface PlaylistDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(playlist: Playlist): Long
}
This translates into an SQLite-Statement:
CREATE TABLE `Playlist` (
`playlistId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` TEXT,
`description` TEXT,
`category` TEXT DEFAULT 'normal',
`createdTime` TEXT DEFAULT CURRENT_TIMESTAMP,
`lastModifiedTime` TEXT DEFAULT CURRENT_TIMESTAMP
)
I did make one insert:
mDb.playListDao().insert(Playlist().apply { name = "Test 1" })
But the timestamps are always Null.
With the DB Browser for SQLite I added another entry, here I get timestamps.
How do I insert without a Null-Timestamp in room?
(Info: createdTime is also always the same as lastModifiedTime. I think this has to be done with triggers in SQLite, but that is a different problem not to be discussed here).
You don't need to use another class, you can use #Query as an alternative to the convenience #Insert.
as per :-
There are 4 type of statements supported in Query methods: SELECT, INSERT, UPDATE, and DELETE.
Query
e.g.
#Query("INSERT INTO test_table001 (name) VALUES(:name) ")
void insert(String name);
You are also not limited to CURRENT_TIMESTAMP as the only means of getting the current timestamp you can use embedded datetime functions (as is shown below), which can store the value more efficiently and also be more flexible e.g. you could adjust the current time using modifiers such as '+7 days'.
If you consider the following :-
#Entity(tableName = "test_table001")
public class TestTable001 {
#PrimaryKey
Long id;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String dt1;
#ColumnInfo(defaultValue = "(datetime('now'))")
String dt2;
#ColumnInfo(defaultValue = "(strftime('%s','now'))")
String dt3;
String name;
}
Note that the inefficient autogenerate = true has not been used BUT as will be shown you can still have an SQLite assigned id (note that you must use the type Long/Integer as opposed to long or int)
Also note the alternative ways of getting the current date time (the latter being more efficient as the value will ultimately be stored as an Integer (max 8 bytes) rather than a more byte hungry String).
With a Dao as :-
#Dao
public interface TestTable001Dao {
#Insert()
long insert(TestTable001 testTable001);
#Query("INSERT INTO test_table001 (name) VALUES(:name) ")
long insert(String name);
#Query("SELECT * FROM test_table001")
List<TestTable001> getAllTestTable001();
}
And the following to test/demonstrate :-
public class MainActivity extends AppCompatActivity {
AppDatabase mRoomDB;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRoomDB = Room.databaseBuilder(this,AppDatabase.class,"testdb")
.allowMainThreadQueries()
.build();
TestTable001 tt01 = new TestTable001();
tt01.setName("tt01");
mRoomDB.useTestTable001().insert(tt01);
mRoomDB.useTestTable001().insert("tt02");
logAllTestTable001();
}
private void logAllTestTable001() {
for (TestTable001 tt: mRoomDB.useTestTable001().getAllTestTable001()) {
Log.d(
"TTINFO",
"ID = " + tt.getId() +
" Name = " + tt.getName() +
" Date1 = " + tt.getDt1() +
" Date2 = " + tt.getDt2() +
" Date3 = " + tt.getDt3());
}
}
}
The result is :-
2019-12-14 03:18:32.569 D/TTINFO: ID = 1 Name = tt01 Date1 = null Date2 = null Date3 = null
2019-12-14 03:18:32.569 D/TTINFO: ID = 2 Name = tt02 Date1 = 2019-12-13 16:18:32 Date2 = 2019-12-13 16:18:32 Date3 = 1576253912
Found it. Did not read the manual.
You have to create a 2nd class without the auto-set fields to insert.
public class NameAndDescription {
String name;
String description
}
I think, this is not a good idea.
If you have an autoincrement field in the DB it will get an automatically updated value when you pass 0.
Likewise the default value of the timestamp should be used when passing null or "".
I found the best solution was creating an abstract Dao that implemented the insert and update methods. I didn't get the default value to work (perhaps I was doing something wrong). Take a look at my answer here: How to implement created_at and updated_at column using Room Persistence ORM tools in android
Just started learning Dapper. I have an ADO.NET background. Using a demo I downloaded, I can insert/delete data from a webform into a MySql table just fine. This, however, I have searched all morning on.
In retrieving a single row from the db by ID, it doesn't return a LIST<>, it seems to be just an object (using code from the demo I downloaded). The query works, I get the object back. It has the fields: "ProductID, Description and Price".
The only way I could get the values to those three fields was like this:
System.Reflection.PropertyInfo pi = Product.GetType().GetProperty("ProductID");
System.Reflection.PropertyInfo desc = Product.GetType().GetProperty("Description");
System.Reflection.PropertyInfo price = Product.GetType().GetProperty("Price");
int _ProductID = (int)(pi.GetValue(Product, null));
string _Description = (string)(desc.GetValue(Product, null));
decimal _Price = (decimal)(price.GetValue(Product, null));
This works and gets the correct values for the three fields.
I'm used to looping through DataTables, but I just think there is probably a better way to get those values.
Is this the correct way to do this or am I missing something? I did actually read documentation and mess with this all morning before asking, too.
Some of the things I looked at seem to be very complex. I thought Dapper was supposed to simplify things.
OK, Thanks Marc. It was difficult for me to see what was supposed to be in the Dapper class files and what was supposed to be in my code behind. The original demo way of getting a product by ID had the query as .FirstOrDefault();
I changed everything to return a List<> and it all worked. I'm sure my ADO.NET is showing, but this works. In Dapper class files:
public List<Product> ProductAsList(int Id)
{
return this._db.Query<Product>("SELECT * FROM Cart_product WHERE ProductID=#Id", new { Id = Id }).**ToList()**;
}
This is just getting one row that matched the ProductID.
In page codebehind:
protected void CartItemAdd(string ProductId) // passing it the selected ProductID
{
var results = cartservice.ProductAsList(Convert.ToInt32(ProductId));
// returns that one row using Dapper ProductAsList(ProductId)
int _ProductId = 0;
string Description = string.Empty;
decimal Price = 0;
// Loop through the list and get the value of each item:
foreach (Product obj in results)
{
_ProductId = obj.ProductID;
Description = obj.Description;
Price = obj.Price;
}
// Using Dapper to insert the selected product into the shopping cart (table):
String UserName = "jbanks";
cartitem = new CartItem();
cartitem.ProductID = _ProductId;
cartitem.Quantity = 1;
cartitem.Description = Description;
cartitem.Price = Price;
cartitem.Created = DateTime.Now;
cartitem.CreatedBy = UserName;
result = cartservice.AddCartItem(cartitem);
if (result)
{
lblMessage.Text = string.Empty;
lblMessage.Text = "Successfully added a cart item";
}
}
}
It does indeed look up the product from one table and insert a selected item into another table.
Thanks again!
The main Query<T> API returns an IEnumerable<T>, which often will be a List<T>; the AsList<T>() extension method can get it back to a list without a copy, but either way: they are just T, for whatever T you asked for. If you asked for Query<Product>, then: they should be Product instances:
var results = connection.Query<Product>(someSql, someArgs); // perhaps .AsList()
foreach (Product obj in results) { // "var obj" would be fine here too
// now just use obj.ProductID, obj.Description and obj.Price
}
If that didn't work: check that you used the <T> version of Query. There is a non-generic variant too, which returns dynamic. Frankly, you should almost always use the <T> version.
Note: I'm assuming that somewhere you have something like
class Product {
public int ProductID {get;set;}
public string Description {get;set;}
public decimal Price {get;set;}
}
I call
public bool CheckIfFinished ( Guid pid, int sid )
{
// pid: guid corresponding to partner
// sid: survey id
bool finished = false;
using (SqlCommand cmd = new SqlCommand("CheckIfFinished", this._Conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#pid", pid);
cmd.Parameters.AddWithValue("#sid", sid);
this._Conn.Open();
using ( SqlDataReader dataReader = cmd.ExecuteReader() )
{
while (dataReader.Read())
{
finished = dataReader.GetByte(0) == 1 ? true : false;
}
}
this._Conn.Close();
}
return finished;
}
which calls the sproc
GO
-- Create sproc for returning whether a partner has marked their survey as finished
CREATE PROCEDURE CheckIfFinished
#pid UNIQUEIDENTIFIER,
#sid INT
AS
BEGIN
SELECT 1 FROM FinishedMappings WHERE partner_id=#pid AND survey_id=#sid
END
which uses the table FinishedMappings which does exist because it was defined with
CREATE TABLE FinishedMappings (
partner_id UNIQUEIDENTIFIER,
survey_id INT,
PRIMARY KEY (partner_id,survey_id),
FOREIGN KEY (partner_id) REFERENCES Partners(id),
FOREIGN KEY (survey_id) REFERENCES Surveys(id)
);
right above the sproc. Why am I getting the error?
If the table was created using a different user to the one that you are executing the sp with then a fully qualified name could be needed. I would check this first as dimasun suggested in his first reply.
Also check to see if you can execute the sp directly in SQL management studio. If this doesn't work then you can focus on the code in the sp
I have an application which stores tasks and I want to add attachments to those tasks.
I have tried three different ways of doing this and don't know if any of them are correct and am looking for advice on where to go:
For example, simplified I have used a table:
+----------------------------------------------------------------------------+
| TaskID Description attachmentString |
+----------------------------------------------------------------------------+
| 1 Task1 "FileName1:::fileLocation;FileName2:::fileLocation" |
| 2 Task2 "FileName3:::fileLocation;FileName4:::fileLocation" |
+----------------------------------------------------------------------------+
This is similar to how profile data is stored in ASP.NET membership.
I have also tried:
+---------------------------+
| TaskID Description |
+---------------------------+
| 1 Task1 |
| 2 Task2 |
+---------------------------+
+------------------------------------------------------+
| AttachmentId Description Location TaskId |
+------------------------------------------------------+
| 1 FileName1 FileLocation 1 |
| 2 FileName2 FileLocation 1 |
+------------------------------------------------------+
If I use the first option, I can just select tasks and get all the attachment data in one SQL call; but it seems cluncky to me to have to then parse the string. Its also not very "relational"
However using an attachment Id, if I want to get the attachments, I either JOIN both tables on attachmentId and then have number of attachments x number of tasks returned. I can have up to 5 attachments so for 50 tasks, it could return 250 rows of which the first columns (from the task table side of the JOIN) are repeated and this seems like a waste. Obviously I have a little more than just description in my table!!!
I have also considered just getting the task data and then just getting the attachment data separately and then joining them in my application. This returns less data than the second option, but requires two calls to the database and that seems wrong too.
I am doing this wrong? Is there a better way? Does anyone have any thoughts on the best way to do this.
I'm not very confident with SQL and maybe I have missed something huge so any pointers would be gratefully received.
The right design is obviously two tables. Having only one table violates the first normal form.
Relating to the load problem, both approaches are correct.
Joining the tables in the sql statement is what most ORM's do to eagerly load related objects. Obviously there is some network traffic overhead, but I think it is acceptable.
Executing two separate sql statements is also correct. You can send them together in one batch to SQL Server to save roundtrips. It has a disadvantage although, you need to perform the join at the client side.
So, are you willing to write more code to save some network traffic?
EDIT:
Given the following table and data:
CREATE TABLE Tasks
(
TaskId int IDENTITY(1,1) PRIMARY KEY,
TaskDescription nvarchar(500) NOT NULL
)
CREATE TABLE TaskAttachments
(
AttachmentId int IDENTITY(1,1) PRIMARY KEY,
TaskId int NOT NULL REFERENCES Tasks(TaskId),
[FileName] nvarchar(500) NOT NULL,
[FileLocation] nvarchar(500) NOT NULL
)
GO
INSERT INTO Tasks VALUES
('Task1'), ('Task2')
INSERT INTO TaskAttachments VALUES
(1, 'FileName1', 'File location 1'),
(1, 'Filename2', 'File location 2'),
(2, 'FileName3', 'File location 3'),
(2, 'Filename4', 'File location 4')
The following classes:
public class TaskAttachment
{
public int AttachmentId { get; set; }
public string FileName { get; set; }
public string FileLocation { get; set; }
}
public class AppTask
{
public int TaskId { get; set; }
public string TaskDescription { get; set; }
public List<TaskAttachment> Attachments { get; set; }
public AppTask()
{
this.Attachments = new List<TaskAttachment>();
}
}
The following class loads the tasks with its attachments by executing two select statements in one single batch:
public class DataLayer
{
private readonly SqlConnection connection;
public DataLayer(SqlConnection connection)
{
this.connection = connection;
}
public List<AppTask> GetTasks()
{
var commandText = #"
SELECT TaskId, TaskDescription FROM Tasks;
SELECT AttachmentId, TaskId, [FileName], FileLocation FROM TaskAttachments;
";
using (var cmd = new SqlCommand(commandText, connection))
using (var reader = cmd.ExecuteReader())
{
var tasks = new List<AppTask>();
while (reader.Read())
{
var task = new AppTask
{
TaskId = reader.GetInt32(0),
TaskDescription = reader.GetString(1)
};
tasks.Add(task);
}
var taskDic = tasks.ToDictionary(x => x.TaskId);
reader.NextResult();
while (reader.Read())
{
var attachment = new TaskAttachment
{
AttachmentId = reader.GetInt32(0),
TaskId = reader.GetInt32(1),
FileName = reader.GetString(2),
FileLocation = reader.GetString(3)
};
var task = taskDic[attachment.TaskId];
task.Attachments.Add(attachment);
}
return tasks;
}
}
}
You can use the above class like this:
using (var cn = new SqlConnection("Data Source=(local);Initial Catalog=Tests;Integrated Security=SSPI"))
{
var dataLayer = new DataLayer(cn);
cn.Open();
var tasks = dataLayer.GetTasks();
}
I have a Drop-Down list in my InfoPath form and I am loading some other fields based on the selection of the drop-down list. so that I have written code as follows for the "changed" event of the drop down list.
public void ProjectName_Changed(object sender, XmlEventArgs e)
{
string projectId = e.NewValue;
dataQueryConnection = (AdoQueryConnection)this.DataConnections["ProjectInformation"];
dataQueryConnection.Command = dataQueryConnection.Command + string.Format(" WHERE ProjectId = '{0}'", projectId);
dataQueryConnection.Execute();
}
For the first time when I change an item in the drop down list its working fine but for the subsequent changes of items(2nd time, etc..) its give the following error,
The query cannot be run for the
following DataObject:
ProjectInformation InfoPath cannot run
the specified query.
[0x80040E14][Microsoft OLE DB Provider
for SQL Server] Incorrect syntax near
the keyword 'WHERE'.
And this is the reason, for the second time,
dataQueryConnection.Command = select
"EmployeeID","Accountname","ProjectName","ProjectId","ProjectRole","BillableUtilization","ClientName","BillingCode","BUHead"
from "TRF"."hrt_vw_ProjectInformation"
as "hrt_vw_ProjectInformation" WHERE
ProjectId = '3072507' WHERE ProjectId
= '3076478'
subsequent event firing biding the WHERE clause every time with the previous executed query.
How I can over come from this issue?
Store the initial command string in a global variable in your code (in the loading event). Then in your Changed function append to the global variable instead of to the previous value of the command.
string OrigString
public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
OrigString = dataQueryConnection.Command;
}
public void ProjectName_Changed(object sender, XmlEventArgs e)
{
string projectId = e.NewValue;
dataQueryConnection = (AdoQueryConnection)this.DataConnections["ProjectInformation"];
dataQueryConnection.Command = OrigString + string.Format(" WHERE ProjectId = '{0}'", projectId);
dataQueryConnection.Execute();
}