Cột được tính trong EF Code First


79

Tôi cần có một cột trong cơ sở dữ liệu của mình được cơ sở dữ liệu tính toán bằng (tổng các hàng) - (tổng các hàngb). Tôi đang sử dụng mô hình đầu tiên mã để tạo cơ sở dữ liệu của mình.

Đây là những gì tôi muốn nói:

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

Làm cách nào để đạt được điều này trong EF CodeFirst?

Câu trả lời:


137

Bạn có thể tạo các cột được tính toán trong các bảng cơ sở dữ liệu của mình. Trong mô hình EF, bạn chỉ cần chú thích các thuộc tính tương ứng với DatabaseGeneratedthuộc tính:

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

Hoặc với lập bản đồ thông thạo:

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

Theo đề xuất của Matija Grcic và trong một nhận xét, bạn nên tạo thuộc tính private setvì có thể bạn sẽ không bao giờ muốn đặt nó trong mã ứng dụng. Entity Framework không có vấn đề gì với bộ thiết lập riêng.

Lưu ý: Đối với EF .NET Core, bạn nên sử dụng ValueGeneratedOnAddOrUpdate vì HasDatabaseGeneratedOption không tồn tại, ví dụ:

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

26
Tôi biết về nó nhưng làm cách nào để thêm một công thức để tính nó vào cơ sở dữ liệu của tôi thông qua EF, để nó sẽ được tạo bởi console-commad update-database?
CodeDemen

9
Vui lòng nêu rõ điều này trong câu hỏi của bạn. Nó có nghĩa là bạn muốn di chuyển để tạo một cột được tính toán. Có một ví dụ ở đây .
Gert Arnold

2
setter có phải là riêng tư không?
Cherven

1
@Cherven Có, có lẽ tốt hơn nên làm điều đó.
Gert Arnold

6
Câu trả lời này nên được cập nhật để thêm rằng đối với EF Core, trình tạo mô hình nên sử dụng phương pháp ValueGeneratedOnAddOrUpdate()HasDatabaseGeneratedOptionkhông tồn tại. Nếu không, câu trả lời tuyệt vời.
Tối đa

34
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 */ }
    }
}

Người giới thiệu:


+1 để thêm nhóm riêng tư. Không nên đặt cột được tính khi thêm đối tượng mới.
Taher

1
Đã xảy ra để xem lại câu hỏi này và bây giờ tôi thấy rằng phần /* do your sum here */không áp dụng. Nếu thuộc tính được tính toán bên trong lớp, thì nó phải được chú thích là [NotMapped]. Nhưng giá trị đến từ cơ sở dữ liệu, vì vậy nó chỉ nên là một thuộc tính đơn giản get.
Gert Arnold,

1
@GertArnold xem tại đây - "Vì thuộc tính FullName được cơ sở dữ liệu tính toán nên nó sẽ không đồng bộ ở phía đối tượng ngay sau khi chúng tôi thực hiện thay đổi đối với thuộc tính FirstName hoặc LastName. May mắn thay, chúng tôi có thể có cả hai thế giới tốt nhất đây bởi cũng thêm phía sau tính đến getter trên thuộc tính FullName"
AlexFoxGill

@AlexFoxGill Vậy thì vấn đề là gì? Tại sao lại bận tâm đến việc lưu trữ các giá trị được tính toán nếu động của bạn luôn tính toán lại chúng trong trường hợp chúng "không đồng bộ"?
Rudey

@RuudLenders để bạn có thể sử dụng cột được tính toán trong các truy vấn LINQ.
AlexFoxGill

13

Kể từ năm 2019, lõi EF cho phép bạn tính toán các cột một cách rõ ràng với API thông thạo:

Giả sử đó DisplayNamelà cột được tính mà bạn muốn xác định, bạn phải xác định thuộc tính như bình thường, có thể với một trình truy cập thuộc tính riêng để ngăn việc gán nó

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

Sau đó, trong trình tạo mô hình, giải quyết nó bằng định nghĩa cột:

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

Để biết thêm thông tin, hãy xem MSDN .


3

