Entidades desconectadasDisconnected entities

Uma instância DbContext automaticamente controlará as entidades retornadas do banco de dados.A DbContext instance will automatically track entities returned from the database. As alterações feitas a essas entidades serão detectadas quando SaveChanges for chamado e o banco de dados será atualizado conforme o necessário.Changes made to these entities will then be detected when SaveChanges is called and the database will be updated as needed. Veja Salvamento Básico e Dados Relacionados para obter detalhes.See Basic Save and Related Data for details.

No entanto, às vezes, as entidades são consultadas usando uma instância de contexto e, em seguida, salvas usando uma instância diferente.However, sometimes entities are queried using one context instance and then saved using a different instance. Isso geralmente ocorre em cenários "desconectados", por exemplo, um aplicativo da Web onde as entidades são consultadas, enviadas ao cliente, modificadas, enviadas de volta para o servidor em uma solicitação e salvas.This often happens in "disconnected" scenarios such as a web application where the entities are queried, sent to the client, modified, sent back to the server in a request, and then saved. Nesse caso, o contexto da segunda instância precisa saber se as entidades são novas (devem ser inseridas) ou existentes (devem ser atualizadas).In this case, the second context instance needs to know whether the entities are new (should be inserted) or existing (should be updated).

Dica

Veja o exemplo deste artigo no GitHub.You can view this article's sample on GitHub.

Dica

O EF Core só pode controlar uma instância de qualquer entidade com um determinado valor de chave primária.EF Core can only track one instance of any entity with a given primary key value. A melhor maneira de evitar que esse seja um problema é usar um contexto de curta duração para cada unidade de trabalho, de modo que o contexto começa vazio, tem entidades anexadas a ele, salva essas entidades e, em seguida, o contexto é descartado.The best way to avoid this being an issue is to use a short-lived context for each unit-of-work such that the context starts empty, has entities attached to it, saves those entities, and then the context is disposed and discarded.

Identificando novas entidadesIdentifying new entities

O cliente identifica novas entidadesClient identifies new entities

O caso mais simples para lidar com isso é quando o cliente informa ao servidor se a entidade é nova ou existente.The simplest case to deal with is when the client informs the server whether the entity is new or existing. Por exemplo, geralmente, a solicitação para inserir uma nova entidade é diferente da solicitação para atualizar uma entidade existente.For example, often the request to insert a new entity is different from the request to update an existing entity.

O restante desta seção aborda os casos onde é necessário determinar se deseja inserir ou atualizar de alguma outra maneira.The remainder of this section covers the cases where it necessary to determine in some other way whether to insert or update.

Com as chaves geradas automaticamenteWith auto-generated keys

O valor de uma chave gerada automaticamente geralmente pode ser usado para determinar se uma entidade precisa ser inserida ou atualizada.The value of an automatically generated key can often be used to determine whether an entity needs to be inserted or updated. Se a chave não tiver sido definida (ou seja, ela ainda tem o valor padrão CLR de nulo, zero etc.), a entidade deverá ser nova e precisa de inserção.If the key has not been set (i.e. it still has the CLR default value of null, zero, etc.), then the entity must be new and needs inserting. Por outro lado, se o valor da chave tiver sido definido, ele já deverá ter sido salvo anteriormente e agora precisa ser atualizado.On the other hand, if the key value has been set, then it must have already been previously saved and now needs updating. Em outras palavras, se a chave tem um valor, a entidade foi consultada, enviada para o cliente e agora volta para ser atualizada.In other words, if the key has a value, then entity was queried, sent to the client, and has now come back to be updated.

É fácil verificar se há uma chave não definida quando o tipo de entidade é desconhecido:It is easy to check for an unset key when the entity type is known:

public static bool IsItNew(Blog blog) 
    => blog.BlogId == 0;

No entanto, o EF também tem uma forma interna de fazer isso para qualquer tipo de entidade e o tipo de chave:However, EF also has a built-in way to do this for any entity type and key type:

public static bool IsItNew(DbContext context, object entity) 
    => !context.Entry(entity).IsKeySet;

Dica

As chaves são definidas assim que as entidades são controladas pelo contexto, mesmo se a entidade estiver no estado adicionado.Keys are set as soon as entities are tracked by the context, even if the entity is in the Added state. Isso ajuda ao passar um gráfico de entidades e decidir o que fazer com cada, por exemplo, ao usar a API TrackGraph.This helps when traversing a graph of entities and deciding what to do with each, such as when using the TrackGraph API. O valor da chave somente deve ser usado da forma mostrada aqui antes que alguma chamada seja feita para controlar a entidade.The key value should only be used in the way shown here before any call is made to track the entity.

Com outras chavesWith other keys

Algum outro mecanismo é necessário para identificar novas entidades quando os valores de chave não são gerados automaticamente.Some other mechanism is needed to identify new entities when key values are not generated automatically. Há duas abordagens gerais para isso:There are two general approaches to this:

  • Consulta para a entidadeQuery for the entity
  • Passar um sinalizador do clientePass a flag from the client

