0

I have these two entities in EF Core:

public class Chat
{
    public int Id { get; set; }
    public string SessionId { set; get; } = string.Empty; // Could be sessionId or anything that indicate the chatId from the source
    public string Source { set; get; } = string.Empty; // Any Chat Source

    public ChatAnalysisResult? AnalysisResult { get; set; }
    public ICollection<Message> Messages { set; get; } = new List<Message>();
}

public enum JobType
{
    [Description("ChatFetch")]
    ChatFetch,
    [Description("ChatAnalyze")]
    ChatAnalyze,
    [Description("RecordFetch")]
    RecordFetch,
    [Description("RecordTranscript")]
    RecordTranscript
}

public enum JobStatus
{
    [Description("Pending")]
    Pending,
    [Description("Started")]
    Started,
    [Description("Finished")]
    Finished,
    [Description("Stopped")]
    Stopped,
    [Description("Failed")]
    Failed
}

public class Job
{
    public int Id { get; set; }
    public JobStatus status { get; set; } = JobStatus.Pending;
    public List<string> Ids { get; set; } = new();
    public List<string> Pages { get; set; } = new();
    public JobType Type { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime? StartedAt { get; set; }
    public DateTime? EndedAt { get; set; }
    public List<JobOption> JobOptions { get; set; } = new();
}

This is my original code:

var queryable = context.Chats
    .Include(chat => chat.Messages)
    .Include(chat => chat.AnalysisResult)
    .AsQueryable();

if (query.AnalyzedOnly is not null && (bool)query.AnalyzedOnly)
{
    queryable = queryable.Where(c => c.AnalysisResult != null);
}

var response = await queryable
    .ApplySorting(query)
    .ApplyPagination(query)
    .ProjectTo<ChatResponse>(mapper.ConfigurationProvider)
    .ToArrayAsync(ct);
        
await SendOkAsync(response, ct);

Now what I want to do is to check if there is a Job that contains a chat id in the Job.Ids list, the job status should be Pending or Started, and the job type should be ChatAnalyze.

I tried doing this:

var queryable = context.Chats
    .Include(chat => chat.Messages)
    .Include(chat => chat.AnalysisResult)
    .AsQueryable();

if (query.AnalyzedOnly is not null && (bool)query.AnalyzedOnly)
{
    queryable = queryable.Where(c => c.AnalysisResult != null);
}

var result = await queryable
    .ApplySorting(query)
    .ApplyPagination(query)
    .Select(chat => new
            {
                Chat = chat,
                Analyzing = context.Jobs
                    .Where(j => (j.status == JobStatus.Pending || j.status == JobStatus.Started) &&
                                j.Type == JobType.ChatAnalyze &&
                                j.Ids.Contains(chat.Id.ToString()))
                    .Any()
    })
    .ToArrayAsync(ct);

var response = new List<ChatResponse>();

foreach (var element in result)
{
    var res = mapper.Map<ChatResponse>(element.Chat);
    res.Analyzing = element.Analyzing;
    response.Add(res);
}

await SendOkAsync(response, ct);

The produced query seems right:

SELECT 
    "t"."Id", ... , 
    EXISTS (SELECT 1
            FROM "Jobs" AS "j"
            WHERE "j"."status" IN (0, 1) 
                AND "j"."Type" = 1 
                AND CAST("t"."Id" AS TEXT) IN (SELECT "i"."value"
                                                FROM json_each("j"."Ids") AS "i"))
FROM 
    (SELECT 
            "c"."Id", "c"."SessionId", "c"."Source"
        FROM 
            "Chats" AS "c"
        ORDER BY 
            "c"."Id" DESC
        LIMIT 
            @__p_1 OFFSET @__p_0) AS "t"
LEFT JOIN 
    "ChatAnalysisResults" AS "c0" ON "t"."Id" = "c0"."ChatId"
LEFT JOIN 
    "Messages" AS "m" ON "t"."Id" = "m"."ChatId"
ORDER BY 
    "t"."Id" DESC, "c0"."Id"

But I don't really know if this is the right way to do this, any opinions on how to make this better ?

4
  • 2
    Try ProjectTo instead. Commented Jun 20, 2024 at 12:05
  • 1
    Looks good, i woud not change anything. Commented Jun 20, 2024 at 12:28
  • 1
    How can query.AnalyzedOnly be a boolean? Where is AnalyzedOnly in the class model? Commented Jun 20, 2024 at 15:41
  • Its just a query parameter in the request query dto so I can filter based on it @GertArnold Commented Jun 25, 2024 at 6:04

1 Answer 1

2

I would suggest to change the subquery on the Job entity to directly use .Any() with a predicate instead of .Where().Any(). This improves readability and has a minor performance improvement, since .Where() first needs to set up an iterator.

context.Jobs
       .Any(j => (j.status == JobStatus.Pending || 
                  j.status == JobStatus.Started) &&
                 j.Type == JobType.ChatAnalyze &&
                 j.Ids.Contains(chat.Id.ToString()))

For building the response you can also use LINQ. I am assuming you are using AutoMapper. Instead of using .ToArrayAsync() you can transform it into an IEnumerable with .AsEnumerable(). You should be able to perform the .Select() with the mapping logic on the IEnumerable. Afterwards you can use .ToArrayAsync() or .ToList().

You might also want to structure your code, since at least building your query should be separated from mapping the entities and returning the result to the client (e.g. defining a QueryBuilder and/or a Repository).

Sign up to request clarification or add additional context in comments.

1 Comment

Sorry for being late to respond, thanks this is very useful I will be implementing that for sure.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.