Расчетный столбец в EF Code First

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, чтобы она была создана консольной командой 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 за добавление приватного набора. Вычисляемый столбец не следует устанавливать при добавлении новых объектов.
Taher
1
Приходилось просматривать этот вопрос снова, и теперь я вижу, что часть /* do your sum here */не применяется. Если свойство вычисляется внутри класса, оно должно быть аннотировано как [NotMapped]. Но значение поступает из базы данных, поэтому это должно быть просто getсвойство.
Герт Арнольд
1
@GertArnold см. Здесь - «Поскольку свойство FullName вычисляется базой данных, оно выйдет из синхронизации на стороне объекта, как только мы внесем изменения в свойство FirstName или LastName. К счастью, у нас есть лучшее из обоих миров. здесь, также добавив вычисление обратно к получателю для свойства FullName »
AlexFoxGill
@AlexFoxGill Тогда в чем смысл? Зачем беспокоиться о сохранении вычисленных значений, если вы собираетесь их динамически пересчитывать каждый раз, если они «не синхронизируются»?
Руди
@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, я не думаю, что было бы хорошей идеей хранить вычисленное значение в базе данных. Просто мысли.

Craigvl
источник
+1 за I don't think it would be a good idea keeping the computed value in the database- особенно если вы собираетесь использовать Azure SQL, который начнет зайти в тупик при большой нагрузке.
Петр Кула
1
@ppumkin В большинстве случаев агрегированный расчет лучше выполнять в БД. Подумайте о чем-то вроде «идентификатор последнего комментария». Вы не хотите, чтобы вам приходилось отбирать каждый идентификатор комментария, чтобы взять только один из них. Вы не только тратите впустую данные и память, но также увеличиваете нагрузку на саму БД и фактически на более длительное время устанавливаете общие блокировки на большее количество строк. Более того, вам придется постоянно обновлять множество строк, что, вероятно, требует пересмотра дизайна.
JoeBrockhaus
ХОРОШО. Я просто имел в виду хранить их в памяти, вычислять значения, кешировать. Не заходите в базу данных для каждого посетителя. Это вызовет проблемы. Я видел это слишком много раз.
Петр Кула
-2

Я наткнулся на этот вопрос, когда пытался получить модель EF Code First со строковым столбцом «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