Using a view with no primary key with Entity

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


Expanding on the answer from Michael Buen: I found that adding the row number to the view with an ISNULL() will allow the entity framework to pull the view in and create the necessary EntitySet data automatically.

create view RowNumberedView as

select 
    ISNULL(ROW_NUMBER() OVER (ORDER BY <column>), 0) AS RN
    , *
from your_existing_view

In case you are using Entity Framework with MVC in ASP.NET

As previously said, create your view with column that has auto-increment or ROW_NUMBER. Let's say you have that column and it's name is rowNumber.

an than go to context file (yourDatabaseNameContext) file in your Models directory of your MVC application, find definition for your view and instead of

modelBuilder.Entity<yourView>(entity =>
    {
        entity.HasNoKey();

change it to:

 modelBuilder.Entity<yourView>(entity =>
            {
                entity.HasKey(e => e.rowNumber);

At work recently, i ran into this same issue. Based on my research, I could not find any answers on how to attach a view to EF6 CodeFirst without a PK. Most seem to involve migrations and were quite confusing. I believe DB first has better support for working SQL VIEWS.

I did try introducing a window function (RowNumber) with the idea being, to use the row identifier as the PK to keep EF6 Happy. But this made my query more expensive overall so i had to drop this idea.

In the end, I had to carefully analyse my data set to see if i could introduce a composite key - one that covers all of the scenarios my business application needed to ensure would work. Remember to use IsNull(ColumnName,0) too to ensure you can satisfy the .IsRequired() in CodeFirst fluent methods.

i.e

HasKey(x => new { x.KfiId, x.ApplicationNumber, x.CustomerId });

I hope this helps someone - the answer for me was analyse the dataset the view denormalises and look for a composite key.

Another cool idea you can try suggested by Marc Cals.

Tags:

Sql

Oracle

Entity