Para consultar para a entidade, use apenas o método de localização:To query for the entity, just use the Find method:

public static bool IsItNew(BloggingContext context, Blog blog)
    => context.Blogs.Find(blog.BlogId) == null;

Está além do escopo deste documento mostrar o código completo para passar um sinalizador de um cliente.It is beyond the scope of this document to show the full code for passing a flag from a client. Em um aplicativo Web, isso geralmente significa fazer solicitações diferentes para diferentes ações, ou passar algum estado na solicitação e, em seguida, extraí-lo no controlador.In a web app, it usually means making different requests for different actions, or passing some state in the request then extracting it in the controller.

Como salvar entidades simplesSaving single entities

Se ele for conhecido ou não, uma inserção ou atualização será necessária, em seguida, adicionar ou atualizar pode ser usado de forma apropriada:If it is known whether or not an insert or update is needed, then either Add or Update can be used appropriately:

public static void Insert(DbContext context, object entity)
{
    context.Add(entity);
    context.SaveChanges();
}

public static void Update(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

No entanto, se a entidade usar valores de chave gerada automaticamente, o método de atualização poderá ser usado para ambos os casos:However, if the entity uses auto-generated key values, then the Update method can be used for both cases:

public static void InsertOrUpdate(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

O método de atualização normalmente marca a entidade para a atualização, não inserção.The Update method normally marks the entity for update, not insert. No entanto, se a entidade tem uma chave gerada automaticamente e nenhum valor de chave tiver sido definido, a entidade será automaticamente marcada para inserir.However, if the entity has a auto-generated key, and no key value has been set, then the entity is instead automatically marked for insert.

Dica

Esse comportamento foi introduzido no EF Core 2.0.This behavior was introduced in EF Core 2.0. Para versões anteriores, sempre é necessário escolher explicitamente adicionar ou atualizar.For earlier releases it is always necessary to explicitly choose either Add or Update.

Se a entidade não estiver usando as chaves geradas automaticamente, o aplicativo deverá decidir se a entidade deve ser inserida ou atualizada. Por exemplo:If the entity is not using auto-generated keys, then the application must decide whether the entity should be inserted or updated: For example:

public static void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }

    context.SaveChanges();
}

As etapas aqui são:The steps here are:

  • Se Localizar retornar nulo, isso significa que o banco de dados ainda não contém o blog com essa ID; portanto, chamamos Adicionar e a marcaremos para inserção.If Find returns null, then the database doesn't already contain the blog with this ID, so we call Add mark it for insertion.
  • Se Localizar retornar uma entidade, ela existirá no banco de dados e o contexto agora é controlar a entidade existenteIf Find returns an entity, then it exists in the database and the context is now tracking the existing entity
    • Em seguida, usamos SetValues para definir os valores de todas as propriedades nessa entidade para os estados que vieram do cliente.We then use SetValues to set the values for all properties on this entity to those that came from the client.
    • A chamada SetValues marcará a entidade para ser atualizada conforme o necessário.The SetValues call will mark the entity to be updated as needed.

Dica

SetValues somente marcará como modificadas as propriedades que têm valores diferentes para aqueles na entidade controlada.SetValues will only mark as modified the properties that have different values to those in the tracked entity. Isso significa que, quando a atualização é enviada, somente as colunas que realmente foram alteradas serão atualizadas.This means that when the update is sent, only those columns that have actually changed will be updated. (E, se nada foi alterado, nenhuma atualização será enviada).(And if nothing has changed, then no update will be sent at all.)

Como trabalhar com gráficosWorking with graphs

Resolução de identidadeIdentity resolution

Conforme observado acima, o EF Core só pode controlar uma instância de qualquer entidade com um determinado valor de chave primária.As noted above, EF Core can only track one instance of any entity with a given primary key value. Ao trabalhar com elementos gráficos, o gráfico deve ser criado idealmente de modo que essa constante seja mantida e o contexto deve ser usado para apenas uma unidade de trabalho.When working with graphs the graph should ideally be created such that this invariant is maintained, and the context should be used for only one unit-of-work. Se o gráfico contiver duplicatas, será necessário processar o gráfico antes de enviá-lo ao EF para consolidar várias instâncias em uma.If the graph does contain duplicates, then it will be necessary to process the graph before sending it to EF to consolidate multiple instances into one. Isso pode não ser trivial onde as instâncias têm valores e relações conflitantes, de modo que consolidar duplicatas deve ser feito assim que possível em seu pipeline de aplicativo para evitar a resolução de conflitos.This may not be trivial where instances have conflicting values and relationships, so consolidating duplicates should be done as soon as possible in your application pipeline to avoid conflict resolution.

Todas as entidades novas ou existentesAll new/all existing entities

