Wczytanie pierwszego zgrupowanego wiersza z sortowaniem malejącym

0

hej, mam problem ze zbudowaniem zapytania w .net uzywajac Expreession (z LINQ)

mam model

public class SomeModel
{
  public int SomeInt{get;set;}
  public int DictValue {get;set;}
  public DateTime Created{get;set;}
}

uzywam go jako DBSet<SomeModel> Models

w tabeli mam wiele rekordow, gdzie SomeInt = 1, DictValue = 1, a chciałbym pobrać tylko ostatnio utworzony, czyli OrderByDescending(Created).Take(1)

ale... chce zbudować zapytanie z kolekcji, ktore by wygladalo mniej wiecej tak:

...where (SomeInt = queryParameters[0].someInt and DictValue = queryParameters[0].dictValue) or (SomeInt = queryParameters[1].someInt and DictValue = queryParameters[1].dictValue) ... or (SomeInt = queryParameters[10].someInt and DictValue = queryParameters[10].dictValue)

w metodzie otrzymuje te parametry i probuje:

public void SomeFunc(<int someInt, int dictVal>[] model)
{
   var parameter = Expression.Parameter(typeof(SomeModel), "x");
        var predicateExpressions = model.Select(element =>
            Expression.AndAlso(
                Expression.Equal(Expression.Property(parameter, nameof(SomeModel.SomeInt)), Expression.Constant(element.someInt)),
                Expression.AndAlso(
                    Expression.Equal(Expression.Property(parameter, nameof(SomeModel.DictVal))
            )
        );

       var orExpression = predicateExpressions.Aggregate(Expression.OrElse);
}

to jest chyba ok, ale musze jeszcze pogrupować po SomeInt i wziać pierwszy wynik w zależności od Created malejąco
i tutaj mam problem
spróbowałem tego co poniżej, ale to zapytanie leci 15 sekund do bazy ;D

var orderByExpression = Expression.Lambda<Func<SomeModel, DateTime>>(Expression.Property(parameter, "Created"), parameter);
var lambda = Expression.Lambda<Func<SomeModel, bool>>(orExpression, parameter);
var compiledLambda = lambda.Compile();

var lastUpdates = _context.SomeModel
  .Where(compiledLambda)
  .GroupBy(x => x.SomeInt)
  .Select(g => g.OrderByDescending(x => x.Created).FirstOrDefault())
var result = (await _context.Models
        .Where(x => lastUpdates.Contains(x.Id))
        .ToListAsync())

male zastrzezenie - nie chcialbym uzywac jakis dodatkowyhc libek, ani raw sql w kodzie
znajdzie sie jakis dotnetnowy mocarz skory do pomocy ;) ?

0

spróbowałem tego co poniżej, ale to zapytanie leci 15 sekund do bazy ;D

No, a jaki jest wygenerowany SQL? jest sensowny? nie?

public void SomeFunc(<int someInt, int dictVal>[] queryParameters)
{
   var parameter = Expression.Parameter(typeof(SomeModel), "x");
        var predicateExpressions = model.Select(element =>
            Expression.AndAlso(
                Expression.Equal(Expression.Property(parameter, nameof(SomeModel.SomeInt)), Expression.Constant(element.someInt)),
                Expression.AndAlso(
                    Expression.Equal(Expression.Property(parameter, nameof(SomeModel.DictVal))
            )
        );

       var orExpression = predicateExpressions.Aggregate(Expression.OrElse);
}

to jest cały kod? jeżeli tak, to czemu to jest budowane za pomocą Expression? co tu jest dynamiczne? bo chyba ślepy jestem, czemu queryParameters jest nieużyte?

edit.

Może Ci ułatwię, ktoś na Stacku to fajnie rozwiązał robiąc "incremental predicate"

Func<Pantient, bool> predicate = p => false;

if (User.IsInRole("Administrator"))
{
    var oldPredicate = predicate;
    predicate = p => oldPredicate(p) || p.AdministratorID == UserID;
}

if (User.IsInRole("Counselor"))
{
    var oldPredicate = predicate;
    predicate = p => oldPredicate(p) || p.CounselorID == UserID;
}


var query = db.Patients.Where(predicate);
0

to jest inkrementowane :P tzn zamiast foreach, zrobilem po prostu IEnumerable.Select
sory cos tutaj zle nazwy skoczyly
to models to jest queryparameters, juz edytuje kod
co do zapytania SQL to jak bede w pracy to podejrze znow, ale z tego co pamietam to co orderbyquery+groupby rodzilo mega problem

0

@pawel666

co do zapytania SQL to jak bede w pracy to podejrze znow, ale z tego co pamietam to co orderbyquery+groupby rodzilo mega problem

nie wiem jak duże masz te dane oraz jak szybko rosną, ale zawsze możesz rozbić query na: część po stronie bazy, a część po client side np. może ten group by?

0

Na Expression (czyli tłumaczone na sql):

    class Model
    {
        public int Id { get; set; }
        public int Value { get; set; }
    }

    static void Main(string[] args)
    {
        var conditions = new List<Model>() { new Model { Id = 1, Value = 2 } };
        var data = new List<Model>() { new Model { Id = 1, Value = 2 } }.AsQueryable();

        var queryCondition = PredicateBuilder.Create<Model>(x => false);

        foreach (var key in conditions)
        {
            queryCondition = queryCondition.Or(
                c => c.Value == key.Value && c.Id == key.Id
            );
        }

        var result = data.Where(queryCondition).ToList();
    }

Definicję PredicateBuilder znajdziesz np. tu: https://stackoverflow.com/questions/6912733/linq-to-entities-where-in-clause-with-multiple-columns.

Na linq-to-objects (absolutnie nie używaj tego, jeśli sięgasz po dane do bazy):

var result = list.Where(m => conditions.Any(c => c.Id == m.Id && c.Value == m.Value)).ToList();

IEnumerable (AsEnumerable czy ToList) przed wykonaniem warunków nie używaj w niczym, co sięga do bazy danych. Wykonanie czegokolwiek po stronie C# oznacza wykonanie łańcuszka metod linq i na koniec ściągnięcie wyników; jeśli chain jest pusty, to ściągasz zawartość całej tabeli i warunki wykonujesz po stronie C#. Chyba nie muszę pisać, jak skrajnie niewydajne jest to rozwiązanie.

1
pawel666 napisał(a):

spróbowałem tego co poniżej, ale to zapytanie leci 15 sekund do bazy ;D

A czy spróbowałeś również sprawdzić plan wykonania swojego zapytania? Skąd wiesz, że wysyła się 15 sekund, a nie wykonuje przez tyle czasu? Czy masz niezbędny indeks na odpowiednich polach w odpowiedniej kolejności z odpowiednimi include'ami?

1 użytkowników online, w tym zalogowanych: 0, gości: 1