Trong EF6, bạn chỉ có thể định cấu hình cài đặt ánh xạ để bỏ qua thuộc tính được tính toán, như sau:

Xác định phép tính trên thuộc tính get của mô hình của bạn:

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

Sau đó, đặt nó thành bỏ qua trên cấu hình mô hình

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

1

Một cách đang thực hiện với 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;

Bạn có thể làm điều đó trong đối tượng POCO của mình public class FirstTable, nhưng tôi sẽ không đề xuất, vì tôi nghĩ rằng nó không phải là thiết kế tốt.

Một cách khác sẽ là sử dụng dạng xem SQL. Bạn có thể đọc dạng xem như bảng với Entity Framework. Và trong mã chế độ xem, bạn có thể thực hiện các phép tính hoặc bất cứ điều gì bạn muốn. Chỉ cần tạo một chế độ xem như

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

Tôi sẽ làm điều này bằng cách chỉ sử dụng một mô hình xem. Ví dụ: thay vì có lớp FirstTable dưới dạng thực thể db, bạn sẽ không tốt hơn nếu chỉ có một lớp mô hình chế độ xem được gọi là FirstTable và sau đó có một hàm được sử dụng để trả về lớp này sẽ bao gồm tổng được tính toán? Ví dụ, lớp của bạn sẽ chỉ là:

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

Và sau đó bạn sẽ có một hàm mà bạn gọi để trả về tổng được tính toán:

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

Về cơ bản giống như dạng xem SQL và như @Linus đã đề cập, tôi không nghĩ rằng sẽ là một ý kiến ​​hay khi giữ giá trị được tính toán trong cơ sở dữ liệu. Chỉ là một số suy nghĩ.


+1 cho I don't think it would be a good idea keeping the computed value in the database- đặc biệt nếu bạn sử dụng Azure SQL sẽ bắt đầu bế tắc khi tải nặng.
Piotr Kula,

1
@ppumkin Trong hầu hết các trường hợp, tính toán tổng hợp sẽ được thực hiện tốt hơn trong DB. Hãy nghĩ về một cái gì đó như 'id nhận xét gần đây nhất'. Bạn không muốn phải kéo lại mọi CommentID để chỉ lấy một trong số chúng. Bạn không chỉ lãng phí dữ liệu và bộ nhớ, mà còn tăng tải cho chính DB và bạn thực sự đặt các khóa dùng chung trên nhiều hàng hơn trong thời gian dài hơn. Hơn nữa, bạn phải cập nhật rất nhiều hàng mọi lúc, đây có lẽ là một thiết kế cần được xem xét lại.
JoeBrockhaus

ĐỒNG Ý. Ý tôi chỉ là giữ chúng trong bộ nhớ, các giá trị được tính toán, được lưu vào bộ nhớ đệm. Không truy cập cơ sở dữ liệu cho mọi khách truy cập. Nó sẽ gây ra vấn đề. Tôi đã thấy điều này xảy ra quá nhiều lần.
Piotr Kula,

-2

Tôi tình cờ gặp câu hỏi này khi cố gắng có một mô hình EF Code First với cột chuỗi "Slug", được bắt nguồn từ một cột chuỗi khác "Tên". Cách tiếp cận tôi đã thực hiện hơi khác một chút nhưng hoạt động tốt nên tôi sẽ chia sẻ nó ở đây.

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

Điều thú vị về cách tiếp cận này là bạn có được hệ thống tạo sên tự động, trong khi không bao giờ để lộ trình cài đặt sên. Phương thức .ToUrlSlug () không phải là phần quan trọng của bài đăng này, bạn có thể sử dụng bất kỳ thứ gì thay thế cho nó để thực hiện công việc bạn cần làm. Chúc mừng!


Bạn đã không chỉnh sửa tất cả các bit thực sự liên kết Slugvới Name? Như đã viết hiện tại, người Nameđịnh lập thậm chí không nên biên dịch.
Auspex 14/03/18

Không, trước khi bạn chỉnh sửa, đó là một ví dụ khả thi. Sau khi bạn chỉnh sửa, nó không có ý nghĩa gì.
Auspex
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.