ASP.NET MVC3 WebGrid - custom, server-side sorting - asp.net-mvc-3

Is there a way to override default MVC3 WebGrid sorting behavior to call my controller (which will perform server side sorting and return the data) when sort is called?
Thanks for any help!

You can pass the server side sorted data to the webgrid, along with the info on how many records there are. http://msdn.microsoft.com/en-us/magazine/hh288075.aspx has a helpful walkthrough. I'm doing database level sorting/filtering/paging to minimize the amount of data being passed around. I don't think my webserver would love me if I passed all 70,000 objects a customer has just so they can see the 25 on page 1. This is pretty much everything you need, except the very lightweight view model that just wraps your IEnumerable collection with some extra paging data.
Since web grid just uses the query string vars to decide what to do you need to use a get method form. And you need to include the sort field/direction in querystring in a way webgrid knows how to read it. So you end up with urls like localhost/example/admin/thing?thingName=Hyatt&City=&State=TX&Country=&sort=city&sortdir=ASC
Controller:
public ActionResult Index(string thingName, string city, string state, string country, int page = 1)
{
const int pageSize = 25;
int totalRecords = 0;
IEnumerable<Thing> things = ThingModel.GetPagedSortedLocationsForCustomer(customerId, sort, sortdir, out totalRecords, pageSize, page, thingName, city, state, country);
PagedThings viewModel = new PagedThings(pageSize, page, totalRecords, things);
return View(viewModel);
}
View:
#model ExampleCompany.Area.ViewModels.PagedThings
#{
using (Html.BeginForm("Index", "ThingaMaBob", System.Web.Mvc.FormMethod.Get))
{
<label for="ThingName">ThingName</label>#Html.TextBox("ThingName", "")
<label for="City">City</label>#Html.TextBox("City", "")
<label for="State">State</label>#Html.TextBox("State", "")
<label for="Country">Country</label>#Html.TextBox("Country", "")
<input type="submit" value="Filter" />
<br />
var grid = new WebGrid(canPage: true, rowsPerPage: Model.PageSize, canSort: true);
grid.Bind(Model.Things, rowCount: Model.TotalRows, autoSortAndPage: false);
grid.Pager(WebGridPagerModes.All);
#grid.GetHtml(htmlAttributes: new { id = "grid"},
columns: grid.Columns(
//ommitted
grid.Column("thingName", "Thing"),
));
Html.Hidden(grid.SortFieldName, grid.SortColumn);
Html.Hidden(grid.SortDirectionFieldName, grid.SortDirection == SortDirection.Ascending ? "ASC" : "DESC");
}
Model:
public static IEnumerable<Thing> GetPagedSortedThingsForCustomer(int customerid, String sortby, String sorttype, out int totalRecords, int pageSize, int pageIndex, string thingName, string city, string state, string country)
{
var tmp = new List<Thing>();
int total = 0;
dynamic dr = OurDBUtility.ReturnDR("ExampleProc_GetThingsSortedPaged", ConnectionInfo.ExampleConnection, customerid, sortby, sorttype, pageSize, pageIndex, thingName, city, state, country);
{
while (dr.Read())
{
var tmpThing = new Thing();
tmpThing.LoadFromDr(dr);
tmp.Add(tmpThing);
if (total == 0)
{
total = (int)dr["TOTAL_THINGS"];
}
}
}
totalRecords = total;
return tmp;
}
Proc with dynamic sql - yes, you could use Linq-to-Sql or other techniques if you wanted to, but i'm old school:
CREATE PROCEDURE ExampleProc_GetThingsSortedPaged
( #CustomerId int
, #sortby nvarchar(60)
, #sorttype nvarchar(60)
, #pageSize int
, #pageIndex int
, #thingName nvarchar(255) = null
, #city nvarchar(30) = null
, #state nvarchar(30) = null
, #country char(2) = null
)
as
DECLARE #strSql nvarchar(3000)
--calculate paging rows
declare #startRow int, #endRow int
--e.g. if you have a page size of 10, page 1 = 1 - 10, page 2 = 11 -20
set #startRow = ((#pageIndex - 1) * #pageSize) + 1
set #endRow = #startRow + #pageSize - 1
if #thingName = ''
set #thingName = null
if #city = ''
set #city = null
if #state = ''
set #state = null
if #country = ''
set #country = null
--return total for webgrid, accounting for filter
declare #totalThings int
select #totalThings = COUNT(*)
from EXAMPLE_TABLE T with(nolock)
where CUSTOMER_ID = #CustomerId
AND (T.THING_NAME LIKE #thingName + '%' OR #thingName is null)
AND (T.CITY LIKE #city + '%' or #city is null)
AND (T.STATE LIKE #state + '%' or #state is null)
AND (T.COUNTRY = #country or #country is null)
DECLARE #ParameterDefinition AS NVARCHAR(200)
set #ParameterDefinition = '#totalThings int, #CustomerId INT, #startRow INT, #endRow INT, #thingName nvarchar(255), #city nvarchar(30), #state nvarchar(30), #country char(2)'
--When we need to do dynamic sql it is better to use paramterization, but you cannot do (ORDER BY #sortBy).
SET #strSql = N'SELECT * from
(
select ROW_NUMBER() OVER (ORDER BY T.' + #sortby + ' ' + #sorttype + ') as Row,
#totalThings [TOTAL_THINGS],
T.THING_ID, T.THING_NAME, T.ADDRESS, T.CITY, T.STATE,
T.ZIP_CODE, T.COUNTRY
FROM EXAMPLE_TABLE T
WHERE T.CUSTOMER_ID = #CustomerId
AND (T.THING_NAME LIKE #thingName + ''%'' OR #thingName is null)
AND (T.CITY LIKE #city + ''%'' or #city is null)
AND (T.STATE LIKE #state + ''%'' or #state is null)
AND (T.COUNTRY = #country or #country is null)
) paged
where Row between #startRow and #endRow
ORDER BY Row'
--print #strSql
EXECUTE sp_executesql #strSql, #ParameterDefinition, #totalThings, #CustomerId, #startRow, #endRow, #thingName, #city, #state, #country
GO
Proc with CTE:
CREATE PROCEDURE ExampleProc_GetThingsSortedPaged
( #CustomerID int
, #sortby nvarchar(60)
, #sorttype nvarchar(60)
, #pageSize int = 25
, #pageIndex int = 1
, #thingName nvarchar(255) = null
, #city varchar(30) = null
, #state nvarchar(30) = null
, #country char(2) = null
)
as
declare #startRow int
declare #endRow int
SET #startRow = ((#pageIndex - 1) * #pageSize) + 1;
SET #endRow = #startRow + #pageSize - 1;
set #sortby = replace(LOWER(#sortby), '_', '')
SET #sorttype = LOWER(#sorttype)
if #sorttype != 'asc' and #sorttype != 'desc'
begin
set #sorttype = 'asc'
end
;with cte_things as (
SELECT
CASE
WHEN #sortby ='country' AND #sorttype = 'asc' then row_number() over (order by C.COUNTRY_NAME ASC)
WHEN #sortby ='country' AND #sorttype = 'desc' then row_number() over (order by C.COUNTRY_NAME DESC)
WHEN #sortby ='state' AND #sorttype = 'asc' then row_number() over (order by STATE ASC)
WHEN #sortby ='state' AND #sorttype = 'desc' then row_number() over (order by STATE DESC)
WHEN #sortby ='city' AND #sorttype = 'asc' then row_number() over (order by CITY ASC)
WHEN #sortby ='city' AND #sorttype = 'desc' then row_number() over (order by CITY DESC)
WHEN #sortby ='thingname' AND #sorttype = 'desc' then row_number() over (order by THING_NAME DESC)
ELSE row_number() over (order by THING_NAME ASC)
END AS Row
,T.THING_ID, T.THING_NAME, T.THING_TYPE, T.ADDRESS, T.CITY, T.STATE
, T.ZIP_CODE, T.COUNTRY_CODE, C.COUNTRY_NAME, T.PHONE_NUMBER
, T.LATITUDE, T.LONGITUDE
FROM EXAMPLE_TABLE L
join COUNTRIES C
on C.COUNTRY_CODE = L.COUNTRY_CODE
where
T.CUSTOMER_ID = #CustomerId
and L.CITY = ISNULL(#city, CITY)
and L.STATE = ISNULL(#state, STATE)
and L.COUNTRY_CODE = ISNULL(#country, L.COUNTRY_CODE)
and L.THING_NAME = ISNULL(#thingName, THING_NAME)
)
, cte_total as (select COUNT(*) as TOTAL_THINGS from cte_things)
, cte_all as (select cte_things.*, cte_total.TOTAL_THINGS from cte_things cross join cte_total)
SELECT * FROM cte_all
where
Row >= #startRow
and Row <= #endRow
ORDER BY Row
GO

Related

ORA-01427 Single row subquery return more than one row

I have the below query once I run it, it's exceeded successfully without error, but when I run it inside a procedure, I got the single error.
CREATE OR REPLACE PROCEDURE ABLEA_NEW.AB_VATFILE
IS
fHandle UTL_FILE.FILE_TYPE;
err varchar2(200);
v_str VARCHAR2(4000);
CURSOR VAT1 IS
SELECT (SELECT cif_no
FROM nbfc_customer_m
WHERE customerid = a.bpid) ||
(SELECT EXTRACT_ACCT(HOST_ACCT_INFO, 'SUFFIX')
FROM LEA_AGREEMENT_GROUPGL_MAP A, FA_ACCTCATG_M B
WHERE EXTRACT_ACCT(A.HOST_ACCT_INFO, 'ACCTCATG') = B.ACCTCATG
AND B.GROUPID = 'FA'
AND A.ACTIVE_FLAG = 'Y'
and AGREEMENTID = a.caseid)
"Account No",lpad(a.caseid,6,0) Loan_No
,
(SELECT AGREEMENTNO
FROM lea_agreement_dtl
WHERE AGREEMENTID = a.caseid)
AGREEMENTNO
,
LPAD(A.productid,3,0) Scheme_ID,
(SELECT rpad(schemedesc,35,' ')
FROM lea_scheme_m
WHERE schemeid = a.productid)
SchemeDesc,
to_char(a.advicedate,'ddmmyyyy') advicedate,
it_conv(a.adviceamt) adviceamt,
rpad(a.chargeid,6,' ')chargeid,
(SELECT rpad(chargedesc,35,' ')
FROM nbfc_charges_m
WHERE chargeid = a.chargeid)
"Charge Description",
IT_CONV(a.chargeamt)chargeamt,
(SELECT
decode(count(1),0,'N','Y')
FROM nbfc_pmnt_dtl y
WHERE a.txnadviceid = y.txnadviceid
AND a.status = 'A'
AND y.status IS NULL
--and TRUNC(y.pmntdate) between :p_datefrom and :p_dateto
AND a.tax_applicable = 'Y'
AND a.ptxnadviceid IS NULL)Paid,
LPAD(b.chargeid,6,0)
"VAT ChargeID",
(SELECT RPAD(chargedesc,35,' ')
FROM nbfc_charges_m
WHERE chargeid = b.chargeid)
"VAT Charge Description",
IT_CONV(b.chargeamt)
"VAT Amount"
FROM (SELECT *
FROM nbfc_txn_advice_dtl
WHERE status = 'A' AND tax_applicable = 'Y' AND ptxnadviceid IS NULL)
a,
(SELECT *
FROM nbfc_txn_advice_dtl
WHERE status = 'A' AND ptxnadviceid IS NOT NULL) b
WHERE a.txnadviceid = b.ptxnadviceid;
BEGIN
fHandle := UTL_FILE.FOPEN('UAEDB', 'VAT', 'W');
FOR I IN VAT1
LOOP
v_str:= null;
v_str:= I."Account No"||I.Loan_No||I.AGREEMENTNO || I.Scheme_ID ||I.SchemeDesc|| I.advicedate|| I.adviceamt|| I.chargeid||I."Charge Description"||I.chargeamt||I.Paid||
I."VAT ChargeID" ||I."VAT Charge Description"||I."VAT Amount";
UTL_FILE.PUTF(fHandle,v_str);
UTL_FILE.PUTF(fHandle, '\n');
END LOOP;
UTL_FILE.FCLOSE(fHandle);
END ;
/
How can I solve this?
Note: the query return mor than 10000 record.
comment or un-comment the "(select from )as ColumnAlias" query column one by one, you can find which sub query column return more than one row

I want to add a subquery with a date parameter to return a calculated value but my query is not working

I have a query that fetches data from oracle inventory and purchasing. Now I want to add a subquery from po_line_locations_all with a date parameter to return a calculated value as if the value lies in the subquery show it else display zero, But in my case the query shows nothing if subquery returns null.
I want to show the result of the main query even if subquery returns null.
SELECT distinct msib.segment1 Item_Code,
MSIB.inventory_item_id,
MSIB.organization_id ORG_ID,
msib.description Item_Description,
msib.primary_unit_of_measure UOM,
ph.attribute1 Item_Type,
SUM(plla.quantity) - SUM(plla.quantity_received) On_Order,
ph.currency_code currency,
max(pl.unit_price) FOB_Value_in_FCY,
max(ph.rate_date),
max(ph.rate) forex_rate,
(
SELECT SUM (moq.transaction_quantity)
FROM mtl_system_items_b msi, mtl_onhand_quantities moq
WHERE moq.organization_id(+) = msi.organization_id
AND moq.inventory_item_id(+) = msi.inventory_item_id
and moq.ORGANIZATION_ID =MSIB.organization_id
and msi.inventory_item_id = MSIB.inventory_item_id
GROUP BY msi.segment1, msi.organization_id
) CURR_STOCK,
(
SELECT NVL(ABS(sum(mtmt.transaction_quantity)),0) from MTL_MATERIAL_TRANSACTIONS mtmt
WHERE 1=1
AND mtmt.inventory_item_id = MSIB.inventory_item_id --4018
AND mtmt.organization_id = MSIB.organization_id--499
and mtmt.TRANSACTION_ACTION_ID NOT IN (24, 30)
AND to_date(mtmt.transaction_date) >= to_date(&Roll_back_date,'DD-MON-RRRR')
)RB_TRANSACTIONS,
(
select ABS(SUM(mmt.transaction_quantity))
from MTL_MATERIAL_TRANSACTIONS mmt
where mmt.TRANSACTION_ACTION_ID NOT IN (24, 30)
and (mmt.ORGANIZATION_ID = MSIB.organization_id --499--579
)
and (mmt.INVENTORY_ITEM_ID = MSIB.inventory_item_id --4128 --4165
and mmt.TRANSACTION_TYPE_ID in (33, 52)
)
and (mmt.transaction_date between
to_date(add_months(&CONS_f_DATE, -12),'DD-MON-YYYY')
AND to_date(&CONS_f_DATE, 'DD-MON-YYYY')
)
AND (mmt.parent_transaction_id IS NULL)
) annual_Consumption,
(
select ABS(SUM(mmt.transaction_quantity) / 4)
FROM MTL_MATERIAL_TRANSACTIONS mmt
WHERE mmt.TRANSACTION_ACTION_ID NOT IN (24, 30)
and (mmt.ORGANIZATION_ID = MSIB.organization_id --499--579
)
and (mmt.INVENTORY_ITEM_ID = MSIB.inventory_item_id --4128 --4165
AND mmt.TRANSACTION_TYPE_ID in (33, 52)
)
and (mmt.transaction_date between
to_date(add_months(&CONS_f_DATE, -12),
'DD-MON-YYYY') AND
to_date(&CONS_f_DATE, 'DD-MON-YYYY'))
AND (mmt.parent_transaction_id IS NULL)
) months_Consumption,
(
select ABS((SUM(mmt.transaction_quantity) / 4) / 3)
FROM MTL_MATERIAL_TRANSACTIONS mmt
WHERE mmt.TRANSACTION_ACTION_ID NOT IN (24, 30)
and (mmt.ORGANIZATION_ID = MSIB.organization_id --499--579
)
and (mmt.INVENTORY_ITEM_ID = MSIB.inventory_item_id --4128 --4165
AND mmt.TRANSACTION_TYPE_ID in (33, 52))
and (mmt.transaction_date between
to_date(add_months(&CONS_f_DATE, -12),
'DD-MON-YYYY') AND
to_date(&CONS_f_DATE, 'DD-MON-YYYY'))
AND (mmt.parent_transaction_id IS NULL)
) monthly_Average,
(
select MATERIAL_COST
FROM CST_ITEM_COST_TYPE_V vw
WHERE vw.organization_id = MSIB.organization_id
AND - 1 = -1
and (vw.INVENTORY_ITEM_ID = MSIB.inventory_item_id)
) Unit_Cost, --new
sum(quan.t_quantity) - sum(r_quantity) finala
FROM mtl_system_items_b MSIB,
PO_HEADERS_ALL ph,
Po_Lines_All pl,
PO_LINE_LOCATIONS_ALL PLLA,
-------------------SUBQUERY---------------------------------------
(select nvl(sum(subplla.quantity),0) t_quantity, nvl(sum(subplla.quantity_received),0) r_quantity ,subpl.item_id
from po_headers_all subph,
po_lines_all subpl,
po_line_locations_all subplla
where subph.po_header_id = subpl.po_header_id
and subplla.po_header_id = subph.po_header_id
and subpl.po_line_id = subplla.po_line_id
and subplla.org_id = subpl.org_id
and to_date(subplla.creation_date) >= to_date(&Roll_back_date,'DD-MON-RRRR')
group by subph.attribute1, subph.currency_code, subpl.item_id
) quan
-------------------SUBQUERY---------------------------------------
WHERE 1=1
and ph.po_header_id = pl.po_header_id
and msib.inventory_item_id (+) = pl.item_id
and pl.item_id (+) = quan.item_id
and plla.po_header_id = ph.po_header_id
and pl.po_line_id = plla.po_line_id
and plla.org_id = pl.org_id
and msib.organization_id in
(select haou.organization_id
from hr_organization_information hoi,
hr_all_organization_units haou
where haou.organization_id = hoi.organization_id
and hoi.org_information1 = 'INV'
and hoi.org_information2 = 'Y'
and haou.name like '%HEIS%')
and MSIB.Inventory_Item_Id=NVL(&ITEM,MSIB.Inventory_Item_Id)
and MSIB.organization_id = nvl(&P_ORGI, MSIB.organization_id)
AND to_date(plla.creation_date) BETWEEN
to_date(add_months(&Roll_back_date, -12),'DD-MON-YYYY') AND
to_date(&Roll_back_date,'DD-MON-YYYY')
GROUP BY msib.segment1,
MSIB.inventory_item_id,
msib.description,
MSIB.organization_id,
msib.primary_unit_of_measure,
ph.attribute1,
ph.currency_code
My guess is that your problem is simply using old-syntax joins instead of something that has been around for a really long time.
SELECT DISTINCT msib.segment1 Item_Code,
MSIB.inventory_item_id,
MSIB.organization_id ORG_ID,
msib.description Item_Description,
msib.primary_unit_of_measure UOM,
ph.attribute1 Item_Type,
SUM(plla.quantity) - SUM(plla.quantity_received) On_Order,
ph.currency_code currency,
max(pl.unit_price) FOB_Value_in_FCY,
max(ph.rate_date),
max(ph.rate) forex_rate,
(
SELECT SUM(moq.transaction_quantity)
FROM mtl_system_items_b msi
RIGHT JOIN mtl_onhand_quantities moq ON moq.organization_id = msi.organization_id
AND moq.inventory_item_id = msi.inventory_item_id
WHERE moq.ORGANIZATION_ID = MSIB.organization_id
AND msi.inventory_item_id = MSIB.inventory_item_id
GROUP BY msi.segment1,
msi.organization_id
) CURR_STOCK,
(
SELECT NVL(ABS(sum(mtmt.transaction_quantity)), 0)
FROM MTL_MATERIAL_TRANSACTIONS mtmt
WHERE 1 = 1
AND mtmt.inventory_item_id = MSIB.inventory_item_id --4018
AND mtmt.organization_id = MSIB.organization_id --499
AND mtmt.TRANSACTION_ACTION_ID NOT IN (24,30)
AND to_date(mtmt.transaction_date) >= to_date(&Roll_back_date, 'DD-MON-RRRR')
) RB_TRANSACTIONS,
mmt.annual_Consumption annual_Consumption,
mmt.annual_Consumption / 4 months_Consumption,
mmt.annual_Consumption / 12 monthly_Average,
(
SELECT MATERIAL_COST
FROM CST_ITEM_COST_TYPE_V vw
WHERE vw.organization_id = MSIB.organization_id
AND vw.INVENTORY_ITEM_ID = MSIB.inventory_item_id
) Unit_Cost, --new
sum(quan.t_quantity) - sum(r_quantity) finala
FROM mtl_system_items_b MSIB
LEFT JOIN PO_HEADERS_ALL ph ON msib.inventory_item_id = pl.item_id
INNER JOIN Po_Lines_All pl ON ph.po_header_id = pl.po_header_id
INNER JOIN PO_LINE_LOCATIONS_ALL PLLA ON plla.po_header_id = ph.po_header_id AND pl.po_line_id = plla.po_line_id AND plla.org_id = pl.org_id
LEFT JOIN
-------------------SUBQUERY---------------------------------------
(
SELECT nvl(sum(subplla.quantity), 0) t_quantity,
nvl(sum(subplla.quantity_received), 0) r_quantity,
subpl.item_id
FROM po_headers_all subph
INNER JOIN po_lines_all subpl ON subph.po_header_id = subpl.po_header_id
INNER JOIN po_line_locations_all subplla ON subplla.po_header_id = subph.po_header_id
AND subpl.po_line_id = subplla.po_line_id
AND subplla.org_id = subpl.org_id
WHERE to_date(subplla.creation_date) >= to_date(&Roll_back_date, 'DD-MON-RRRR')
GROUP BY subph.attribute1,
subph.currency_code,
subpl.item_id
) quan ON pl.item_id = quan.item_id
-------------------SUBQUERY---------------------------------------
LEFT JOIN (
SELECT mmt.ORGANIZATION_ID,
mmt.INVENTORY_ITEM_ID,
ABS(SUM(mmt.transaction_quantity)) AS annual_Consumption
FROM MTL_MATERIAL_TRANSACTIONS mmt
WHERE mmt.TRANSACTION_ACTION_ID NOT IN (24,30)
AND mmt.TRANSACTION_TYPE_ID IN (33,52)
AND mmt.transaction_date BETWEEN to_date(add_months(&CONS_f_DATE, - 12), 'DD-MON-YYYY')
AND to_date(&CONS_f_DATE, 'DD-MON-YYYY')
AND mmt.parent_transaction_id IS NULL
) mmt ON mmt.ORGANIZATION_ID = MSIB.organization_id --499--579
AND mmt.INVENTORY_ITEM_ID = MSIB.inventory_item_id --4128 --4165
WHERE msib.organization_id IN (
SELECT haou.organization_id
FROM hr_organization_information hoi
JOIN hr_all_organization_units haou ON haou.organization_id = hoi.organization_id
WHERE hoi.org_information1 = 'INV'
AND hoi.org_information2 = 'Y'
AND haou.name LIKE '%HEIS%'
)
AND MSIB.Inventory_Item_Id = NVL(&ITEM, MSIB.Inventory_Item_Id)
AND MSIB.organization_id = nvl(&P_ORGI, MSIB.organization_id)
AND to_date(plla.creation_date) BETWEEN to_date(add_months(&Roll_back_date, - 12), 'DD-MON-YYYY')
AND to_date(&Roll_back_date, 'DD-MON-YYYY')
GROUP BY msib.segment1,
MSIB.inventory_item_id,
msib.description,
MSIB.organization_id,
msib.primary_unit_of_measure,
ph.attribute1,
ph.currency_code;
Here is the query in its simplest form, The main query is working good without the subquery. But when subquery returns null (i.e. no output in the date range) the whole query returns nothing. I want it to display results regardless the subquery results. My guess is something is wrong with the JOIN –
SELECT distinct msib.segment1 Item_Code,
MSIB.inventory_item_id,
MSIB.organization_id ORG_ID,
msib.description Item_Description,
msib.primary_unit_of_measure UOM,
ph.attribute1 Item_Type,
ph.currency_code currency,
max(pl.unit_price) FOB_Value_in_FCY,
max(ph.rate_date),
max(ph.rate) forex_rate,
sum(quan.t_quantity) - sum(r_quantity) finala
FROM mtl_system_items_b MSIB,
PO_HEADERS_ALL ph,
Po_Lines_All pl,
PO_LINE_LOCATIONS_ALL PLLA,
------------SUBQUERY-------------------------------
(select nvl(sum(subplla.quantity),0) t_quantity, nvl(sum(subplla.quantity_received),0) r_quantity ,subpl.item_id
from po_headers_all subph,
po_lines_all subpl,
po_line_locations_all subplla
where subph.po_header_id = subpl.po_header_id
and subplla.po_header_id = subph.po_header_id
and subpl.po_line_id = subplla.po_line_id
and subplla.org_id = subpl.org_id
and to_date(subplla.creation_date) >= to_date(&Roll_back_date,'DD-MON-RRRR')
group by subph.attribute1, subph.currency_code, subpl.item_id
) quan
------------SUBQUERY-------------------------------
WHERE 1=1
and ph.po_header_id = pl.po_header_id
and msib.inventory_item_id (+) = pl.item_id
-----------------joining subquery-------------------
and pl.item_id (+) = quan.item_id
-----------------joining subquery-------------------
and plla.po_header_id = ph.po_header_id
and pl.po_line_id = plla.po_line_id
and plla.org_id = pl.org_id
and msib.organization_id in
(select haou.organization_id
from hr_organization_information hoi,
hr_all_organization_units haou
where haou.organization_id = hoi.organization_id
and hoi.org_information1 = 'INV'
and hoi.org_information2 = 'Y'
and haou.name like '%HEIS%')
and MSIB.Inventory_Item_Id=NVL(&ITEM,MSIB.Inventory_Item_Id)
and MSIB.organization_id = nvl(&P_ORGI, MSIB.organization_id)
AND to_date(plla.creation_date) BETWEEN
to_date(add_months(&Roll_back_date, -12),'DD-MON-YYYY') AND
to_date(&Roll_back_date,'DD-MON-YYYY')
GROUP BY msib.segment1,
MSIB.inventory_item_id,
msib.description,
MSIB.organization_id,
msib.primary_unit_of_measure,
ph.attribute1,
ph.currency_code

Oracle update subquery of records

I need to update a list of record given by a subquery, sniffing around on the web I tried this structure:
UPDATE
(
SELECT
a.COL1
FROM
TABLE1 a,
TABLE2 b
WHERE
a.field1 = b.field1
) update_tbl
SET
update_tbl.COL1 = 'VALUE'
But it returns to me this Oracle error:
-> ORA-01779: Cannot modify a column which maps to a non key-preserved table
My query is the following one:
UPDATE
(
SELECT
imp.*
FROM table1 imp
JOIN table2 sp ON imp.id_p = sp.id_p
JOIN table3 cs ON sp.id_s = cs.id_s
JOIN table4 cb ON cb.id_c = cs.id_c
WHERE
imp.id_b = cb.id_b
AND (
(to_char(imp.p,'yyyymm') < to_char(cb.data_in,'yyyymm')) OR
(cb.data_fi IS NOT NULL AND to_char(imp.p,'yyyymm') > to_char(cb.data_fi,'yyyymm'))
)
and (
(imp.v is not null) or
(imp.v_s is not null and imp.v_s <> 0) or
(imp.imp_co is not null and imp.imp_co <> 0) or
(imp.imp_acc is not null and imp.imp_acc <> 0)
)
) i
SET
i.v = null,
i.v_s = 0,
i.imp_co = 0,
i.imp_acc = 0,
i.ID_S_CONT = 'N',
i.ID_T_COMP = 'P',
i.date_upd = SYSDATE,
i.user_upd = 'SeR'
The subquery return 82 rows (tested now), and I do want to modify only that rows, What am I doing wrong?
I think you are updating to imp table. so you can try MERGE like below
MERGE INTO
IMP A
USING
( SELECT
imp.*
FROM table1 imp
JOIN table2 sp ON imp.id_p = sp.id_p
JOIN table3 cs ON sp.id_s = cs.id_s
JOIN table4 cb ON cb.id_c = cs.id_c
WHERE
imp.id_b = cb.id_b
AND (
(to_char(imp.p,'yyyymm') < to_char(cb.data_in,'yyyymm')) OR
(cb.data_fi IS NOT NULL AND to_char(imp.p,'yyyymm') >
to_char(cb.data_fi,'yyyymm'))
)
and (
(imp.v is not null) or
(imp.v_s is not null and imp.v_s <> 0) or
(imp.imp_co is not null and imp.imp_co <> 0) or
(imp.imp_acc is not null and imp.imp_acc <> 0)
)) B
ON (A.ID =B.ID)
WHEN MATCHED THEN
UPDATE SET
A.v = null,
A.v_s = 0,
A.imp_co = 0,
A.imp_acc = 0,
A.ID_S_CONT = 'N',
A.ID_T_COMP = 'P',
A.date_upd = SYSDATE,
A.user_upd = 'SeR'
Will update the table given by subquery. here A.ID =B.ID use the primary key.

How to get rid of cursor loop to improve performance?

I have a function is like below, which has very bad performance, because of cursor loop. I need somebody help me get rid of the cursor and using one statement instead. Anybody can help will be greate appreciated!!
function get_pat_liv_donor_trans_date(p_patr_id IN NUMBER) return date is
v_transplant_date date;
v_rst date;
v_patr_id number;
--get patient all the patr_id(s) which has been transplanted (kidney) with living donor
cursor c_cur1 is
select distinct patr.patr_id
from pat,
pat_register patr,
pat_register_org_det prod,
transplant_org_det tod,
transplant trans,
don,
don_org_con doc,
allo,
all_pat_list apl,
ORG_SPEC OS,
DON_ORG_OFF_RES DOOR
where pat.pat_id = patr.pat_id
and patr.patr_id = prod.patr_id
and prod.prod_id = tod.prod_id --patient has been transplanted (kidney)
and tod.orgsp_id in (5, 6, 7, 8, 9) --5:Kidney-Unknown, 6:Kidney-Right, 7:Kidney-Left, 8:Kidney-Both, 9:Kidney-Single
and trans.patr_id = patr.patr_id
and don.don_id = doc.don_id
and doc.doc_id = allo.doc_id
and allo.all_id = apl.all_id
and apl.prod_id = prod.prod_id
and don.cadaveric_flg = 'N' --living donor
and OS.ORGSP_ID = DOOR.ORGSP_ID
AND DOOR.ACCEPT_IND = 'Y'
AND DOOR.ACCEPT_CANCEL_DATE IS NULL
AND APL.APL_ID = DOOR.APL_ID
and pat.pat_id in (select pat.pat_id
from pat, pat_register patr
where patr.patr_id = p_patr_id)
and patr.patr_id not in -- Not suspend registration
(select patr_id from pat_register_suspend where end_date is null)
--Not canceled registration
and patr.exp_date is null;
--get patient transplant_date which has been transplanted (kidney) with living
cursor c_cur2 is
select min(distinct trans.transplant_date)
from pat,
pat_register patr,
pat_register_org_det prod,
transplant_org_det tod,
transplant trans,
don,
don_org_con doc,
allo,
all_pat_list apl,
ORG_SPEC OS,
DON_ORG_OFF_RES DOOR
where pat.pat_id = patr.pat_id
and patr.patr_id = prod.patr_id
and prod.prod_id = tod.prod_id --patient has been transplanted (kidney)
and tod.orgsp_id in (5, 6, 7, 8, 9) --5:Kidney-Unknown, 6:Kidney-Right, 7:Kidney-Left, 8:Kidney-Both, 9:Kidney-Single
and trans.patr_id = patr.patr_id
and don.don_id = doc.don_id
and doc.doc_id = allo.doc_id
and allo.all_id = apl.all_id
and apl.prod_id = prod.prod_id
and patr.patr_id = v_patr_id
and don.cadaveric_flg = 'N' --living donor
and OS.ORGSP_ID = DOOR.ORGSP_ID
AND DOOR.ACCEPT_IND = 'Y'
AND DOOR.ACCEPT_CANCEL_DATE IS NULL
AND APL.APL_ID = DOOR.APL_ID;
begin
--if patient current is Not waiting for pancreas
if (tttt_kp_allocation.is_pat_waiting_for_pancreas(p_patr_id) != 'Y') then
return v_rst;
end if;
open c_cur1;
loop
fetch c_cur1
into v_patr_id;
exit when c_cur1%notfound;
open c_cur2;
fetch c_cur2
into v_transplant_date;
if (v_rst is null) and (v_transplant_date is not null) then
v_rst := v_transplant_date;
end if;
--get earlier of Date of Living Donor transplant for PAK
if (v_rst is not null) and (v_transplant_date is not null) then
if (v_rst - v_transplant_date > 0) then
v_rst := v_transplant_date;
end if;
end if;
close c_cur2;
end loop;
close c_cur1;
return v_rst;
end;
The c_cur1 will return like :
patr_id
1001
1002
1003
1004
The c_cur2 will return a date like:
2001-jan-01 ( patr_id : 1001)
2002-jan-01 ( patr_id : 1002)
2003-jan-01 ( patr_id : 1003)
2004-jan-01 ( patr_id : 1004)
the final result v_rst will get the minimum date from (2001-jan-01, 2002-jan-01, 2003-jan-01, 2004-jan-01)
So, I need a way to get rid of the cursor loop (performance too bad) and using one statement get the final result.
Something like following may help you.
function get_pat_liv_donor_trans_date(p_patr_id IN NUMBER) return date is
v_transplant_date date;
v_rst date;
v_patr_id number;
--get patient all the patr_id(s) which has been transplanted (kidney) with living donor
select case when v_rst is null and B.transplant_date is not null then v_rst := v_transplant_date
when v_rst IS NOT NULL AND B.transplant_date is not null AND v_rst - v_transplant_date > 0 THEN v_rst := v_transplant_date
END
from (
select distinct patr.patr_id
from pat,
pat_register patr,
pat_register_org_det prod,
transplant_org_det tod,
transplant trans,
don,
don_org_con doc,
allo,
all_pat_list apl,
ORG_SPEC OS,
DON_ORG_OFF_RES DOOR
where pat.pat_id = patr.pat_id
and patr.patr_id = prod.patr_id
and prod.prod_id = tod.prod_id --patient has been transplanted (kidney)
and tod.orgsp_id in (5, 6, 7, 8, 9) --5:Kidney-Unknown, 6:Kidney-Right, 7:Kidney-Left, 8:Kidney-Both, 9:Kidney-Single
and trans.patr_id = patr.patr_id
and don.don_id = doc.don_id
and doc.doc_id = allo.doc_id
and allo.all_id = apl.all_id
and apl.prod_id = prod.prod_id
and don.cadaveric_flg = 'N' --living donor
and OS.ORGSP_ID = DOOR.ORGSP_ID
AND DOOR.ACCEPT_IND = 'Y'
AND DOOR.ACCEPT_CANCEL_DATE IS NULL
AND APL.APL_ID = DOOR.APL_ID
and pat.pat_id in (select pat.pat_id
from pat, pat_register patr
where patr.patr_id = p_patr_id)
and patr.patr_id not in -- Not suspend registration
(select patr_id from pat_register_suspend where end_date is null)
--Not canceled registration
and patr.exp_date is null
) A
inner join (
select patr.patr_id, min(trans.transplant_date)
from pat,
pat_register patr,
pat_register_org_det prod,
transplant_org_det tod,
transplant trans,
don,
don_org_con doc,
allo,
all_pat_list apl,
ORG_SPEC OS,
DON_ORG_OFF_RES DOOR
where pat.pat_id = patr.pat_id
and patr.patr_id = prod.patr_id
and prod.prod_id = tod.prod_id --patient has been transplanted (kidney)
and tod.orgsp_id in (5, 6, 7, 8, 9) --5:Kidney-Unknown, 6:Kidney-Right, 7:Kidney-Left, 8:Kidney-Both, 9:Kidney-Single
and trans.patr_id = patr.patr_id
and don.don_id = doc.don_id
and doc.doc_id = allo.doc_id
and allo.all_id = apl.all_id
and apl.prod_id = prod.prod_id
and patr.patr_id = v_patr_id
and don.cadaveric_flg = 'N' --living donor
and OS.ORGSP_ID = DOOR.ORGSP_ID
AND DOOR.ACCEPT_IND = 'Y'
AND DOOR.ACCEPT_CANCEL_DATE IS NULL
AND APL.APL_ID = DOOR.APL_ID
group by patr.patr_id
) B
on A.patr_id = B.patr_id
return v_rst;
Your query of c_cur1 seems to be incorrect. Its result doesn't depend on p_patr_id parameter.
The only place where p_patr_id is mentioned, is the following subquery:
(select pat.pat_id
from pat, pat_register patr
where patr.patr_id = p_patr_id)
The result of this subquery obviously doesn't depend on p_patr_id. :-)

How to generate model from database using Dapper?

I am coming from PetaPoco camp. PetaPoco has a T4 template which generates model from the database. Is anything similar available for Dapper?
I installed Dapper using NuGet and added SqlHelper.cs, but I didn't find anything which generates model from the database.
I've just recently written a sql query to do the job for myself. And updating it with extra types when i need. Just replace the table name where it says ####.
To make alot of tables i created a temp stored procedure to call. eg.
exec createTablePOCO(#tableName)
SELECT
'public ' + a1.NewType + ' ' + a1.COLUMN_NAME + ' {get;set;}'
,*
FROM (
/*using top because i'm putting an order by ordinal_position on it.
putting a top on it is the only way for a subquery to be ordered*/
SELECT TOP 100 PERCENT
COLUMN_NAME,
DATA_TYPE,
IS_NULLABLE,
CASE
WHEN DATA_TYPE = 'varchar' THEN 'string'
WHEN DATA_TYPE = 'datetime' AND IS_NULLABLE = 'NO' THEN 'DateTime'
WHEN DATA_TYPE = 'datetime' AND IS_NULLABLE = 'YES' THEN 'DateTime?'
WHEN DATA_TYPE = 'int' AND IS_NULLABLE = 'YES' THEN 'int?'
WHEN DATA_TYPE = 'int' AND IS_NULLABLE = 'NO' THEN 'int'
WHEN DATA_TYPE = 'smallint' AND IS_NULLABLE = 'NO' THEN 'Int16'
WHEN DATA_TYPE = 'smallint' AND IS_NULLABLE = 'YES' THEN 'Int16?'
WHEN DATA_TYPE = 'decimal' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'decimal' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'numeric' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'numeric' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'money' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'money' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'bigint' AND IS_NULLABLE = 'NO' THEN 'long'
WHEN DATA_TYPE = 'bigint' AND IS_NULLABLE = 'YES' THEN 'long?'
WHEN DATA_TYPE = 'tinyint' AND IS_NULLABLE = 'NO' THEN 'byte'
WHEN DATA_TYPE = 'tinyint' AND IS_NULLABLE = 'YES' THEN 'byte?'
WHEN DATA_TYPE = 'char' THEN 'string'
WHEN DATA_TYPE = 'timestamp' THEN 'byte[]'
WHEN DATA_TYPE = 'varbinary' THEN 'byte[]'
WHEN DATA_TYPE = 'bit' AND IS_NULLABLE = 'NO' THEN 'bool'
WHEN DATA_TYPE = 'bit' AND IS_NULLABLE = 'YES' THEN 'bool?'
WHEN DATA_TYPE = 'xml' THEN 'string'
END AS NewType
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '####'
ORDER BY ORDINAL_POSITION
) as a1
Calling the stored procedure from a cursor
If you combine the sp mattritchies mentioned (see answer above) and call it from a cursor you can generate the poco class for every table in your database
USE YourDataBaseName
GO
DECLARE #field1 nvarchar(400)
DECLARE cur CURSOR LOCAL for
SELECT TABLE_NAME FROM information_schema.tables
OPEN cur
FETCH NEXT FROM cur INTO #field1 --, #field2
WHILE ##FETCH_STATUS = 0 BEGIN
exec Helper_CreatePocoFromTableName #field1 -- , #field2
fetch next from cur into #field1 -- , #field2
END
close cur
deallocate cur
Stored Procedure mattritchies mentioned
I took the sql from mattritchies answer (see above) and created the stored procedure he mentioned and modified it a bit so that it adds the class name as well. If you put Management Studio into Text-Output-Mode and remove the output of the column names you get copy paste text for all classes:
CREATE PROCEDURE [dbo].[Helper_CreatePocoFromTableName]
#tableName varchar(100)
AS
BEGIN
SET NOCOUNT ON;
-- Subquery to return only the copy paste text
Select PropertyColumn from (
SELECT 1 as rowNr, 'public class ' + #tableName + ' {' as PropertyColumn
UNION
SELECT 2 as rowNr, 'public ' + a1.NewType + ' ' + a1.COLUMN_NAME + ' {get;set;}' as PropertyColumn
-- ,* comment added so that i get copy pasteable output
FROM
(
/*using top because i'm putting an order by ordinal_position on it.
putting a top on it is the only way for a subquery to be ordered*/
SELECT TOP 100 PERCENT
COLUMN_NAME,
DATA_TYPE,
IS_NULLABLE,
CASE
WHEN DATA_TYPE = 'varchar' THEN 'string'
WHEN DATA_TYPE = 'nvarchar' THEN 'string'
WHEN DATA_TYPE = 'datetime' AND IS_NULLABLE = 'NO' THEN 'DateTime'
WHEN DATA_TYPE = 'datetime' AND IS_NULLABLE = 'YES' THEN 'DateTime?'
WHEN DATA_TYPE = 'smalldatetime' AND IS_NULLABLE = 'NO' THEN 'DateTime'
WHEN DATA_TYPE = 'datetime2' AND IS_NULLABLE = 'NO' THEN 'DateTime'
WHEN DATA_TYPE = 'smalldatetime' AND IS_NULLABLE = 'YES' THEN 'DateTime?'
WHEN DATA_TYPE = 'datetime2' AND IS_NULLABLE = 'YES' THEN 'DateTime?'
WHEN DATA_TYPE = 'int' AND IS_NULLABLE = 'YES' THEN 'int?'
WHEN DATA_TYPE = 'int' AND IS_NULLABLE = 'NO' THEN 'int'
WHEN DATA_TYPE = 'smallint' AND IS_NULLABLE = 'NO' THEN 'Int16'
WHEN DATA_TYPE = 'smallint' AND IS_NULLABLE = 'YES' THEN 'Int16?'
WHEN DATA_TYPE = 'decimal' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'decimal' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'numeric' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'numeric' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'money' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'money' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'bigint' AND IS_NULLABLE = 'NO' THEN 'long'
WHEN DATA_TYPE = 'bigint' AND IS_NULLABLE = 'YES' THEN 'long?'
WHEN DATA_TYPE = 'tinyint' AND IS_NULLABLE = 'NO' THEN 'byte'
WHEN DATA_TYPE = 'tinyint' AND IS_NULLABLE = 'YES' THEN 'byte?'
WHEN DATA_TYPE = 'char' THEN 'string'
WHEN DATA_TYPE = 'timestamp' THEN 'byte[]'
WHEN DATA_TYPE = 'varbinary' THEN 'byte[]'
WHEN DATA_TYPE = 'bit' AND IS_NULLABLE = 'NO' THEN 'bool'
WHEN DATA_TYPE = 'bit' AND IS_NULLABLE = 'YES' THEN 'bool?'
WHEN DATA_TYPE = 'xml' THEN 'string'
END AS NewType
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tableName
ORDER BY ORDINAL_POSITION
) AS a1
UNION
SELECT 3 as rowNr, '} // class ' + #tableName
) as t Order By rowNr asc
END
P.S.: I would have done it as an edit suggestion to his answers but my experience is that often edit suggestions get rejected.
Update
User chris-w-mclean suggested the following changes (see his suggested-edit) which i have not tried myself:
Replace SELECT 1 as rowNr, 'public class ' with SELECT 1.0 as rowNr, 'public class '
Replace SELECT 2 as rowNr, 'public ' with SELECT 2 + a1.ORDINAL_POSITION/1000 as rowNr, 'public '
Replace SELECT TOP 100 PERCENT COLUMN_NAME, with SELECT COLUMN_NAME,
add between IS_NULLABLE, CASE this line cast(ORDINAL_POSITION as float) as ORDINAL_POSITION,
remove ORDER BY ORDINAL_POSITION
change SELECT 3 as to SELECT 3.0 as
Try this version I optimized a bit, so that the result doesn't need to be piped to Text output. Instead, the PRINT statement allows the output to be copy/pasted easily. I've also removed the subquery and added declarations for nvarchar/ntext types.
This is for a single table, but it can be converted to a stored proc to use one of the cursor suggestions above.
SET NOCOUNT ON
DECLARE #tbl as varchar(255)
SET #tbl = '####'
DECLARE #flds as varchar(8000)
SET #flds=''
SELECT -1 as f0, 'public class ' + #tbl + ' {' as f1 into #tmp
INSERT #tmp
SELECT
ORDINAL_POSITION,
' public ' +
CASE
WHEN DATA_TYPE = 'varchar' THEN 'string'
WHEN DATA_TYPE = 'nvarchar' THEN 'string'
WHEN DATA_TYPE = 'text' THEN 'string'
WHEN DATA_TYPE = 'ntext' THEN 'string'
WHEN DATA_TYPE = 'char' THEN 'string'
WHEN DATA_TYPE = 'xml' THEN 'string'
WHEN DATA_TYPE = 'datetime' AND IS_NULLABLE = 'NO' THEN 'DateTime'
WHEN DATA_TYPE = 'datetime' AND IS_NULLABLE = 'YES' THEN 'DateTime?'
WHEN DATA_TYPE = 'int' AND IS_NULLABLE = 'YES' THEN 'int?'
WHEN DATA_TYPE = 'int' AND IS_NULLABLE = 'NO' THEN 'int'
WHEN DATA_TYPE = 'smallint' AND IS_NULLABLE = 'NO' THEN 'Int16'
WHEN DATA_TYPE = 'smallint' AND IS_NULLABLE = 'YES' THEN 'Int16?'
WHEN DATA_TYPE = 'decimal' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'decimal' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'numeric' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'numeric' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'money' AND IS_NULLABLE = 'NO' THEN 'decimal'
WHEN DATA_TYPE = 'money' AND IS_NULLABLE = 'YES' THEN 'decimal?'
WHEN DATA_TYPE = 'bigint' AND IS_NULLABLE = 'NO' THEN 'long'
WHEN DATA_TYPE = 'bigint' AND IS_NULLABLE = 'YES' THEN 'long?'
WHEN DATA_TYPE = 'tinyint' AND IS_NULLABLE = 'NO' THEN 'byte'
WHEN DATA_TYPE = 'tinyint' AND IS_NULLABLE = 'YES' THEN 'byte?'
WHEN DATA_TYPE = 'timestamp' THEN 'byte[]'
WHEN DATA_TYPE = 'varbinary' THEN 'byte[]'
WHEN DATA_TYPE = 'bit' AND IS_NULLABLE = 'NO' THEN 'bool'
WHEN DATA_TYPE = 'bit' AND IS_NULLABLE = 'YES' THEN 'bool?'
END + ' ' + COLUMN_NAME + ' {get;set;}'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tbl
INSERT #tmp SELECT 999, '}'
SELECT #flds=#flds + f1 +'
' from #tmp order by f0
DROP TABLE #tmp
PRINT #flds
Dapper itself provides few extension methods (Query, Execute) for the connection object and does not have "model generator." Perhaps some other framework can be used to generate POCO's based on the db schema.
Update:
Database tables to C# POCO classes T4 template
<## template language="C#" debug="True" #>
<## assembly name="System" #>
<## assembly name="System.Data" #>
<## assembly name="System.Core" #>
<## assembly name="System.Xml" #>
<## assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<## assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<## assembly name="Microsoft.SqlServer.Smo" #>
<## import namespace="System" #>
<## import namespace="System.Text" #>
<## import namespace="System.Xml" #>
<## import namespace="Microsoft.SqlServer.Management.Smo" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="Microsoft.SqlServer.Management.Common" #>
namespace Namespace
{
<#
var databaseName = "testDb";
var serverConnection = new SqlConnection(
#"Data Source=.\SQLEXPRESS; Integrated Security=true; Initial Catalog=" + databaseName);
var svrConnection = new ServerConnection(serverConnection);
Server srv = new Server(svrConnection);
foreach (Table table in srv.Databases[databaseName].Tables)
{
#>
class <#= table.Name #>
{
<#
foreach (Column col in table.Columns)
{
#>
public <#= GetNetDataType(col.DataType.Name) #> <#= col.Name #> { get; set; }
<#
}
#>
}
<# }
#>
}
<#+
public static string GetNetDataType(string sqlDataTypeName)
{
switch (sqlDataTypeName.ToLower())
{
case "bigint":
return "Int64";
case "binary":
return "Byte[]";
case "bit":
return "bool";
case "char":
return "char";
case "cursor":
return string.Empty;
case "datetime":
return "DateTime";
case "decimal":
return "Decimal";
case "float":
return "Double";
case "int":
return "int";
case "money":
return "Decimal";
case "nchar":
return "string";
case "numeric":
return "Decimal";
case "nvarchar":
return "string";
case "real":
return "single";
case "smallint":
return "Int16";
case "text":
return "string";
case "tinyint":
return "Byte";
case "varbinary":
return "Byte[]";
case "xml":
return "string";
case "varchar":
return "string";
case "smalldatetime":
return "DateTime";
case "image":
return "byte[]";
default:
return string.Empty;
}
}
#>
My approach is to:
Use <dynamic> to fetch some rows without type
Serialize these rows to JSON
Copy the JSON string from the console (or using the debugger)
Paste this into a JSON to C# model generator (e.g. https://app.quicktype.io/).
I.e.:
var persons = connection.Query<dynamic>("SELECT * FROM Persons");
var serializedPerson = JsonConvert.Serialize(persons.First());
Console.WriteLine(serializedPerson);
This one is for Oracle. It's probably not complete, but it's worked for me thus far.
SELECT
'public ' || A.NewType || ' ' || REPLACE(INITCAP(REPLACE(A.COLUMN_NAME, '_', ' ')), ' ', '') || ' {get;set;}' GET_SET
, A.*
FROM
(
SELECT
COLUMN_NAME,
DATA_TYPE,
NULLABLE,
CASE
WHEN DATA_TYPE = 'VARCHAR2' THEN 'string'
WHEN DATA_TYPE = 'VARCHAR' THEN 'string'
WHEN DATA_TYPE = 'DATE' AND NULLABLE = 'N' THEN 'DateTime'
WHEN DATA_TYPE = 'DATE' AND NULLABLE = 'Y' THEN 'DateTime?'
WHEN DATA_TYPE = 'INT' AND NULLABLE = 'N' THEN 'int?'
WHEN DATA_TYPE = 'INT' AND NULLABLE = 'Y' THEN 'int'
WHEN DATA_TYPE = 'DECIMAL' AND NULLABLE = 'N' THEN 'decimal'
WHEN DATA_TYPE = 'DECIMAL' AND NULLABLE = 'Y' THEN 'decimal?'
WHEN DATA_TYPE = 'NUMBER' AND NULLABLE = 'N' THEN 'decimal'
WHEN DATA_TYPE = 'NUMBER' AND NULLABLE = 'Y' THEN 'decimal?'
WHEN DATA_TYPE = 'NUMBER2' AND NULLABLE = 'N' THEN 'decimal'
WHEN DATA_TYPE = 'NUMBER2' AND NULLABLE = 'Y' THEN 'decimal?'
WHEN DATA_TYPE = 'CHAR' THEN 'string'
WHEN DATA_TYPE = 'CHAR2' THEN 'string'
WHEN DATA_TYPE = 'timestamp' THEN 'byte[]'
WHEN DATA_TYPE = 'CLOB' THEN 'byte[]'
ELSE '??'
END AS NewType
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = 'FIN_GLOBAL_CR_NUM_A'
ORDER BY COLUMN_ID
) A
Here's dapper-pocos I made for generating POCOs for Dapper. The solution uses SQL Server's "sp_HELP" and "sp_describe_first_result_set". Give it the name of a stored procedure, or give it a select statement, and it will generate the related POCOs for use with Dapper. The app just passes the stored procedure or select statement to sp_Help and sp_describe_first_result_set, and maps the results to C# data types.
I know it is an old topic,but there is another simple option can choose.
You can use PocoClassGenerator: Mini Dapper's POCO Class Generator (Support Dapper Contrib)
Support current DataBase all tables and views generate POCO class code
Support Dapper.Contrib
Support mutiple RDBMS : sqlserver,oracle,mysql,postgresql
mini and faster (only in 5 seconds generate 100 tables code)
Use appropriate dialect schema table SQL for each database query
DEMO
POCOGenerator Generate Class By Dynamic SQL | .NET Fiddle
POCO Class Generator GenerateAllTables | .NET Fiddle
DataTable POCO Class Generator | .NET Fiddle
GetStart
👇First : Copy&Paste PocoClassGenerator.cs Code to your project or LINQPad.
or Install from NuGet
PM> install-package PocoClassGenerator
👇Second : Use Connection to call GenerateAllTables and then print it.
using (var connection = Connection)
{
Console.WriteLine(connection.GenerateAllTables());
}
Support Dapper Contrib POCO Class
Just call method with GeneratorBehavior.DapperContrib
using (var conn = GetConnection())
{
var result = conn.GenerateAllTables(GeneratorBehavior.DapperContrib);
Console.WriteLine(result);
}
The Online Demo : POCO Dapper Contrib Class Generator GenerateAllTables | .NET Fiddle
Generate Comment
using (var conn = GetConnection())
{
var result = conn.GenerateAllTables(GeneratorBehavior.Comment);
Console.WriteLine(result);
}
Generate View
using (var conn = GetConnection())
{
var result = conn.GenerateAllTables(GeneratorBehavior.View);
Console.WriteLine(result);
}
Generate View and Comment and Dapper.Contrib
using (var conn = GetConnection())
{
var result = conn.GenerateAllTables(GeneratorBehavior.View | GeneratorBehavior.Comment | GeneratorBehavior.DapperContrib);
Console.WriteLine(result);
}
Generate one class by sql
Generate one class
using (var connection = Connection)
{
var classCode = connection.GenerateClass("select * from Table");
Console.WriteLine(classCode);
}
Specify class name
using (var connection = Connection)
{
var classCode = connection.GenerateClass("with EMP as (select 1 ID,'WeiHan' Name,25 Age) select * from EMP", className: "EMP");
Console.WriteLine(classCode);
}
DataTablePocoClass
Code at DataTablePocoClassGenerator.cs
var dt = new DataTable();
dt.TableName = "TestTable";
dt.Columns.Add(new DataColumn() { ColumnName = "ID", DataType = typeof(string) });
var result = dt.GenerateClass();
var expect =
#"public class TestTable
{
public string ID { get; set; }
}";
Assert.Equal(expect, result);
I had exactly the same requirement to generate objects from a database while handling CRUD reliably and efficiently in Dapper and took a different approach of preparing a replacement for Dapper's own Dapper.Contrib with support of Entity Framework schema definition so that scaffolding a database (models, relations, keys) can be done using Entity Framework tools like described for example here, sample below:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet ef dbcontext scaffold "Server=.\;Database=AdventureWorksLT2012;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Model
Above dependencies can be removed from the project after scaffolding.
Currently Dapper.SqlGenerator is successfully working in production. It does not produce any overhead over Dapper in terms of performance, sometimes reducing time to generate a query by other means.
Keep in mind there are 2 separate nuget packages - Dapper.SqlGenerator for purely SQL Code generation from EF (Core) scaffolded models and Dapper.SqlGenerator.Async to run CRUD queries against the database using Dapper.
TLDR; You can use Entity Framework (Core) to scaffold model from database and use Dapper.SqlGenerator to generate CRUD queries on generated objects.
I've seen where people use a hybrid project, using EF to scaffold the database, but I had to dapperize the output from that. For the recommended tools, I'm sure they are good, but I shy away from installing special software, until I had a stab at writing my own solution.
That said, here's a small CLI program(for my needs) that may be useful. Disclaimer, I'm not a seasoned C# programmer, so forgive anything that may be off kilter.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using Dapper;
namespace Pocos
{
public class TAB {
public string TABLE_NAME { get; set; }
}
public class COL {
public string COLUMN_NAME { get; set; }
public int? ORIDINAL_POSITIONS { set; get; }
public string DATA_TYPE { get; set; }
public string CHARACTER_MAXIMUM_LENGTH { get; set; }
public string NUMERIC_PRECISION { get; set; }
public string NUMERIC_SCALE { get; set; }
}
class Program {
static void Main(string[] args) {
string sConnect = "Server=LT6-MARKL;Database=PKDEM815;UID=PKDEM815;Password=PKDEM815";
IEnumerable tables;
IEnumerable columns;
List lines;
using ( var conn = new SqlConnection(sConnect))
tables = conn.Query("SELECT * FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_NAME");
// Roll through each table of the database and generate an .cs file, as a POCO
foreach (TAB t in tables.OrderBy(t => t.TABLE_NAME)) {
lines = new List();
lines.Add("using System;");
lines.Add("using System.Collections.Generic;");
lines.Add("using System.Configuration;");
lines.Add("using System.Data.SqlClient;");
lines.Add("using Dapper;");
lines.Add("using Dapper.Contrib.Extensions;");
lines.Add("");
lines.Add("namespace PKDataLayer.Models {");
lines.Add("");
lines.Add("\t[Table(\"" + t.TABLE_NAME + "\")]");
lines.Add("\tpublic class " + t.TABLE_NAME + " {");
lines.Add("");
using (var conn2 = new SqlConnection(sConnect)) {
columns = conn2.Query("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '"+ t.TABLE_NAME +"' ORDER BY ORDINAL_POSITION");
foreach( COL c in columns) {
if (t.TABLE_NAME + "_KEY" == c.COLUMN_NAME || t.TABLE_NAME + "_SEQNUM" == c.COLUMN_NAME)
lines.Add("\t\t[Key]");
// SELECT DISTINCT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME IN ( SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES )
if (c.DATA_TYPE == "char" || c.DATA_TYPE == "varchar")
lines.Add("\t\tpublic string " + c.COLUMN_NAME + " { get; set; }");
if (c.DATA_TYPE == "int")
lines.Add("\t\tpublic int " + c.COLUMN_NAME + " { get; set; }");
if (c.DATA_TYPE == "datetime")
lines.Add("\t\tpublic DateTime? " + c.COLUMN_NAME + " { get; set; }");
if (c.DATA_TYPE == "decimal" || c.DATA_TYPE == "numeric")
lines.Add("\t\tpublic decimal? " + c.COLUMN_NAME + " { get; set; }");
}
}
lines.Add("\t}");
lines.Add("}");
Console.WriteLine("Creating POCO for " + t.TABLE_NAME);
using (TextWriter tw = new StreamWriter( t.TABLE_NAME + ".cs" ))
foreach (String s in lines)
tw.WriteLine(s);
}
}
}
}
This might not work with VS2010, but if you've been updating your Version this should work.
My way of generating Models from a Database is with Ef Core Power Tools, a little Add-On that uses Ef Core 6.
Go to Extensions in Visual Studio and install it. After that, you can right-click on your Project and select Reverse Engineer under EF Core Power Tools.
From there, you connect to the Database, select the Tables to be reverse-engineered and select EntityTypes only
You can be as specific as you want, e.g. specify the output path (DbModels in my case). Click on OK.
Then, your Models should pop up and you're free to use these Models in your Dapper-Code.
I'm the author of a POCO-Generator template called CodegenCS.POCO.
The link above contains C# and PowerShell Scripts which allow you to build complete POCOs (ready to use in Dapper) which also include override bool Equals(), override int GetHashCode(), and (for those who like it) it includes full ActiveRecord CRUD queries (Insert/Update).
Check out this example POCO from Northwind database. If you like it, it's very easy to use the templates:
Edit the connection string and paths in RefreshSqlServerSchema.csx and invoke it through PowerShell script RefreshSqlServerSchema.ps1
This will Extract the Schema of a SQL Server database into a JSON file
Edit the paths and the POCOs Namespace in GenerateSimplePOCOs.csx and invoke it through PowerShell script GenerateSimplePOCOs.ps1
This will Read the JSON Schema and build the POCOs.
The generator script is very simple to understand and customize.
So based on some SQL here and there. I've build a "simple" select that generate class(es) for table(s) or a schema(s)!
What's nice about it, is that it will:
Keep the column order
Adding namespaces
Add the [Table] Attributes on the class.
Add the [Key] on the primary key(s) (might not be supported by dapper.contrib if 2+ PK)
The function below can be used like so:
select *
from dbo.TableToClass('schema','null or table name') m
where m.TableName not in('unwantedtablename')
order by m.TableSchema asc
, m.TableName asc
, m.ClassOrder asc
, m.ColumnOrder asc;
When you copy paste from SSMS, it might remove the TAB. Here is the code:
CREATE or alter function dbo.TableToClass(
#schema varchar(250)
, #table varchar(250)
)
returns table
as
return
/*
--USE IT LIKE: the order by is necessary on the query
select *
from dbo.TableToClass('schemaName', 'null or table name') m
order by
m.tableSchema asc
, m.tableName asc
, m.ClassOrder asc
, m.columnOrder asc
*/
with typeConversion as(
Select
typeConversion.sqlType
,typeConversion.isNullable
,typeConversion.cSharpType
from
( values
('nvarchar', 'YES', 'string'), ('nvarchar','NO', 'string')
,('varchar', 'YES', 'string'), ('varchar', 'NO', 'string')
,('char', 'YES', 'string'), ('char', 'NO', 'string')
,('datetime', 'YES', 'DateTime?'), ('datetime', 'NO', 'DateTime')
,('datetime2', 'YES', 'DateTime?'), ('datetime2', 'NO', 'DateTime')
,('date', 'YES', 'DateTime?'), ('date', 'NO', 'DateTime')
,('datetimeoffset', 'YES', 'DateTimeOffset?'), ('datetimeoffset', 'NO', 'DateTimeOffset')
,('time', 'YES', 'TimeSpan?'), ('timestamp', 'NO', 'TimeSpan')
,('bigint', 'YES', 'long?'), ('bigint', 'NO', 'long')
,('int', 'YES', 'int?'), ('int', 'NO', 'int')
,('smallint', 'YES', 'Int16?'), ('smallint','NO', 'Int16')
,('decimal', 'YES', 'decimal?'), ('decimal', 'NO', 'decimal')
,('numeric', 'YES', 'decimal?'), ('numeric', 'NO', 'decimal')
,('money', 'YES', 'decimal?'), ('money', 'NO', 'decimal')
,('tinyint', 'YES', 'byte?'), ('tinyint', 'NO', 'byte')
,('varbinary', 'YES', 'byte[]'), ('varbinary', 'NO', 'byte[]?')
,('bit', 'YES', 'bool?'), ('bit', 'NO', 'bool')
,('xml', 'YES', 'string'), ('xml', 'NO', 'string')
) typeConversion(sqlType, isNullable, cSharpType)
), columnInfo as (
select
colInfo.TABLE_SCHEMA
, colInfo.TABLE_NAME
, concat(colInfo.TABLE_SCHEMA, '.', colInfo.TABLE_NAME) FullTableName
, colInfo.COLUMN_NAME
, colInfo.ORDINAL_POSITION
, typeConversion.sqlType
, typeConversion.csharpType
,case
(
Select top 1 pk.CONSTRAINT_TYPE
from INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS columnUsage
join INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS pk
--on pk.CONSTRAINT_TYPE = 'PRIMARY KEY'
on pk.TABLE_SCHEMA = colInfo.TABLE_SCHEMA
and pk.TABLE_NAME = colInfo.TABLE_NAME
and pk.CONSTRAINT_NAME = columnUsage.CONSTRAINT_NAME
where
columnUsage.TABLE_SCHEMA = colInfo.TABLE_SCHEMA
and columnUsage.TABLE_NAME = colInfo.TABLE_NAME
and columnUsage.COLUMN_NAME = colInfo.COLUMN_NAME
)
when 'PRIMARY KEY' then (
case (select COLUMNPROPERTY(OBJECT_ID(concat(colInfo.TABLE_SCHEMA ,'.',colInfo.TABLE_NAME)),colInfo.COLUMN_NAME,'isidentity'))
when 1 then 'PK IDENTITY'
else 'PK'
end
)
when 'FOREIGN KEY' then 'FK'
else 'COL'
end as ColumnType
from INFORMATION_SCHEMA.COLUMNS as colInfo
left join typeConversion on typeConversion.sqlType = colInfo.DATA_TYPE and typeConversion.isNullable = colInfo.IS_NULLABLE
where
/************ SET PARAMETER / CONDITION HERE **************/
( --SCHEMA
'True' = (
case
--when #schema is null then return 'True'
when colInfo.TABLE_SCHEMA = coalesce(#schema, 'dbo') then 'True'
else 'False'
end
)
And -- SET SCHEMA NAME HERE (might be dbo for default)
'True' = ( --Table
case
when #table is null then 'True'
when colInfo.TABLE_NAME = #table then 'True'
else 'False'
end
)
)
), classBuilder2_StartFile as (
select top 1
concat(
'using System;', char(10)
,'using Dapper;', char(10)
,'using Dapper.Contrib;', char(10)
,'using Dapper.Contrib.Extensions;', char(10)
, char(10)
,'namespace MYPROJECTNAMESPACE.',c.TABLE_SCHEMA, '.Models.Db; ', char(10)
) as txt
, 'Using & Namespace' as ClassPart
, 5 as ClassOrder
, c.TABLE_SCHEMA as tableSchema
from columnInfo c
), classBuilder2_StartClass as(
select distinct
concat(
char(10)
, '[Table("',c.FullTableName,'")]', char(10)
, 'public partial class ', c.TABLE_NAME, char(10)
, '{'
) as txt
, 'Class name' as ClassPart
, 17 as ClassOrder
, c.TABLE_NAME as tableName
, c.TABLE_SCHEMA as tableSchema
from columnInfo c
), classBuilder2_Properties as(
select
concat(
case c.ColumnType --Column Attribute for dapper.
when 'PK' then
concat(Char(9),'[ExplicitKey]', char(10)) --Dapper: After insert return 0
when 'PK IDENTITY'then
concat(Char(9),'[Key]', char(10)) -- Dapper: After inser return actual PK
else ''
end
, Char(9), char(9), 'public ', c.csharpType,' ', c.COLUMN_NAME, ' { get; set; }'
) as txt
, ORDINAL_POSITION as columnOrder
, 'Property' as ClassPart
, 30 as ClassOrder
, c.COLUMN_NAME as columnName
, c.TABLE_NAME as tableName
, c.TABLE_SCHEMA as tableSchema
from columnInfo c
), classBuilder2_EndClass as(
select distinct
concat(
char(9), '}'
) as txt
, 'End of C# Class' as ClassPart
, 111 as ClassOrder
, c.TABLE_NAME as tableName
, c.TABLE_SCHEMA as tableSchema
from columnInfo c
), classBuilder2_EndFile as(
select top 1
concat(
char(10),char(10)
) as txt
, 'End of C# Class' as ClassPart
, 120 as ClassOrder
, 'ZZZZZ' as tableName
, 'ZZZZZ' as tableSchema
from columnInfo c
), classBuilder_merge as(
select txt, ClassPart, ClassOrder, 'AAA_SCHEMA' as tableName, tableSchema, 'N/A' as columnName, 0 as columnOrder
from classBuilder2_StartFile
union all
select txt, ClassPart, ClassOrder, tableName, tableSchema, 'N/A' as columnName, 0 as columnOrder
from classBuilder2_StartClass
union all
select txt, ClassPart, ClassOrder, tableName, tableSchema, columnName, columnOrder
from classBuilder2_Properties
union all
select txt, ClassPart, ClassOrder, tableName, tableSchema, 'N/A'as columnNam, 0 as columnOrder
from classBuilder2_EndClass
union all
select txt, ClassPart, ClassOrder, tableName, tableSchema, 'N/A'as columnNam, 0 as columnOrder
from classBuilder2_EndFile
), finalSelect as(
--AFTER SQL Server 2016 (13.x) and later. If before that, remove CROSS APPLY STRING_SPLIT
select top 100 percent
lines.*
, m.tableSchema
, m.tableName
, m.ClassOrder
, m.columnOrder
from classBuilder_merge m
CROSS APPLY STRING_SPLIT(m.txt, char(10)) lines
order by --
m.tableSchema asc
, m.tableName asc
, m.ClassOrder asc
, m.columnOrder asc
)
select * from finalSelect;

Resources