web-dev-qa-db-fra.com

Écriture de membres calculés avec Entity Framework Core

Nous utilisons Entity Framework Core 3 avec SqlServer Database. Le programme d'entreprise doit créer de nombreuses colonnes qui ne sont pas dans la base de données, en raison du stockage, du coût élevé des requêtes, etc. Actuellement, l'équipe copie la couche de base de données entière et crée une autre couche en ajoutant des membres calculés dans de nouvelles entités. Prenant actuellement la couche de base de données et appliquant AutoMapper à la nouvelle couche. Pour une raison quelconque, cela ne semble pas être une méthode optimale.

Dans cet exemple, nous avons besoin de membres calculés appelés

FullName => FirstName + LastName

AccountValue => Quantity * StockPrice

Entity Framework 3 n'autorise plus l'évaluation côté client, https://devblogs.Microsoft.com/dotnet/announcing-ef-core-3-0-and-ef-6-3-general-availability/ si curieux de savoir quelle est la manière standardisée pour les membres calculés dans Entity Framework Core 3?

En lisant cet article, curieux de savoir ce qui est à jour et qui peut être utilisé? Ou Entity Framework Core 3 offre-t-il une nouvelle syntaxe?

https://daveaglick.com/posts/computed-properties-and-entity-framework

1) Nous pourrions matérialiser les entités. Cela semble correct, mais cela oblige le développeur à se souvenir d'utiliser ToList (), a eu des problèmes que les développeurs oublient, provoquant de longues requêtes d'analyse de la base de données ou une évaluation côté client provoquant une erreur.

var result = ctx.Customers
  .ToList()
  .Select(c => new
  {
    FullName = c.FullName,
    AccountValue = c.AccountValue
  });

2) Créez une extension interrogeable. Cela extrait uniquement les colonnes calculées ou oblige les développeurs à créer des membres calculés dans une seule classe (rompt l'idée de responsabilité unique de SRP). Sauf s'il existe une modification alternative qui résout ce problème. Cela entraîne également des problèmes de chaîne de composition et des problèmes de performances possibles comme l'option 1.

public static IQueryable<CustomerData> SelectCustomerData(this IQueryable<Customer> customers) {   return customers.Select(c => new CustomerData   {
    FullName = c.FirstName + " " + c.LastName,
    AccountValue = c.Holdings.Sum(h => h.Quantity * h.Stock.Price)   }); }

3) Projection d'expression, n'autorise pas l'affectation dans Select sans projet d'expression Linq. La société n'autorise pas cet outil tiers, à moins qu'il ne soit créé par Microsoft.

public readonly Expression<Func<Customer, decimal>> AccountValueExpression = c => c.Holdings.Sum(h => h.Quantity * h.Stock.Price);

Ou Entity Framework Core 3 offre-t-il une syntaxe plus récente?

La solution doit être l'endroit où (a) la personne peut extraire tout ou partie des membres existants de DBEntity d'origine, (b) et tout ou partie des nouveaux membres,

Exemple, besoin de FirstName (existant) et AccountValue (nouveau membre)

Ou FullName, FirstName, LastName, StockPrice,

Ou tout, FirstName, LastName, FullName, Quantity, StockPrice, AccountValue, etc., etc.

Tout mélange ou correspondance d'entités.

En fait, la migration de 2.2 vers Core 3, cependant 2.2 a ClientSide Evaluation désactivée. Impossible d'utiliser des outils tiers, tels que Linq.Translations ou DelegateCompiler, sauf s'ils sont créés à partir d'un fournisseur Microsoft.

Préférez ne pas utiliser les colonnes SqlServer Computed, car nous comptons sur l'équipe DBA. De plus, il existe des calculs plus complexes.

5
Artportraitdesign1

Tout d'abord, permettez-moi de citer le avis de rupture EF ici:

À partir de la version 3.0, EF Core autorise uniquement les expressions de la projection de niveau supérieur (le dernier appel Select () dans la requête) à être évaluées sur le client. Lorsque les expressions d'une autre partie de la requête ne peuvent pas être converties en SQL ou en paramètre, une exception est levée.

Je viens de tester avec succès la requête suivante:

var list = context.Customers.Include(c => c.Stocks).Select(c => new
{
    FullName = c.FirstName + " " + c.LastName,
    TotalInvestment = c.Stocks.Sum(s => s.Price*s.Quantity)
});
list.ToList();
/*
      SELECT ([c].[FirstName] + N' ') + [c].[LastName] AS [FullName], (
          SELECT SUM([s].[Price] * [s].[Quantity])
          FROM [Stocks] AS [s]
          WHERE [c].[Id] = [s].[CustomerId]) AS [TotalInvestment]
      FROM [Customers] AS [c]
*/

Mais explorons un peu plus le sujet et disons que vous souhaitez interroger votre table sur un champ calculé sans amener toute l'évaluation du côté client.

var list = context.Customers.Include(c => c.Stocks)
                    .Where(c => string.Concat(string.Concat(c.FirstName, " "), c.LastName) == "John Doe") // notice how we have to do string.Concat twice. observe what hppens if you use the next line instead
                    //.Where(c => string.Concat(c.FirstName, " ", c.LastName) == "John Doe"); // this query will fail to evaluate on SQL and will throw an error, because EF's default concat translator implementation only caters for two parameters
                ;
list.ToList();
/* the following SQL has been generated
      SELECT [c].[Id], [c].[FirstName], [c].[LastName], [s].[Id], [s].[CustomerId], [s].[Price], [s].[Quantity]
      FROM [Customers] AS [c]
      LEFT JOIN [Stocks] AS [s] ON [c].[Id] = [s].[CustomerId]
      WHERE (([c].[FirstName] + N' ') + [c].[LastName]) = N'John Doe'
      ORDER BY [c].[Id], [s].[Id]
*/

Apparemment, EF3 est livré avec quelques fonctions prédéfinies qui vous permettent de le faire: voir les implémentations IMethodCallTranslator (telles que StringMethodTranslator par exemple) pour en obtenir un peu plus un aperçu de la façon dont il fait cela (et d'autres traducteurs disponibles immédiatement).

D'accord, que faire si votre traducteur n'est pas implémenté, je vous entends demander. C'est là que les choses deviennent un peu plus excitantes. J'ai réussi à faire cela pour EF 2.2 comme indiqué dans this SO answer (que je vous invite à vérifier). Le code ne se traduit malheureusement pas directement en EF3 1: 1 mais je suis assez convaincu que la même approche fonctionnera.

[~ # ~] upd [~ # ~] : voir mon dépôt github pour PoC des fonctions DB personnalisées fonctionnant avec EF Noyau 3.1

1
timur

Je n'ai pas essayé cela, mais c'est juste une pensée - Que diriez-vous de créer une méthode Extension personnalisée de DbFunctions?

J'ai utilisé EF.Functions.Like méthode qui est une implémentation de l'opération SQL LIKE. Sur les bases de données relationnelles, cela est généralement traduit directement en SQL.

Vérifiez ces liens -

0
Indar-AIS

En supposant qu'il soit pris en charge par votre backend, vous pouvez mettre en miroir vos propriétés calculées avec des colonnes de base de données calculées.

    public string FullName => FirstName + LastName;

    entity.Property(e => e.FullName).HasComputedColumnSql("FirstName + LastName");

Ensuite, vous pouvez filtrer, classer par, projeter, etc. ces propriétés.

0
Jeremy Lakeman