Розрахований стовпець у коді EF Спочатку


80

Мені потрібно мати один стовпець у моїй базі даних, обчислений за базою даних як (сума рядків) - (сума рядків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?

Відповіді:


137

Ви можете створювати обчислювані стовпці у таблицях бази даних. У моделі EF ви просто анотуєте відповідні властивості DatabaseGeneratedатрибутом:

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

Або з вільним картографуванням:

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

Як запропонував Матія Грчіч та у коментарі, це гарна ідея зробити власність private set, тому що ви, мабуть, ніколи не захочете встановлювати його в коді програми. Entity Framework не має проблем з приватними сетерами.

Примітка: Для EF .NET Core слід використовувати ValueGeneratedOnAddOrUpdate, оскільки HasDatabaseGeneratedOption не існує, наприклад:

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

26
Я знаю про це, але як я можу додати формулу для її обчислення до своєї бази даних через EF, щоб вона була створена за допомогою консолі commad update-database?
CodeDemen

10
Будь ласка, чітко сформулюйте це у своєму питанні. Це означає, що ви хочете, щоб міграції створили обчислюваний стовпець. Існує приклад тут .
Герт Арнольд

2
сетер повинен бути приватним?
Червен

1
@Cherven Так, мабуть, краще це зробити.
Герт Арнольд

6
Цю відповідь слід оновити, щоб додати, що для EF Core конструктор моделей повинен використовувати метод, ValueGeneratedOnAddOrUpdate()оскільки HasDatabaseGeneratedOptionне існує. Інакше чудова відповідь.
Макс

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

Список літератури:


+1 за додавання приватного набору. Обчислюваний стовпець не слід встановлювати під час додавання нових об’єктів.
Тахер

1
Трапилось переглянути це запитання ще раз, і тепер я бачу, що частина /* do your sum here */не застосовується. Якщо властивість обчислюється всередині класу, його слід позначити як [NotMapped]. Але значення походить від бази даних, тому це має бути простою getвластивістю.
Герт Арнольд

1
@GertArnold дивіться тут - "Оскільки властивість
FullName

@AlexFoxGill Тоді який сенс? Навіщо турбуватися про зберігання обчислених значень, якщо ви динамічно збираєтеся їх перераховувати щоразу на випадок, якщо вони не синхронізуються?
Rudey

@RuudLenders, щоб ви могли використовувати обчислюваний стовпець у запитах LINQ.
AlexFoxGill

15

Починаючи з 2019 року, ядро ​​EF дозволяє чітко обчислювати стовпці за допомогою вільного 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 .


3

У 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 як сутність db, чи не буде вам краще просто мати клас моделі подання, який називається 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

1
@ppumkin У більшості випадків сукупний розрахунок буде краще виконуватися в БД. Подумайте про щось на зразок "останнього ідентифікатора коментаря". Вам не потрібно знімати кожен CommentID, щоб взяти лише один із них. Ви не тільки марно витрачаєте дані та пам’ять, але й збільшуєте навантаження на саму БД, і фактично ставите спільні блокування на більше рядків довше. Більше того, вам доведеться постійно оновлювати багато рядків, що, мабуть, вимагає перевірки дизайну.
JoeBrockhaus

ГАРАЗД. Я просто мав на увазі зберегти їх у пам’яті, обчислити значення, кешувати. Не переходьте до бази даних для кожного відвідувача. Це буде викликати проблеми. Я бачив, як це траплялося занадто багато разів.
Piotr Kula

-2

Я наткнувся на це запитання, намагаючись отримати першу модель коду EF з рядковим стовпцем "Slug", яка буде виведена з іншого стовпця рядка "Name". Підхід, який я застосував, був дещо іншим, але виробленим добре, тому я поділюсь ним тут

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 () не є важливою частиною цього допису, замість нього ви можете використовувати що завгодно, щоб виконати потрібну вам роботу. Ура!


Хіба ти не правий з усіх бітів , які на самому справі посилаються Slugна Name? Як зараз написано, Nameустановник не повинен навіть компілювати.
Auspex

Ні, перед вашим редагуванням це був дієвий приклад. Після редагування це не має сенсу.
Auspex
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.