Розрахований стовпець у коді 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.