【问题标题】:Using a view with no primary key with Entity将没有主键的视图与实体一起使用
【发布时间】:2012-07-07 09:17:37
【问题描述】:

我刚开始将应用程序从原始 ADO.NET 和嵌入式 SQL 转换为实体的项目。我遇到了应用程序使用的视图之一的问题。该视图没有主键,也没有唯一标识行的列(或列组合)。这是创建视图的选择:

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;

下面是一些示例数据,显示有一些行完全重复(第 3 行和第 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 是 aip.slbtransinsured 表的 pk,rsn 是 aip.slbtransinsurer 和 aip.slblinecodes 的 pk。

是否可以在没有唯一标识符的情况下向实体模型添加视图?或者有没有一种简单的方法可以向视图添加唯一的行标识符?视图只被读取,从不被写入。

【问题讨论】:

    标签: sql oracle entity


    【解决方案1】:

    如果您在 ASP.NET 中使用带有 MVC 的实体框架

    如前所述,使用具有自动增量或 ROW_NUMBER 的列创建视图。 假设您有该列,它的名称是 rowNumber

    比转到 MVC 应用程序的 Models 目录中的上下文文件 (yourDatabaseNameContext) 文件,找到视图的定义,而不是

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

    改成:

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

    【讨论】:

      【解决方案2】:

      在使用视图时考虑使用 AsNoTracking()。这将禁用 EF 跟踪的任何关键字段。然后可以手动将任何非空字段定义为 EF 键(即使它重复)。

      建议不要创建额外的行计数器字段,因为大多数行计数器最终要求引擎扫描视图的整个域以生成正确的计数器值,即使使用谓词进行查询也是如此(where 子句)。

      请参阅link

      【讨论】:

        【解决方案3】:

        最近在工作中,我遇到了同样的问题。根据我的研究,我找不到任何关于如何在没有 PK 的情况下将视图附加到 EF6 CodeFirst 的答案。大多数似乎都涉及迁移并且相当混乱。我相信 DB first 对工作 SQL VIEWS 有更好的支持。

        我确实尝试引入 window function (RowNumber),其想法是使用行标识符作为 PK 来保持 EF6 的快乐。但这使我的查询总体上更加昂贵,所以我不得不放弃这个想法。

        最后,我必须仔细分析我的数据集,看看我是否可以引入一个复合键——它涵盖了我的业务应用程序需要确保能够正常工作的所有场景。记住也要使用IsNull(ColumnName,0),以确保您可以满足 CodeFirst fluent 方法中的.IsRequired()

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

        我希望这对某人有所帮助 - 我的答案是分析视图非规范化的数据集并寻找复合键。

        Marc Cals 提出的另一个很酷的想法,你可以试试。

        【讨论】:

          【解决方案4】:

          扩展 Michael Buen 的回答: 我发现使用 ISNULL() 将行号添加到视图将允许实体框架将视图拉入并自动创建必要的 EntitySet 数据。

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

          【讨论】:

          • 这对我不起作用,我已经放了一个 row_number 但我仍然没有在我的 EntitySet 中检索视图
          • 使用 row_number() 通常会妨碍谓词的有效使用,并且可能需要引擎Alternative answer 扫描整个视图。
          • 这个答案,扩展 - 使用 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) 代替:stackoverflow.com/a/44106422/1004187 这个帖子 + 链接的帖子解决了我的 EF6 视图与复制主键
          【解决方案5】:

          是否可以在没有 唯一标识符?

          如果没有主键,则没有。这将导致这种error

          在模型生成过程中检测到一个或多个验证错误:

          System.Data.Edm.EdmEntityType: : EntityType 'SalesOnEachCountry' 有 没有定义键。定义此 EntityType 的键。 System.Data.Edm.EdmEntitySet:EntityType:EntitySet SalesOnEachCountryList 基于类型 SalesOnEachCountry 没有 已定义键。

          如果没有唯一标识符,是的,尽管它有一个不受欢迎的输出。具有相同标识符的记录将引用相同的对象,这称为Identity Map Pattern

          一个例子,即使你的视图产生这两行:

          Country     Year TotalSales
          Philippines 2010 20.000000
          Philippines 2011 40.000000
          

          如果您只在 Country 字段上映射主键,例如

          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; }
          }
          

          ,即使您的视图在您的 Oracle 查询编辑器上生成上述两行,实体框架也会生成此错误输出:

          Country     Year TotalSales
          Philippines 2010 20.000000
          Philippines 2010 20.000000
          

          Entity Framework 将认为第二行与第一行是同一对象。

          为保证唯一性,您必须确定使每一行唯一的列。在上面的示例中,必须包含年份,因此主键是唯一的。即

          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; }
          }
          

          使您的主键类似于上面的属性,Entity Framework 可以正确地将您的每个视图的行映射到它们自己的对象。因此,Entity Framework 现在可以显示与您的视图完全相同的行。

          Country     Year TotalSales
          Philippines 2010 20.000000
          Philippines 2011 40.000000
          

          这里有完整的细节:http://www.ienablemuch.com/2011/06/mapping-class-to-database-view-with.html


          然后,对于没有任何列来使行唯一的视图,保证 Entity Framework 可以将视图的每个行映射到它们自己的对象的最简单方法是为视图的 创建一个单独的列主键,一个不错的选择是在每一行上创建一个行号列。例如

          create view RowNumberedView as
          
          select 
              row_number() over(order by <columns of your view sorting>) as RN
              , *
          from your_existing_view
          

          然后在class RowNumberedView 的 RN 属性上分配 [Key] 属性

          【讨论】:

          • 谢谢你 - 你真的帮了我。我正在使用没有可见唯一 ID 列的 Oracle 表,并且由于 LINQ 返回同一行而开始绝望。我听从了您关于在 Entity 类中创建自己的密钥的建议,并且效果很好。
          • 我认为向投影添加 guid 更有效。在具有二进制数据字段的视图中,我发现 over(order by ) 会减慢查询速度。这种方法有什么问题吗?例如:SELECT NEWID() AS PrimaryKey, FROM View
          • 但是,如果您使用数据库优先的实体框架,有一种方法可以将视图添加为“只读实体”而没有主键。当您添加一个没有明显键的视图时,您甚至会得到a neat error,该键表示 EF 强制您的实体成为无键只读实体。肯定有办法自己复制吗?
          • 使用 row_number() 通常会妨碍谓词的有效使用,并且可能需要引擎Alternative answer 扫描整个视图。
          • 为你的 ROW_NUMBER() 列设置别名 ROW_ID,查看更多medium.com/@243/…
          【解决方案6】:

          是否可以在没有唯一标识符的情况下向实体模型添加视图?

          可能有一个视图,其中没有创建主键的单个列或一组列;因此,你最终会得到虚假的关系。数据仓库表有时遵循这种形式。简而言之,出于性能原因或报告原因,有时不遵循规范化。

          现在进入第二点:

          或者有没有一种简单的方法可以为视图添加唯一的行标识符?

          我建议你做的是从 slbtransinsured 中选择所有列,看看你是否能找到唯一标识每条记录的一列。在我看来,数据在 slblinecodes 中应该有一个您需要选择的代码类型,有点像查找。

          为了好玩,尝试运行它并告诉我你得到了什么:

          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 filingmonth,
                   licnum,
                   filingyear,
                   DECODE (GROUPING (insurername), '1', '- All Insured -', insurername),
                   insurername,
                   policylinecode,
                   linedescription,
                   TRUNC (
                       CASE
                           WHEN (b.rsn IS NOT NULL OR a.zeroreport = 1)
                                AND b.datereceived IS NULL
                           THEN
                               a.datereceived
                           ELSE
                               b.datereceived
                       END),
                   a.insuredid;
          

          【讨论】:

          • 谢谢,它不喜欢 GROUP BY 子句中的 GROUPING 函数。 slbtransinsurured 中唯一唯一的列是它的主键,被保险人id,它在视图中是重复的。 slbtransinsurured 和 slblinecodes 之间没有关系,只有 slbtransinsurer 和 slblinecodes 之间。
          猜你喜欢
          • 2012-02-26
          • 2013-12-10
          • 2023-02-09
          • 2021-08-06
          • 2014-02-19
          • 2018-10-02
          • 1970-01-01
          • 2015-09-08
          • 2010-11-04
          相关资源
          最近更新 更多