Passing a list of int to dapper with mySQL - c#

I have a current working solution that is doing a new db call for each project Id in a list and I am trying to do a single call instead that returns data from multiple projects.
To do this I am trying to pass a list of project Id's into a Dapper Query that hits a MySQL database. I either get an error of operand should contain 1 column(s) or I get the first result back and not one per projectId that is in the database.
The current c# code I am using is
public List<ProjectPortalManager> GetPPTech(IEnumerable<int> projIds)
{
string sql = #"SELECT tProject.ProjectID,
tProject.ProjectName,
tProject.PMUserID,
if(cast(tproject.dateinit as char) = '0000-00-00 00:00:00',null,tproject.dateinit) as DateInit,
tproject.comments,
tproject.ProjectNumber,
c.LName,
c.FName,
c.orgid,
c.orgname as organization,
c.Email,
c.Phone
From tProject left Join tContacts c on tProject.PMUserID = c.UserId
Where tProject.ProjectID in (#ProjIds);";
try
{
List<ProjectManager> pms = Conn.Query<ProjectManager>(sql, new { ProjIds = new[] { projIds } }).ToList();
return pms;
}
catch (Exception ex)
{
ErrorReport.ReportError(ex);
}
return new List<ProjectPortalManager>();
}
This does not error out but returns 0 results. When running the query in MySQL Workbench I do get one result back. However I am expecting several results. The SQL I run in workbench is:
SET #projIds = ('28, 99, 9');
SELECT tProject.ProjectID,
tProject.ProjectName,
tProject.PMUserID,
if(cast(tproject.dateinit as char) = '0000-00-00 00:00:00',null,tproject.dateinit) as DateInit,
tproject.comments, tproject.ProjectNumber,
c.LName,
c.FName,
c.orgid,
c.orgname as organization,
c.Email,
c.Phone
From tProject left Join tContacts c on tProject.PMUserID = c.UserId
where tProject.ProjectID IN (#projIds);
I have verified that all the Id numbers used do exists in the database.
There seems to be conflicting information online about how to do this but I have not found a solution that seems to work.

Don't put parentheses around the IN if you want Dapper to expand it to a list of parameters and populate them
where tProject.ProjectID IN #PIDs
Suppose you'd passed an array of size 3 in new { PIDs = projIds.ToArray() } - Dapper would effectively transform your SQL to:
where tProject.ProjectID IN (#PIDs1, #PIDs2, #PIDs3)
then behave as if you'd passed new { PIDs1 = projIds[0], PIDs2 = projIds[1], PIDs3 = projIds[2] }

Related

Syntax for calling a stored procedure in .NET Core without parameters

I have created a stored procedure that returns a recordset model which joins several different tables in a single database. I have scoured the internet for information regarding "proper syntax for calling a stored procedure from mvc 6 C# without parameters" and have learned several things.
Firstly, there used to be what looked like understandable answers, to wit: "ExecuteSqlCommand " and "ExecuteSqlCommandAsync ", which, evidently are no longer used. Their replacements are explained here: [https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes#fromsql][1]. They seem to be limited to "FromSql/FromSqlRaw" (which returns a recordset model) and "ExecuteSqlRaw/ExecuteSqlRawAsync()" which returns an integer with a specified meaning.
The second thing is that, everywhere examples of "before and after" are given, the example without parameters are skipped (as in all of the MS docs).
And thirdly, all of the examples that return a recordset model with data seem tied to a table, such as:
"var students = context.Students.FromSql("GetStudents 'Bill'").ToList();" And, as stored procedures are stored in their own directories, can reference any tables, multiple tables, or even no tables, I don't understand this relationship requirement in calling them.
(such as here:
[https://www.entityframeworktutorial.net/efcore/working-with-stored-procedure-in-ef-core.aspx][2]
var students = context.Students.FromSql("GetStudents 'Bill'").ToList();)
Or maybe they are models (since in this entity framework, everything seems to have the exact same name)... But what if your stored procedure isn't returning a recordset tied to a model. Do you have to create a new model just for the output of this stored procedure? I tried that, and it didn't seem to help.
So, my fundamental question is, how do I call a stored procedure without any parameters that returns a recordset model with data?
return await _context.(what goes here?).ExecuteSqlRaw("EXEC MyStoredProcedure").ToListAsync();
return await _context.ReturnModel.ExecuteSqlRaw("EXEC? MyStoredProcedure").ToListAsync();
Updated Code:
Added Model
public class InquiryQuote
{
public Inquiry inquiry { get; set; }
public int QuoteID { get; set; } = 0;
}
Added DBSet:
public virtual DbSet<InquiryQuote> InquiryQuotes { get; set; } = null!;
And updated the calling controller:
// GET: api/Inquiries
[HttpGet]
public async Task<ActionResult<IEnumerable<InquiryQuote>>> GetInquiries()
{
//return await _context.Inquiries.ToListAsync();
//return await _context.Inquiries.Where(i => i.YNDeleted == false).ToListAsync();
// var IQ = await _context.InquiryQuotes.FromSqlRaw("GetInquiryList").ToListAsync();
var IQ = await _context.InquiryQuotes.FromSqlRaw("EXEC GetInquiryList").ToListAsync();
return Ok(IQ);
}
Both versions of "IQ" return the same results:
System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.
at Microsoft.Data.SqlClient.SqlBuffer.ThrowIfNull()
at Microsoft.Data.SqlClient.SqlBuffer.get_Int32()
at Microsoft.Data.SqlClient.SqlDataReader.GetInt32(Int32 i)
at lambda_method17(Closure , QueryContext , DbDataReader , Int32[] )
...
[And here is the image of the stored procedure run directly from my development site:][1]
UPDATE (And partial answer to the question in the comments):
I am using the Entity Framework, and will be performing data manipulation prior to returning the newly created InquiryQuotes model from the stored procedure to be used in several views.
Why I am getting a SQL error thrown in postman (System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.) when calling the stored procedure directly from visual studio returns a "dataset" as shown in my image. Does it have something to do with additional values being returned from the stored procedure that are not being accounted for, like "DECLARE #return_value Int / SELECT #return_value as 'Return Value' ", or is this just a feature of executing it from VS. Since it has no input params, where is the NULL coming from?
[1]: https://i.stack.imgur.com/RJhMr.png
I seem to have found the answer (but still don't know the why...)
I started breaking it down bit-by-bit. The procedure ran on sql, and ran remotely on Visual Studio when directly accessing sql, but not when called. So I replaced the complex stored procedure with a simple one that returned all fields from the inquiry table where the id matched an input variable (because I had LOTS) of examples for that.
Stored Procedure:
CREATE PROCEDURE [dbo].[GetInquiry]
#InquiryID int = 0
AS
BEGIN
SET NOCOUNT ON
select i.*
FROM dbo.Inquiries i
WHERE i.YNDeleted = 0 AND i.InquiryId = #InquiryID
END
And the controller method (with the InquiryQuote model modified to eliminate the "quote" requirement:
public async Task<ActionResult<IEnumerable<InquiryQuote>>> GetInquiries()
{
//return await _context.Inquiries.ToListAsync();
//return await _context.Inquiries.Where(i => i.YNDeleted == false).ToListAsync();
SqlParameter ID = new SqlParameter();
ID.Value = 0;
var IQ = _context.InquiryQuotes.FromSqlRaw("GetInquiryList {0}", ID).ToList();
//var IQ = await _context.InquiryQuotes.FromSqlRaw("dbo.GetInquiryList").ToListAsync();
return IQ;
}
And (after a bit of tweaking) it returned a JSON result of the inquiry data for the ID in Postman.
{
"inquiryId": 9,
(snip)
"ynDeleted": false
}
So, once I had something that at least worked, I added just the quote back in to this simple model and ran it again
select i.*, 0 AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0 AND i.InquiryId = #InquiryID
(I set the QuoteID to 0, because I had no data in the Quotes table yet).
AND the Model:
[Keyless]
public class InquiryQuote
{
public Inquiry inquiry { get; set; }
public bool QuoteID{ get; set; } = 0;
}
And ran it again, and the results were astonishing:
{
inquiry:{null},
QuoteID:0
}
I still don't understand why, but, evidently it must have been because of my LEFT join of the inquiryID from the Inquiry Table left joined with a null table returned null results - but when running on SQL, results were returned... The join in sql worked and returned results, but somewhere between sql and the API, the data was being nullified...
To test this theory, I updated my InquiryQuote model to put the "inquiry" data and "quoteid" at the same level, to wit:
public class InquiryQuote
{
public int InquiryId { get; set; } = 0;
(snip)
public Boolean YNDeleted { get; set; } = false;
public int QuoteID { get; set; } = 0;
}
and the entire results set was null...
So at that point, I figured it must have something to do with that LEFT JOIN with a table with no records. So I added a blank (default) entry into that table and, voila, the data I was expecting:
{
"inquiryId": 9,
(snip)
"ynDeleted": false,
"quoteID": 0
}
So, now I have a working way to call a stored procedure with one parameter!!
I then updated the stored procedure to deal with nulls from the database as so:
select i.*, ISNULL(q.QuoteId,0) AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0 AND i.InquiryId = #InquiryID
And now am returning correct data.
I still don't know why the stored procedure runs in sql and returns data, but returns a SQL error when run from the controller. That will require a deeper dive into the interconnectivity between the sql and the API and how errors are passed between the two. And I am pretty certain I will be able to figure out how to convert this call into one that uses no parameters.
Thank you everyone for your help.

C# retrieve data from SQL Server using SqlDataReader to Master Detail List

I have Master and Detail classes:
class Master
{
public int ID { get; set; }
public string Name { get; set; }
public List<Detail> Details { get; set; }
}
class Detail
{
public Description { get; set; }
public Amount { get; set; }
}
I use below approach and working fine now.
List<Master> result = new List<Master>();
// SQL Connection
string sqlCommand = "SELECT * FROM Master LEFT JOIN Detail on Master.ID = Detail.ID";
using (System.Data.SqlClient.SqlDataReader dr = db.DbDataReader as System.Data.SqlClient.SqlDataReader)
{
if (dr.HasRows)
{
Master LastMaster = null;
while (dr.Read())
{
if (LastMaster == null || Convert.ToInt(dr["ID"]) != LastMaster.ID)
{
Master h = new Master();
h.ID = Convert.ToInt(dr["ID"]);
h.Name = Convert.ToString(dr["Name"]);
result.Add(h);
LastMaster = h;
}
if (dr["Description"] == DBNull.Value)
continue;
if (h.Detail == null)
h.Detail = new List<Detail>();
Detail d = new Detail();
d.Description = dr["Description"] as string;
d.Amount = Convert.ToDouble(dr["Amount"]);
LastMaster.Detail.Add(d);
......
}
}
.....
}
Is there any better approach to fill list of list objects in C# ? I appreciate any suggestion. Thanks.
You can use Dapper (a micro ORM) for your scenario. Below is a sample code
const string createSql = #"
create table #Users (Id int, Name varchar(20))
create table #Posts (Id int, OwnerId int, Content varchar(20))
insert #Users values(99, 'Sam')
insert #Users values(2, 'I am')
insert #Posts values(1, 99, 'Sams Post1')
insert #Posts values(2, 99, 'Sams Post2')
insert #Posts values(3, null, 'no ones post')";
using(var connection = new SqlConnection("database connection string"))
{
connection.Execute(createSql);
try
{
const string sql =#"select * from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id";
var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post; }).ToList();
}
catch(Exception ex){}
}
Ibram commented about EF and Dapper and Abu gave an example for Dapper (but I'm not sure it demos generating a graph with a single master and multiple detail per master, as you have - dapper can do so if you want to explore it)
In EF we could do something like:
install EF core power tools - as you have a db already we will use it to generate classes from. This operation can just be done with the command line but EFCPT makes a lot of operations easier
right click your project, choose EF Core Power Tools .. Reverse Engineer
fill in a new connection string detail
choose the database objects you wish to turn into classes
set other options as appropriate (you can find out more about them later, maybe only use the pluralize one for now, if your db tables are like Orders, Customers, Companies and you want your classes called Order/Customer/Company (classes should not have plural names). Tick on "put connectionstring in code" for now- you can remove it to config file later
finish. Eventually you'll get some classes and a context that has a load of code in OnModelCreating that lays out a description of everything in the tables, the columns, keys, relationships..
Now you can run some query like:
var c = new YourContext();
var ms = c.Masters.Include(m => m.Details).ToList();
That's basically the equivalent of what you posted
You can get more trick by shaping a more involved linq query:
var q = c.Masters.Include(m => m.Details)
.Where(m => m.Name.StartsWith("Smith"))
.OrderBy(m => m.Name);
var ms = q.ToList();
It will be translated into something like
SELECT * FROM master join detail on ...
WHERE name LIKE 'Smith%'
ORDER BY m.name
You can see the generated query if you inspect the DebugView property of q
You could make changes:
ms[0].Details.Clear(); //causes delete of all details for this master
ms[1].Details.Add(new Detail { someprop = some value}); //causes insert of new details for this master
ms[2].Name = "Hello"; //causes update of this master name
c.SaveChanges(); //carries out the above, in sql, to affect the db
When you manipulate the returned objects and save, EF will delete/insert/update as appropriate to sync the db to what happened to the objects. It is important that you understand that EF tracks what happens to all the objects it creates, so that it can do this
When would you use EF and when would you use Dapper? Well, it doesn't have to be mutually exclusive; you can use them in the same project. Generally I'd say use EF (or some other ORM like it - nHibernate is another popular one, works on a similar concept of translating linq expressions to sql and tracking the data back into an object) for stuff where the sql is so simple that it's a productivity boost to not have to write it, track it, and write the changes back. What it is not intended for, is forming as hoc queries that don't map well to client side objects. For that you can use Dapper, or you could form client side objects and add them to EF's model and then run raw sql that populates them. Dapper is fast, because it doesn't do any of that tracking changes, mapping or wiring up complex object graphs; you do all that manually. Dapper makes a convenient abstraction over raw sql and creates classes, but EF goes much further; it comes at a cost - EF is highly convenient but much more heavy weight.

SQL query returns value in SSMS, value is DBNull in DataTable

I have this query that I crafted in SQL Server Management Studio to get the definition of a stored procedure in a database.
Select [definition], [uses_ansi_nulls], [uses_quoted_identifier],
[is_schema_bound], [uses_database_collation], [is_recompiled],
[null_on_null_input], [execute_as_principal_id],
[uses_native_compilation]
From [sys].[sql_modules]
Where [object_id] = #object_Id
In SSMS, if I Declare #object_Id int = <storedproc's object_id> and run it, I get back the definition value (the SQL code) and the various other attributes.
In my app, I have this code. The above SQL query is a resource for the project.
static void RenderDefinition(int objectId, XmlElement parentElement)
{
var dt = new DataTable();
using (var da = new SqlDataAdapter(Resources.Commands.GetDefinition, connectionString))
{
da.SelectCommand.Parameters.Add("#object_Id", SqlDbType.Int).Value = objectId;
da.Fill(dt);
}
var row = dt.Rows[0];
// Create object element
var e = parentElement.AppendChild(parentElement.OwnerDocument.CreateElement("definition")) as XmlElement;
foreach (DataColumn col in dt.Columns)
{
var value = row.ToXmlString(col);
if (string.IsNullOrWhiteSpace(value)) continue;
if (col.ColumnName == "definition")
{
// The SQL code
e.InnerText = value; // <-- Value is always DBNull, never the actual value
}
else
{
// Other defining attributes
e.SetAttribute(col.ColumnName, value);
}
}
}
This generates the following XML:
<definition uses_ansi_nulls="true" uses_quoted_identifier="true" is_schema_bound="false" uses_database_collation="false" is_recompiled="false" null_on_null_input="false" uses_native_compilation="false"></definition>
I've tried the query as is. I've tried converting the definition column to a VARCHAR(MAX) and to a VARCHAR(8000).
Any thoughts?
try selecting from different sys table. E.g. :
SELECT DISTINCT SCHEMA_NAME(o.schema_id),o.name,c.[text]
FROM syscomments AS c
INNER JOIN sys.objects AS o ON c.id = o.[object_id]
INNER JOIN sys.schemas AS s ON o.schema_id = s.schema_id
WHERE object_id = <your id>
another query that might help you:
SELECT so.[name], m.[definition]
FROM sys.objects as so
inner join sys.sql_modules as m on m.object_id = so.object_id
WHERE so.[type] = 'P' AND so.object_id = <your id>;
here m.[definition] doesn't have length meaning it is as big as possible. In case it will return several rows for the same SP you will need to add another loop to your application and concatenate strings.
Let me know if it works
It turns out that #DaveBrown had the right answer: It was a permissions issue, though it wasn't behaving in the way I thought it would.
I added LEN([definition]) to the query. The thought being that if it was problem in ADO.Net pulling down a 24,000 character sproc definition, I'd still get the character count of the definition. If it returned a NULL value, then it was something going on in SQL Server.
Select [definition], LEN([definition]) [definition_length], [uses_ansi_nulls], [uses_quoted_identifier],
[is_schema_bound], [uses_database_collation], [is_recompiled],
[null_on_null_input], [execute_as_principal_id],
[uses_native_compilation]
From [sys].[sql_modules]
Where [object_id] = #object_Id
When I stopped at a break point, LEN([definition]) was null.
Turns out, someone who remain nameless to protect the guilty, does not have the same permissions in the environment we want to document as she has in the initial environment. The code works as written in one of the environments where I have authorial permissions for stored procedures and other other modules.
This ultimately comes down to an unexpected behavior due to permissions. I expected for SQL Server to raise an error when trying to query the definition rather than return null.

Why does only the last item of comma-delimited list get processed by stored proc when called from C# web form?

I am using C# in Visual Studio 2013 and SQL Server 2012.
When the user of my ASP.NET web form enters a newline-separated list of codes and clicks submit, the code behind should read the values, concatenate them into a comma-delimited string, and pass the string to a method that calls a stored proc. The stored proc parses out the values and sets the active field to 1 on each record with a matching code.
The Product table is defined as:
id (PK, int, not null),
name (varchar(20), not null),
code (varchar(20), null),
active (bit, not null)
The Product table contains the following five records:
id name code active
-- ---- ---- ------
1 Product 1 AAA 0
2 Product 2 BBB 0
3 Product 3 CCC 0
4 Product 4 DDD 0
5 Product 5 EEE 0
I created the following stored proc:
CREATE PROCEDURE [dbo].[MarkListAsActive]
#codeList varchar(MAX)
AS
UPDATE
dbo.Product
SET
active = 1
WHERE
code IN (SELECT val FROM dbo.f_split(#codeList, ','))
dbo.f_split handles parsing the comma-delimited string. I copied it from this post: https://stackoverflow.com/a/17481595/2677169
If I execute the stored proc in SQL Server Management Studio, all five records get updated (as expected).
DECLARE #return_value int
EXEC #return_value = [dbo].[MarkListAsActive]
#codeList = N'AAA,BBB,CCC,DDD,EEE'
SELECT 'Return Value' = #return_value
GO
However, if I call the stored proc from the code behind of my .aspx page, only the last item in the list gets marked as active.
protected void SubmitButton_Click(object sender, EventArgs e)
{
string[] codeArray;
char separator = '\n';
OutputLabel.Text = "";
codeArray = ProductCodeTextBox.Text.Split(separator);
OutputLabel.Text += "The products with the following codes were marked as active:<br />";
string codes = "";
// TODO: Replace with regex that changes newlines to commas
for (int i = 0; i < codeArray.Length; i++)
{
codes += codeArray[i] + ",";
OutputLabel.Text += codeArray[i] + "<br />";
}
codes = codes.Substring(0, codes.Length - 1);
Utilities.Log(codes);
DataAccess.MarkListAsActive(codes);
}
public static void MarkListAsActive(string codeList)
{
try
{
string connectionString = ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand("[dbo].[MarkListAsActive]", conn)
{
CommandType = CommandType.StoredProcedure
})
{
conn.Open();
command.Parameters.Add("#codeList", codeList);
command.ExecuteNonQuery();
conn.Close();
}
}
catch (Exception ex)
{
Utilities.Log(String.Format("Error in MarkListAsActive: {0}\n{1}", ex.Message, ex.StackTrace));
}
return;
}
Note that I verified that the string being passed to MarkListAsActive() is correct.
Another approach: I tried looping through the codeArray and calling MarkListAsActive() for each item. Even this brute force (and inefficient) approach only updated the last item in the array.
Yet another aproach: I also tried a table valued parameter, but it too only updated the record corresponding to the last item in the input.
Thank you in advance for your help.
Change the IN clause to (SELECT val FROM dbo.f_split(#codeList, ',') where val is not null).
If that doesn't work follow these steps:
(You should know how to debug something like this anyway.)
First, use SQL Profiler to see what exactly is submitted to the database. See here for how to do that - its not as bad as it looks and is invaluable. If not what is expected, you have a problem on the front end.
Second, if the call to the database looks good then take the parameter you see in SQL Profiler and execute dbo.f_split() using it to see if you get back what you think you should.
Last, change your UPDATE statement into a SELECT statement and run it with what you did in the second step and see if you get back something that looks correct.
One of those steps will not return what is expected and that will lead you to the problem.
Your where clause is incorrect. You cannot do
... code in (select val ...
You need to join the result from the f_split function to the table.
For example:
UPDATE p
SET p.active = 1
FROM dbo.Product p inner join dbo.f_split(#codeList, ',') f
WHERE p.code = f.val;

LINQ to DataSet Query Help

I'm really new to LINQ so I'm hoping someone can help me. I've got a database which I need to run a large query from but it's a really old ODBC driver and takes a long time to respond (30+min for even a simple query). It only takes about 2-3min to dump all the data into a dataset however so I figured this was best and then I could run a LINQ to Dataset query. I can't seem to get the query to work and I'm a little confused. I put all the data into an SQL Express database to test the LINQ to SQL query to make sure I was going down the right path. I don't have this option where the application is going to be run as the environment will always be different.
SQL:
SELECT Invoice_detail.Code, Invoice_detail.Description, Product_master.Comment AS Packing, Invoice_detail.QtyInv AS INV, Invoice_detail.QtyBackOrder AS BO, Alternate_product_codes.MasterBarCode AS BarCode, Invoice_detail.PriceAmt AS Price, Invoice_detail.DiscPerc AS Disc, ROUND(Invoice_detail.TaxableAmt/Invoice_detail.QtyInv,2) AS Nett FROM ((Invoice_detail INNER JOIN Product_master ON Invoice_detail.Code = Product_master.Code) INNER JOIN Invoice_header ON Invoice_detail.InternalDocNum = Invoice_header.InternalDocNum AND Invoice_detail.DocType = Invoice_header.DocType) LEFT JOIN Alternate_product_codes ON Invoice_detail.Code = Alternate_product_codes.Code WHERE Invoice_header.DocNum = '{0}' AND Invoice_header.DocType = 1 AND Invoice_detail.LineType = 1 AND Invoice_detail.QtyInv > 0
LINQ to SQL:
from detail in INVOICE_DETAILs
join prodmast in PRODUCT_MASTERs on detail.Code equals prodmast.Code
join header in INVOICE_HEADERs on new { detail.InternalDocNum, detail.DocType } equals new { header.InternalDocNum, header.DocType}
join prodcodes in ALTERNATE_PRODUCT_CODES on detail.Code equals prodcodes.Code into alt_invd
from prodcodes in alt_invd.DefaultIfEmpty()
where
header.DocType == 1 &&
detail.LineType == 1 &&
detail.QtyInv > 0 &&
header.Date > DateTime.Parse("17/07/2011").Date &&
header.DocNum.Trim() == "119674"
select new {
detail.Code,
detail.Description,
Packing = prodmast.Comment,
INV = detail.QtyInv,
BO = detail.QtyBackOrder,
Barcode = prodcodes.MasterBarCode,
Price = detail.PriceAmt,
Disc = detail.DiscPerc,
Nett = Math.Round(Convert.ToDecimal(detail.TaxableAmt/detail.QtyInv),2,MidpointRounding.AwayFromZero)
}
LINQ to Dataset:
var query = from detail in ds.Tables["Invoice_detail"].AsEnumerable()
join prodmast in ds.Tables["Product_master"].AsEnumerable() on detail["Code"] equals prodmast["Code"]
join header in ds.Tables["Invoice_header"].AsEnumerable() on new { docnum = detail["InternalDocNum"], doctype = detail["DocType"] } equals new { docnum = header["InternalDocNum"], doctype = header["DocType"] }
join prodcodes in ds.Tables["Alternate_product_codes"].AsEnumerable() on detail["Code"] equals prodcodes["Code"] into alt_invd
from prodcodes in alt_invd.DefaultIfEmpty()
where
(int)header["DocType"] == 1 &&
(int)detail["LineType"] == 1 &&
(int)detail["QtyInv"] > 0 &&
//header.Field<DateTime>("Date") > DateTime.Parse("17/07/2011").Date &&
header.Field<DateTime>("Date") > DateTime.Now.Date.AddDays(-7) &&
header.Field<string>("DocNum").Trim() == "119674"
select new
{
Code = detail["Code"],
Description = detail["Description"],
Packing = prodmast["Comment"],
INV = detail["QtyInv"],
BO = detail["QtyBackOrder"],
Barcode = prodcodes["MasterBarCode"],
Price = detail["PriceAmt"],
Disc = detail["DiscPerc"],
Nett = Math.Round(Convert.ToDecimal((double)detail["TaxableAmt"] / (int)detail["QtyInv"]), 2, MidpointRounding.AwayFromZero)
};
I need to run the LINQ to DataSet query and then put the results into a DataTable so that I can export to CSV. The query will return many rows so I can see the CopyToDataTable method however that doesn't seem to work unless it is a typed dataset. I'm using the ODBC data adapter fill method so not specifically setting the data types on the Datatables I'm filling. The reason for this is that there is a lot of columns in those tables and setting them all up would be time consuming.
Is LINQ the best option? Am I close? Do I have to set the DataTables up for all the columns and data types? The only other way I can think of is to dump the data into an access database every time and query from there. I'm more curious to get LINQ to work though as I think it's going to be more beneficial for me going forward.
Any help or pointers is appreciated.
Thanks.
Pete.
Consider using POCO objects instead of a DataSet.
Blogs # MSDN
If I understand you correctly, the Linq To Dataset query retrieves the correct information, but you are not able to export the information to csv.
If this is just one csv file you need creating using just the nine fields in your example, you may be able to use a csv library (for example FileHelpers) to export the information.
To give you an example of the extra work involved, you need to define a class eg
[DelimitedRecord(",")]
public class Info
{
[FieldQuoted()]
public string Code ;
[FieldQuoted()]
public string Description ;
[FieldQuoted()]
public string Packing ;
public decimal INV ;
public decimal BO ;
[FieldQuoted()]
public string Barcode ;
public decimal Price ;
public decimal Disc ;
public decimal Nett ;
}
(Note, I'm guessing some of the field types)
You then change your query to use Info , ie
select new Info {
Code = detail["Code"],
...
and finally
FileHelperEngine engine = new FileHelperEngine(typeof(Info));
engine.WriteFile(".\\outputfile.csv", query);
and you are done.

Categories