I just started on a project converting an application from raw ADO.NET and embedded SQL to Entity. I ran in to a problem with one of the views used by the application. The view has no primary key and no column (or combination of columns) that uniquely identifies a row. Here is the select the view is created with:
SELECT
filingmonth,
CEIL(filingmonth / 3),
licnum,
filingyear,
DECODE(GROUPING(insurername), '1', '- All Insured -', insurername),
insurername,
policylinecode,
linedescription,
SUM(NVL(grosspremium, 0)),
SUM(DECODE(taxexempt, 1, grosspremium, 0)),
TRUNC(
CASE
WHEN
(
b.rsn IS NOT NULL
OR A.zeroreport = 1
)
AND b.datereceived IS NULL
THEN A.datereceived
ELSE b.datereceived
END),
SUM(aip.iscompanyadmitted(b.naiccocode, b.naicalienid)),
A.insuredid
FROM
aip.slbtransinsured A
LEFT OUTER JOIN aip.slbtransinsurer b
ON
A.insuredid = b.insuredid
LEFT OUTER JOIN aip.slblinecodes C
ON
b.policylinecode = C.linecode
WHERE
A.submitted = 1
AND A.entryincomplete = 0
GROUP BY
licnum,
filingmonth,
filingyear,
TRUNC(
CASE
WHEN
(
b.rsn IS NOT NULL
OR A.zeroreport = 1
)
AND b.datereceived IS NULL
THEN A.datereceived
ELSE b.datereceived
END),
ROLLUP(insurername, aip.iscompanyadmitted(b.naiccocode, b.naicalienid),
policylinecode, linedescription), A.insuredid;
And here is some sample data showing that there are some rows that are completely duplicated (rows 3 and 4):
FILINGMONTH CEIL(FILINGMONTH/3) LICNUM FILINGYEAR DECODE(GROUPING(INSURERNAME),'1','-ALLINSURED-',INSURERNAME) INSURERNAME POLICYLINECODE LINEDESCRIPTION SUM(NVL(GROSSPREMIUM,0)) SUM(DECODE(TAXEXEMPT,1,GROSSPREMIUM,0)) TRUNC(CASEWHEN(B.RSNISNOTNULLORA.ZEROREPORT=1)ANDB.DATERECEIVEDISNULLTHENA.DATERECEIVEDELSEB.DATERECEIVEDEND) SUM(AIP.ISCOMPANYADMITTED(B.NAICCOCODE,B.NAICALIENID)) INSUREDID
6 2 8150 2007 SAVERS PROPERTY AND CASUALTY INSURANCE CO SAVERS PROPERTY AND CASUALTY INSURANCE CO 17 OTHER LIABILITY 721.25 0 18-JUL-07 0 81
6 2 8150 2007 SAVERS PROPERTY AND CASUALTY INSURANCE CO SAVERS PROPERTY AND CASUALTY INSURANCE CO 17 721.25 0 18-JUL-07 0 81
6 2 8150 2007 SAVERS PROPERTY AND CASUALTY INSURANCE CO SAVERS PROPERTY AND CASUALTY INSURANCE CO 721.25 0 18-JUL-07 0 81
6 2 8150 2007 SAVERS PROPERTY AND CASUALTY INSURANCE CO SAVERS PROPERTY AND CASUALTY INSURANCE CO 721.25 0 18-JUL-07 0 81
insuredid is the pk for the aip.slbtransinsured table, rsn is the pk for aip.slbtransinsurer and aip.slblinecodes.
Is it at all possible to add a view to the Entity model without a unique identifier? Or is there an easy way to add a unique row identifier to the view? The view is only read from, never written to.
Is it at all possible to add a view to the Entity model without a unique identifier?
If without a primary key, no. That will result to this kind of error:
One or more validation errors were detected during model generation:
System.Data.Edm.EdmEntityType: : EntityType 'SalesOnEachCountry' has no key defined. Define the key for this EntityType. System.Data.Edm.EdmEntitySet: EntityType: The EntitySet SalesOnEachCountryList is based on type SalesOnEachCountry that has no keys defined.
If without a unique identifier, yes, albeit it has a non-desirable output. Records with same identifier would reference the same object, this is called Identity Map Pattern
An example, even if your view produces these two rows:
Country Year TotalSales
Philippines 2010 20.000000
Philippines 2011 40.000000
If you will just map the primary key on Country field only, e.g.
public class SalesOnEachCountry
{
[Key]
public int CountryId { get; set; }
public string CountryName { get; set; }
public int OrYear { get; set; }
public long SalesCount { get; set; }
public decimal TotalSales { get; set; }
}
, even your view produces the above two rows on your Oracle query editor, Entity Framework produces this incorrect output:
Country Year TotalSales
Philippines 2010 20.000000
Philippines 2010 20.000000
Entity Framework will take it that the second row is same object as first row.
To guarantee uniqueness, you must identify what columns that makes each row unique. In the above example, Year must be included so the primary key is unique. i.e.
public class SalesOnEachCountry
{
[Key, Column(Order=0)] public int CountryId { get; set; }
public string CountryName { get; set; }
[Key, Column(Order=1)] public int OrYear { get; set; }
public long SalesCount { get; set; }
public decimal TotalSales { get; set; }
}
Making your primary key similar to the attributes above, Entity Framework can correctly map your each view's row to their own objects. Hence, Entity Framework can now display exactly the same rows your view have.
Country Year TotalSales
Philippines 2010 20.000000
Philippines 2011 40.000000
Full details here: http://www.ienablemuch.com/2011/06/mapping-class-to-database-view-with.html
Then regarding your views which don't have any columns to make a row unique, the easiest way to guarantee Entity Framework can map each of your view's row to their own objects is to create a separate column for your view's primary key, a good candidate is to just create a row number column on each row. e.g.
create view RowNumberedView as
select
row_number() over(order by <columns of your view sorting>) as RN
, *
from your_existing_view
Then assign the [Key]
attribute on RN property of your class RowNumberedView