Um exemplo de como trabalhar com elementos gráficos é inserir ou atualizar um blog junto com sua coleção de postagens associadas.An example of working with graphs is inserting or updating a blog together with its collection of associated posts. Se todas as entidades no gráfico tiverem que ser inseridas, ou todas tiverem que ser atualizadas, o processo será o mesmo descrito acima para entidades únicas.If all the entities in the graph should be inserted, or all should be updated, then the process is the same as described above for single entities. Por exemplo, um gráfico de blogs e postagens criados desta forma:For example, a graph of blogs and posts created like this:

var blog = new Blog
{
    Url = "http://sample.com",
    Posts = new List<Post>
    {
        new Post {Title = "Post 1"},
        new Post {Title = "Post 2"},
    }
};

pode ser inserido assim:can be inserted like this:

public static void InsertGraph(DbContext context, object rootEntity)
{
    context.Add(rootEntity);
    context.SaveChanges();
}

A chamada para adicionar marcará o blog e todas as postagens a serem inseridas.The call to Add will mark the blog and all the posts to be inserted.

Da mesma forma, se todas as entidades em um gráfico precisarem ser atualizados, atualização pode ser usado:Likewise, if all the entities in a graph need to be updated, then Update can be used:

public static void UpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

O blog e todas as suas postagens serão marcados para serem atualizados.The blog and all its posts will be marked to be updated.

Combinação de entidades novas e existentesMix of new and existing entities

Com as chaves geradas automaticamente, a atualização pode novamente ser usada para inserções e atualizações, mesmo se o gráfico contiver uma mistura de entidades que exigem inserção e as que precisam de atualização:With auto-generated keys, Update can again be used for both inserts and updates, even if the graph contains a mix of entities that require inserting and those that require updating:

public static void InsertOrUpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

A atualização marcará qualquer entidade no gráfico, blog ou postagem para inserção se não tiver um conjunto de valores de chave, enquanto todas as outras entidades estejam marcadas para atualização.Update will mark any entity in the graph, blog or post, for insertion if it does not have a key value set, while all other entities are marked for update.

Como antes, quando não estiver usando as chaves geradas automaticamente, uma consulta e algum processamento poderão ser usados:As before, when not using auto-generated keys, a query and some processing can be used:

public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }
    }

    context.SaveChanges();
}

Tratamento de exclusõesHandling deletes

A exclusão pode ser complicada de lidar porque geralmente a ausência de uma entidade significa que ela deve ser excluída.Delete can be tricky to handle since often the absence of an entity means that it should be deleted. Uma maneira de lidar com isso é usar "exclusões a quente", de modo que a entidade seja marcada como excluída, em vez de ser excluída de fato.One way to deal with this is to use "soft deletes" such that the entity is marked as deleted rather than actually being deleted. Exclui e, em seguida, torna-se o mesmo que as atualizações.Deletes then becomes the same as updates. As exclusões a quente podem ser implementadas usando filtros de consulta.Soft deletes can be implemented in using query filters.

Para exclusões verdadeiras, um padrão comum é usar uma extensão do padrão de consulta para executar o que é essencialmente uma diferença de gráfico. Por exemplo:For true deletes, a common pattern is to use an extension of the query pattern to perform what is essentially a graph diff. For example:

public static void InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }

        foreach (var post in existingBlog.Posts)
        {
            if (!blog.Posts.Any(p => p.PostId == post.PostId))
            {
                context.Remove(post);
            }
        }
    }

    context.SaveChanges();
}

TrackGraphTrackGraph

Internamente, adicionar, anexar e atualizar usam percurso de gráfico com uma determinação feita para cada entidade como se ela deve ser marcada como adicionada (para inserir), modificada (para atualizar), inalterada (não fazer nada) ou excluída (para excluir).Internally, Add, Attach, and Update use graph-traversal with a determination made for each entity as to whether it should be marked as Added (to insert), Modified (to update), Unchanged (do nothing), or Deleted (to delete). Esse mecanismo é exposto por meio da API TrackGraph.This mechanism is exposed via the TrackGraph API. Por exemplo, vamos supor que, quando o cliente envia de volta um gráfico de entidades, ele define alguns sinalizadores em cada entidade indicando como ela deve ser tratada.For example, let's assume that when the client sends back a graph of entities it sets some flag on each entity indicating how it should be handled. O TrackGraph pode ser usado para processar esse sinalizador:TrackGraph can then be used to process this flag:

public static void SaveAnnotatedGraph(DbContext context, object rootEntity)
{
    context.ChangeTracker.TrackGraph(
        rootEntity,
        n =>
        {
            var entity = (EntityBase)n.Entry.Entity;
            n.Entry.State = entity.IsNew
                ? EntityState.Added
                : entity.IsChanged
                    ? EntityState.Modified
                    : entity.IsDeleted
                        ? EntityState.Deleted
                        : EntityState.Unchanged;
        });

    context.SaveChanges();
}

Os sinalizadores são mostrados apenas como parte da entidade para manter a simplicidade do exemplo.The flags are only shown as part of the entity for simplicity of the example. Normalmente, os sinalizadores devem fazer parte de um DTO ou algum outro estado incluído na solicitação.Typically the flags would be part of a DTO or some other state included in the request.