Leonardo Henriques
Leonardo Henriques

Reputation: 782

DbSet.ToList() seeds other dbset

I'm using entity framework in my application, and I have a one-to-many models like this:

public class Email
{
    public string Id { get; set; }
    public string To { get; set; }
    public string From { get; set; }
    public string CC { get; set; }
    public string Bcc { get; set; }

    public string Subject { get; set; }
    public string Body { get; set; }

    public ICollection<AnexoEmail> Anexos{get;set;}
}

public class AnexoEmail
{
    [Key]
    public string Id { get; set; }

    /// <summary>
    /// Full path do ficheiro
    /// </summary>
    public string Nome { get; set; }
    public DateTime DataEnvio { get; set; }
    public bool Enviado { get; set; }

    public string EmailId { get; set; }
    public Email Email { get; set; }
}

It represents like a Email-Attachment relation. The thing is that I have a UOW and repository EmailRepository and something that I can't understand happens: On the repository:

public class EmailRepository : IEmailRepository
{
    internal DbContext _context;
    internal DbSet<Email> dbSet;
    internal DbSet<AnexoEmail> dbSetAnexo;

    public EmailRepository(DbContext context)
    {
        _context = context;
        dbSet = context.Set<Email>();
        dbSetAnexo = context.Set<AnexoEmail>();
    }
    (...)
    public IEnumerable<Email> GetAll()
    {
        dbSetAnexo.ToList(); //<- I can´t understand this part

        var emails = dbSet.ToList();
        return email;
    }
}

In this example I'm expecting emails.Anexos to be null, but for some reason, if I add that line the emails.Anexos gets the correct attachments(anexos), and if I don't emails.Anexos will be null as expected.

I don't have much experience in Ef core, if I'm missing some class or anything just tell me.

Thank you.

Upvotes: 2

Views: 1191

Answers (2)

DavidG
DavidG

Reputation: 118977

This is all caused by entity tracking. The line dbSetAnexo.ToList(); causes Entity Framework to track the entire DbSet, you can think of it as a form of caching if you like. Because EF has "cached" those AnexoEmail objects, it will present them to you when you request the value of email.Anexos.

You can prevent this from happening by using the AsNoTracking() extension method, for example:

dbSetAnexo.AsNoTracking().ToList();

Now, what you should be doing instead is using the Include function which will force EF to load up the specified related children. For example:

var emails = dbSet
    .Include(e => e.Anexos)
    .ToList();

Upvotes: 2

AustinWBryan
AustinWBryan

Reputation: 3326

This has to do with what .ToList() actually does. It doesn't change the original value of the collection, it just creates a new List<> and returns it. What you're currently doing is throwing the value out the window, and not doing anything with it. There are two ways to store it:

DbSet<AnexoEmail> dbSetAnexo;     // Keep original DbSet<>
List<AnexoEmail> listAnexo;       // Make new List<AnexoEmail>

listAnexo = dbSetAnexo.ToList();

Then, you would use listAnexo instead of dbSetAnexo. If you don't like that, and want to use only one variable, you can use the dynamic type which is the only type that can change from one type to another during runtime.

dynamic anexoEmails = new DbSet<AnexoEmail>();    // anexoEmails is currently a dBSet<>
anexoEmails = anexoEmails.ToList();               // anexoEmails is now a List<>

The latter might behavior more like you expected it would. Just keep in mind, with dynamic types, you're not gonna get as much compiler checking, and no IDE autocompletetion as all of that is check during runtime.

Upvotes: 0

Related Questions