EF Code First中的计算列


79

我需要数据库中的一列由数据库计算为(行总和)-(行总和b)。我正在使用代码优先模型来创建数据库。

这是我的意思:

public class Income {
      [Key]
      public int UserID { get; set; }
      public double inSum { get; set; }
}

public class Outcome {
      [Key]
      public int UserID { get; set; }
      public double outSum { get; set; }
}

public class FirstTable {
      [Key]
      public int UserID { get; set; }
      public double Sum { get; set; } 
      // This needs to be calculated by DB as 
      // ( Select sum(inSum) FROM Income WHERE UserID = this.UserID) 
      // - (Select sum(outSum) FROM Outcome WHERE UserID = this.UserID)
}

如何在EF CodeFirst中实现这一目标?

Answers:


137

您可以在数据库表中创建计算列。在EF模型中,您只需使用属性注释相应的DatabaseGenerated属性:

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public double Summ { get; private set; } 

或使用流利的贴图:

modelBuilder.Entity<Income>().Property(t => t.Summ)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)

正如Matija Grcic所建议的那样,并且在注释中,使该属性为好主意private set,因为您可能永远不想在应用程序代码中对其进行设置。实体框架对于私有设置者没有问题。

注意:对于EF .NET Core,您应该使用ValueGeneratedOnAddOrUpdate,因为HasDatabaseGeneratedOption不存在,例如:

modelBuilder.Entity<Income>().Property(t => t.Summ)
    .ValueGeneratedOnAddOrUpdate()

26
我知道它,但是如何通过EF添加一个公式以将其计算到数据库中,以便由控制台commad update-database创建?
CodeDemen 2013年

9
请在您的问题中明确说明。这意味着您希望迁移创建一个计算列。有一个例子在这里
Gert Arnold

2
二传手必须是私人的吗?
Cherven'Apr

1
@Cherven是的,这样做可能更好。
Gert Arnold

6
该答案应进行更新以添加针对EF Core的信息,ValueGeneratedOnAddOrUpdate()因为HasDatabaseGeneratedOption不存在,模型构建器应使用方法。否则,很好的答案。
最大

33
public string ChargePointText { get; set; }

public class FirstTable 
{
    [Key]
    public int UserID { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]      
    public string Summ 
    {
        get { return /* do your sum here */ }
        private set { /* needed for EF */ }
    }
}

参考文献:


+1用于添加私有集。添加新对象时,不应设置计算列。
塔希尔

1
碰巧再次查看此问题,现在我看到该部分/* do your sum here */不适用。如果属性是在类内部计算的,则应将其注释为[NotMapped]。但是该值来自数据库,因此它应该只是一个简单的get属性。
Gert Arnold

1
@GertArnold参见此处- “由于FullName属性是由数据库计算的,因此一旦我们对FirstName或LastName属性进行更改,它将在对象端不同步。幸运的是,我们可以兼得两者在此还通过将计算重新添加到FullName属性的getter中”
AlexFoxGill 2015年

@AlexFoxGill那有什么意义呢?如果您每次都要动态地重新计算计算值,以防它们“不同步”,为什么还要烦恼呢?
Rudey 2015年

@RuudLenders,以便您可以在LINQ查询中使用计算列。
AlexFoxGill 2015年

12

截至2019年,EF核心使您可以使用fluent API以简洁的方式计算列:

假设这DisplayName是您要定义的计算列,则必须像往常一样定义属性,可能使用私有属性访问器来防止对其进行分配

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    // this will be computed
    public string DisplayName { get; private set; }
}

然后,在模型构建器中,使用列定义对其进行处理:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .Property(p => p.DisplayName)
        // here is the computed query definition
        .HasComputedColumnSql("[LastName] + ', ' + [FirstName]");
}

有关更多信息,请查看MSDN


2

在EF6中,您可以仅将映射设置配置为忽略计算的属性,如下所示:

在模型的get属性上定义计算:

public class Person
{
    // ...
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName => $"{FirstName} {LastName}";
}

然后将其设置为对模型配置忽略

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //...
    modelBuilder.Entity<Person>().Ignore(x => x.FullName)
}

1

一种方法是使用LINQ:

var userID = 1; // your ID
var income = dataContext.Income.First(i => i.UserID == userID);
var outcome = dataContext.Outcome.First(o => o.UserID == userID);
var summ = income.inSumm - outcome.outSumm;

您可以在POCO对象中进行此操作public class FirstTable,但我不建议这样做,因为我认为这不是一个好的设计。

另一种方法是使用SQL视图。您可以使用Entity Framework阅读类似于表格的视图。在视图代码中,您可以执行计算或执行任何所需的操作。只需创建一个像

-- not tested
SELECT FirstTable.UserID, Income.inCome - Outcome.outCome
  FROM FirstTable INNER JOIN Income
           ON FirstTable.UserID = Income.UserID
       INNER JOIN Outcome
           ON FirstTable.UserID = Outcome.UserID

0

我将只使用视图模型来解决这个问题。例如,与其将FirstTable类作为数据库实体,不如让它具有一个名为FirstTable的视图模型类,然后拥有一个用于返回包含计算和的类的函数会更好吗?例如,您的课程将是:

public class FirstTable {
  public int UserID { get; set; }
  public double Sum { get; set; }
 }

然后,您将调用一个函数,该函数返回计算出的总和:

public FirsTable GetNetSumByUserID(int UserId)
{
  double income = dbcontext.Income.Where(g => g.UserID == UserId).Select(f => f.inSum);
  double expenses = dbcontext.Outcome.Where(g => g.UserID == UserId).Select(f => f.outSum);
  double sum = (income - expense);
  FirstTable _FirsTable = new FirstTable{ UserID = UserId, Sum = sum};
  return _FirstTable;
}

与SQL视图和@Linus基本上相同,我认为将计算值保留在数据库中不是一个好主意。只是一些想法。


+1代表I don't think it would be a good idea keeping the computed value in the database-特别是如果您要使用Azure SQL,它将在重负载时开始死锁。
Piotr Kula 2015年

1
@ppumkin在大多数情况下,聚合计算将在数据库中更好地执行。想想“最近的评论ID”之类的东西。您不需要拉回每个CommentID仅使用其中一个。不仅浪费数据和内存,而且还增加了数据库本身的负载,并且实际上将共享锁放置在更多行上的时间更长。而且,您必须一直更新很多行,这可能是需要检查的设计。
JoeBrockhaus

好。我只是想将它们保存在内存中,计算值,进行缓存。不要为每个访客访问数据库。它会引起问题。我已经看到这种情况发生了太多次了。
Piotr Kula 2015年

-2

当我尝试从另一个字符串列“ Name”派生一个带有字符串列“ Slug”的EF Code First模型时,偶然发现了这个问题。我采用的方法稍有不同,但效果很好,因此我将在这里分享。

private string _name;

public string Name
{
    get { return _name; }
    set
    {
        _slug = value.ToUrlSlug(); // the magic happens here
        _name = value; // but don't forget to set your name too!
    }
}

public string Slug { get; private set; }

这种方法的优点是您可以自动生成弹头,而不必暴露弹头设置器。.ToUrlSlug()方法不是本文的重要部分,您可以在其位置使用任何东西来完成所需的工作。干杯!


没有你编辑了所有,实际上链接的比特SlugName?如目前所写,Namesetter甚至不应该编译。
Auspex

不,在您进行编辑之前,这是一个可行的示例。编辑后,没有任何意义。
Auspex
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.