Dapper... Must declare the scalar variable - dapper

I'm starting with Dapper in a C# application (I currently use Entity in most places) and i'm having an issue with a simple query.
I keep getting back "Must declare scalar variable '#ReportId'" but I am declaring it!
db.Execute(#"INSERT INTO cdr_Requests (ReportId, ReportName, StartTime, EndTime, Status, ReportUrl, CreatedAt, UpdatedAt, Timezone, CdrReportRead) VALUES (#ReportId, #ReportName, #StartTime, #EndTime, #Status, #ReportUrl, #CreatedAt, #UpdatedAt, #Timezone, #CdrReportRead)", new { data.id, data.report_name, data.start_time, data.end_time, data.status, data.report_url, data.created_at, data.updated_at, data.timezone, data.cdrreportread });
Here is my class:
public class cdr_Request
{
public int ID { get; set; }
public string ReportId { get; set; }
public string ReportName { get; set; }
}
I'm just having trouble figuring out what is going on!

Ok apparently I had a complete brain fart and was looking at the wrong line. I changed to this and it works:
db.Execute(#"INSERT INTO cdr_Requests (ReportId, ReportName, StartTime, EndTime, Status, ReportUrl, CreatedAt, UpdatedAt, Timezone, CdrReportRead)
VALUES (#ReportId, #ReportName, #StartTime, #EndTime, #Status, #ReportUrl, #CreatedAt, #UpdatedAt, #Timezone, #CdrReportRead)", new {
ReportId = data.id,
ReportName = data.report_name,
StartTime = data.start_time,
EndTime = data.end_time,
Status = data.status,
ReportUrl = data.report_url,
CreatedAt = data.created_at,
UpdatedAt = data.updated_at,
Timezone = data.timezone,
CdrReportRead = 0
});

Just in case any of ma F# peeps come across the 'must declare the scalar variable issue' - often the cause is that you declared your parameters record in a private module. E.g.
module private MyStuff =
type Params = { MyParam : int }
You'll just have to move the type or de-private the module.

Related

EF6 does not recognize Func lambda as filter for Where

I have the following entities:
[Table("Customer", Schema = "dbo")]
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
[Table("Payment", Schema = "dbo")]
public class Payment
{
public int PaymentId { get; set; }
public int CustomerId { get; set; }
public DateTime Period { get; set; }
public int Price { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
Now I want to filter Payment table by Period and Price. Each of predicate must be in its own Where method. So, I get the following:
int price = 200;
var period = new DateTime(2020, 10, 3);
using var db = new TestContext();
// Option 1: anonymous lambda
var payments1 = db.Payments
.Where(p => p.Period < period)
.Where(p => p.Price <= price);
foreach (var payment in payments1)
{
listBox.Items.Add(
$"Payment: Payment Id={payment.PaymentId}, " +
$"Customer Id`={payment.CustomerId}, " +
$"Period={payment.Period.ToShortDateString()}, " +
$"Price={payment.Price}");
}
EF6 generates correct SQL:
exec sp_executesql N'SELECT
[Extent1].[PaymentId] AS [PaymentId],
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[Period] AS [Period],
[Extent1].[Price] AS [Price]
FROM [dbo].[Payment] AS [Extent1]
WHERE ([Extent1].[Period] < #p__linq__0) AND ([Extent1].[Price] <= #p__linq__1)',
N'#p__linq__0 datetime2(7),#p__linq__1 int',
#p__linq__0='2020-10-03 00:00:00',
#p__linq__1=200
However, if I use Func lambda with the same condition:
// Option 2: Func<T, T> lambda
Func<Payment, bool> func = p => p.Period < period;
var payments2 = db.Payments.Where(func).Where(p => p.Price <= price);
I don't get the same SQL, but get this one:
SELECT
[Extent1].[PaymentId] AS [PaymentId],
[Extent1].[CustomerId] AS [CustomerId],
[Extent1].[Period] AS [Period],
[Extent1].[Price] AS [Price]
FROM [dbo].[Payment] AS [Extent1]
As far as I understand, EF switched to client-side evaluation. I wonder, why this happened? I'm using same lambda for filtering!
You need to use an Expression, not just a Func so EF can work out the names of the properties etc.
Try:
Expression<Func<Payment, bool>> func = p => p.Period < period;

FIFO inventory systems - Converting T-SQL to Linq

Please how do I convert this T-SQL statement to Linq or lambda? Trying to implement FIFO inventory systems
DECLARE #TakenQty int;
SET #TakenQty = 90;
WITH cte AS
(
SELECT *, SUM(qty) OVER (ORDER BY accept_date, id ASC) AS CumQty
FROM RS_GIN_Master
WHERE qty > 0
)
SELECT TOP ((SELECT COUNT(*) FROM cte WHERE CumQty <#TakenQty)+1)
batch_no, accept_date,
CASE
WHEN CumQty < #TakenQty THEN qty
ELSE #TakenQty - (CumQty - Qty)
END AS TakenOut
FROM
cte
Table definition
The final result is like this
Please how do I convert this T-SQL statement to Linq or lambda?
You don't.
LINQ and SQL share many common query operators, but LINQ has nothing equivalent to
SUM(qty) OVER (ORDER BY accept_date, id ASC) RANGE BETWEEN UNBOUNDED PRECEEDING AND CURRENT ROW
Which is what that expression is shorthand for. And it certainly has no way to write an expression that EF could translate to this TSQL.
So you leave it in TSQL. And if you must have an implementation outside of SQL Server, you start from scratch.
I was able to resolve this
void Main()
{
var data = new List<History>()
{
new History(1,1,20,DateTime.Now.AddDays(-24),"001"),
new History(2,1,2,DateTime.Now.AddDays(-23),"002"),
new History(3,2,2,DateTime.Now.AddDays(-24),"001"),
new History(3,1,29,DateTime.Now.AddDays(-22),"003"),
new History(3,1,50,DateTime.Now.AddDays(-21),"004"),
};
var demo = Results(data, 30);
demo.Dump(); //note using LinqPad
}
public class History
{
public History(int id, int stockId, int qty, DateTime date, string batchNumber)
{
Id = id;
StockId = stockId;
Qty = qty;
Date = date;
BatchNumber = batchNumber;
}
public int Id { get; set; }
public int StockId { get; set; }
public int Qty { get; set; }
public string BatchNumber { get; set; }
public DateTime Date { get; set; }
}
public static List<Result> Results(List<History> data, int takenQty)
{
var runningTotal = 0;
var result = data.Where(p => p.StockId == 1).OrderBy(p => p.Date).ThenBy(p => p.Id)
.Select(x => new
{
x.Id,
x.Date,
x.BatchNumber,
x.Qty,
x.StockId,
CumQty = (runningTotal = runningTotal + x.Qty)
}).ToList();
var query = result.Select(x => new Result
{
StockId =x.StockId,
Id = x.Id,
BatchNumber = x.BatchNumber,
Qty = x.Qty,
Used = x.CumQty < takenQty ? x.Qty : takenQty - (x.CumQty - x.Qty)
}).Take((result.Count(p => p.CumQty < takenQty)) + 1).ToList();
return query;
}
public class Result
{
public int Id { get; set; }
public int StockId { get; set; }
public int Qty { get; set; }
public string BatchNumber { get; set; }
public int Used { get; set; }
public int Left => Qty - Used;
}
And the final output

Splitting row into several nested objects

I've got the following query:
select id, name, email, supervisor, createdBy, createdAt, changedBy, changedAt from XXX
Now, I'd like to map each row into something that looks like this:
public class Organisation{
public int Id{get; set; }
public string Name{get; set; }
public ContactInfo Contact{get;set;}
public Action CreatedBy{get;set;}
public Action ChangedBy{get;set;}
}
public class ContactInfo{
public string Name{get;set;}
public string Email{get;set;}
}
public class Action{
public string Username{get;set;}
public DateTime At{get;set;}
}
I've seen some examples that show how to partition the info, but it seems like will not work in this example. Can it be done or should I map my query into a helper object which will then be mapped into these classes?
Thanks.
Luis
Dapper allows you to map a single row to multiple objects. This is a
key feature if you want to avoid extraneous querying and eager load
associations.
You can read about it more here
Below is an example:
[Test]
public void Test_Multi()
{
using (var conn = new SqlConnection(#"Data Source=.\sqlexpress;Integrated Security=true; Initial Catalog=foo"))
{
var result = conn.Query<Organisation, ContactInfo, Action, Action, Organisation>(
#"select Id = 100, Name = 'Foo',
Name = 'Contact Name', Email = 'contact#foo.com',
Username = 'The Creator', At = '12/25/2017',
Username = 'The Modifier', At = '12/26/2017' ",
(org, contact, cretedBy, changedBy) =>
{
org.Contact = contact;
org.CreatedBy = cretedBy;
org.ChangedBy = changedBy;
return org;
}, splitOn: "Id, Name, Username, Username").First();
Assert.That(result.Id, Is.EqualTo(100));
Assert.That(result.Contact.Email, Is.EqualTo("contact#foo.com"));
Assert.That(result.CreatedBy.Username, Is.EqualTo("The Creator"));
Assert.That(result.ChangedBy.Username, Is.EqualTo("The Modifier"));
}
}

Dapper Multi-map next level

I'm using multiple mapping for a current query and now I need to map another object on the initial query.
For example:
public class Part {
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address {
public int Id { get; set; }
public string Street { get; set; }
public SiteOu Ou { get; set; }
}
public class SiteOu
public int Id { get; set; }
public string Name { get; set; }
}
Dapper:
connection.Query<Part, Address, Part>(sql, (part, address) => {
part.Address = address;
});
How do I get the Address class to have the SiteOu information?
This example isn't what I'm actually doing because I've actually got
Query<T1,T2,T3,T4,T5,TResult>();
I'm doing 1 select and 5 joins in my query. So hopefully I don't need more overloads of Query.
Dapper allows you to map a single row to multiple objects, so you can just map SiteOu as part of the same query.
[Test]
public void TestSplitOn()
{
var conn = new SqlConnection(#"Data Source=.\SQLEXPRESS;Integrated Security=true;Initial Catalog=db");
conn.Open();
const string sql = "select Id = 1, Name = 'My Part', " +
"Id = 2, Street = 'My Street', " +
"Id = 3, Name = 'My Site'";
var result = conn.Query<Part, Address, SiteOu, Part>(sql, (part, address, siteOu) =>
{
part.Address = address;
address.Ou = siteOu;
return part;
},
commandType: CommandType.Text
).FirstOrDefault();
Assert.That(result, Is.Not.Null);
Assert.That(result.Address, Is.Not.Null);
Assert.That(result.Address.Ou, Is.Not.Null);
}
Important Note: Dapper assumes your Id columns are named "Id" or "id", if your primary key is different or you would like to split the wide row at point other than "Id", use the optional 'splitOn' parameter.
If you have more that 5 types to map, another out of the box option is to use QueryMultiple extension. Here is an example from the Dapper docs.
var sql =
#"
select * from Customers where CustomerId = #id
select * from Orders where CustomerId = #id
select * from Returns where CustomerId = #id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
var customer = multi.Read<Customer>().Single();
var orders = multi.Read<Order>().ToList();
var returns = multi.Read<Return>().ToList();
...
}
Also check out this thread.

Changing default value for null string to datetime conversion in sql server

I have a web from that has a few text boxes which are visible only under certain conditions. These textboxes are bound to calender extenders.However whenever not visible these pass null string to the database. Their corresponding datatype in the table is datetime. While storing these null strings they are converted to 1/1/1900 12:00:00 AM. How can i have a null value stored here instead ?
Do i need to change the data type to some thing else ??
I'm using datetime because i need to perform some comparison operations later.
Relevant code is here.
public class Asset
{
public string dbStatus { get; set; }
public string wtyEndDate { get; set; }
public string amcEndDate { get; set; }
public Asset()
{
dbStatus = default(String);
wtyEndDate = default(String);
amcEndDate = default(String);
}
}
protected void btnSaveAsset_Click(object sender, EventArgs e)
{
Asset a = new Asset();
a.wtyEndDate = txtWtyEndDate.Text;
a.amcEndDate = txtAmcEndDate.Text;
string msg = "";
if (a.dbStatus == "INSERT")
{
try
{
msg = (ab.EXE_Assets_Master(a).Rows.Count>0) ? "Record Inserted"; : "Error"
}
catch (Exception)
{
msg = "Record with this service tag already exists in the database";
}
}
}
protected DataTable _EXE_Asset_Details(Asset a)
{
Parameters.Clear();
AddParameter("#wtyEndDate", a.wtyEndDate);
AddParameter("#amcEndDate",a.amcEndDate);
AddParameter("#DBStatus", a.dbStatus);
return ExecuteDataSet("[Asset_Details]").Tables[0];
}
// Asset_Details
CREATE PROCEDURE [Asset_Details]
#wtyEndDate datetime = NULL,
#amcEndDate datetime =null,
#dbStatus nvarchar(50)
AS
IF(#DBStatus='INSERT')
BEGIN
INSERT INTO [assetsMaster]
([warrantyEndDate],[amcEndDate])
VALUES
( #wtyEndDate,#amcEndDate)
SELECT ##rowcount
END
Only add the optional DateTime parameters if they have data in them.
For example:
if(!string.IsNullOrWhiteSpace(a.wtyEndDate))
{
AddParameter("#wtyEndDate", a.wtyEndDate);
}

Resources