4

I have used the Entity Framework Core and update some object with relation to another object.

I have a following entities:

Client:

public class Client
{
    public int Id { get; set; }

    public string ClientId { get; set; }

    public string ClientName { get; set; }

    public List<ClientScope> Scopes { get; set; }
}

ClientScope:

public class ClientScope
  {
    public int Id { get; set; }

    public string Scope { get; set; }

    public Client Client { get; set; }
  }

OnModelCreating:

modelBuilder.Entity<Client>((Action<EntityTypeBuilder<Client>>) (client =>
      {
        client.ToTable<Client>(storeOptions.Client);
        client.HasKey((Expression<Func<Client, object>>) (x => (object) x.Id));
        client.Property<string>((Expression<Func<Client, string>>) (x => x.ClientId)).HasMaxLength(200).IsRequired(true);
        client.HasIndex((Expression<Func<Client, object>>) (x => x.ClientId)).IsUnique(true);
        client.HasMany<ClientScope>((Expression<Func<Client, IEnumerable<ClientScope>>>) (x => x.AllowedScopes)).WithOne((Expression<Func<ClientScope, Client>>) (x => x.Client)).IsRequired(true).OnDelete(DeleteBehavior.Cascade);
      }));

modelBuilder.Entity<ClientScope>((Action<EntityTypeBuilder<ClientScope>>) (scope =>
      {
        scope.ToTable<ClientScope>(storeOptions.ClientScopes);
        scope.Property<string>((Expression<Func<ClientScope, string>>) (x => x.Scope)).HasMaxLength(200).IsRequired(true);
      }));

I would like update the ClientScope for specific Client.Id for example Id = 1.

I have tried use this way:

public void UpdateClientScope(ClientScope scope){

_dbContext.ClientScope.Update(scope);
_dbContext.SaveChanges();
}

var scope = new ClientScope() { Client = new Client{Id = 1}, Id = 1, Scope = "Test Scope" }
UpdateClientScope(scope);

But this way try to update the Client as well. I want to update only ClientScope and specify ClientId which it is stored on my form.

What is the best way how update ClientScope above?

I have tried to implement the BaseRepository which I want to implement for every entity something like this:

public class BaseRepository<TDbContext, TEntity, TPrimaryKey> : IBaseRepository<TDbContext, TEntity, TPrimaryKey>
        where TEntity : class
        where TDbContext : DbContext
{
 public virtual DbSet<TEntity> Table => _dbContext.Set<TEntity>();
 
private readonly TDbContext _dbContext;

public BaseRepository(TDbContext dbContext)
        {
            _dbContext = dbContext;
        }
      
public virtual async Task<TEntity> UpdateAsync(TEntity entity)
        {
            Table.Update(entity);

            await _dbContext.SaveChangesAsync();

            return entity;
        }
}

How can I correctly specify the update method for entities like this?

9
  • Updating the disconnected entity with relations cannot be done with naïve generic approach which works only for simple entities w/o navigation properties. Commented Sep 21, 2017 at 7:50
  • Please, what do you mean with disconnected entities? Commented Sep 21, 2017 at 7:51
  • The ones that are not retrieved/tracked by the context. Like your var scope = new ClientScope() { Client = new Client{Id = 1}, Id = 1, Scope = "Test Scope" }. Both scope and scope.Client are disconnected. As opposed to var scope = _dbContext.ClientScope.Include(x => x.Client).FirstOrDefault(...);. In the later case EF knows that scope and scope.Client exists in database, in former case it has absolutely no idea what they are, hence you have to properly attach them manually. Commented Sep 21, 2017 at 8:00
  • 1
    The technique is called stub entity and should work as soon as the context is short lived and used just for the operation in question. If there are multiple calls to the same context instance, the things can get messed up. Another way in scenario like this is to have explicit FK property, and pass scope with Client set to null and ClientId set to 1. Commented Sep 21, 2017 at 8:44
  • 1
    Ivan please, post your ideas as an answer - this is very useful. Commented Sep 21, 2017 at 11:15

4 Answers 4

3

If you never plan to add or modify a related entity via your repository methods you can simply set all other entities' state to EntityState.Unchanged, e.g.:

public virtual async Task<TEntity> UpdateAsync(TEntity entity)
{
    Table.Update(entity);

    foreach( var entry in _dbContext.ChangeTracker.Entries() )
    {
        if( entry.Entity != entity )
        {
            entry.State = EntityState.Unchanged;
        }
    }

    await _dbContext.SaveChangesAsync();

    return entity;
}

Alternatively, attach related entities as unchanged before calling the repo method(s). Perhaps create a repo method to return such an entity:

public TEntity GetEntityPlaceholder( int id )
{
    var entity = new TEntity() { Id = id };
    _dbContext.Attach( entity );
    return entity;
}

I prefer having the FK properties available, myself:

public class ClientScope
{
    public int Id { get; set; }

    public string Scope { get; set; }

    public Client Client { get; set; }
    // FK, use [ForeignKey( "Client" )] if you wish
    public int ClientId { get; set; }
}

// or use FluentAPI
modelBuilder.Entity<ClientScope>()
    .HasRequired( cs => cs.Client )
    .WithMany( c => c.Scopes )
    // specify foreign key
    .HasForeignKey( cs => cs.ClientId );

// now can specify a client by setting the ClientId property
var scope = new ClientScope() 
{ 
    ClientId = 1, 
    Id = 1, 
    Scope = "Test Scope",
}
UpdateClientScope(scope);
Sign up to request clarification or add additional context in comments.

3 Comments

Moho - interesting idea - what do you mean having the FK properies available - how do you do updating like above? Thank you.
Create an int ClientId { get; set; } property and configure its use as the Client's FK property either via the ForeignKeyAttribute or FluentAPI. Then you simply set the ClientId property with the client's ID instead of having to instantiate and attach a Client entity object
I love your answer - please can you put your modified model like you mentioned in previous comment and how these model structure update? Thank you so much!
2

As mentioned in the comments (thanks, Ivan), EF needs to 'know about' the object you want to update.

Sorry, I don't have anything to hand to test this with, but your UpdateClientScope method should look something like this:

public void UpdateClientScope(ClientScope scope){

// Get the existing object from the DB
ClientScope dbScope = _dbContext.ClientScope.FirstOrDefault(x => x.Id == scope.Id);

// Test it was in DB
if (dbScope != null)
{
  // Update the database object
  dbScope.Scope = scope.Scope;
  dbScope.Client = scope.Client;

  // SaveChanges works on dbScope
  _dbContext.SaveChanges();
}
else
{
  // Object not found, some error processing
}
}

Comments

1

I do not understand where is a problem ? through DBContext object you can reach any class object than object's property, than just change a value of property with simple assign operator and just DbContext.SaveChanges() your changes will be updated in Database column with no problem at all. If you want separate Update() method for changes you can do it with couple of code lines or more complex way with C# Reflection possibility but i can't imagine why would you need this ?! If i missed something tell me more. Good luck !!!!

6 Comments

You can imagine - I create a new Client with set of properties but if I want to update only ClientScope and I want to specify only Client.Id and I want to update only ClientScope but the object Client will be updated as well and all properties of Client is set to default values. How can I specify that I want to update only ClientScope - not Client? :)
Well i think you model building is not correct in Client model class you have declared List generic and with ClientScope inside generic then in ClientScope model class you have declared Client model instance to be honest i did not understand why did you linked both objects in both model and very different way ? Why ClientScope is List object ? Why do you need List object what is a purpose ? and in ClientScope model Client model what is doing there ?
I've updated my post with current settings in OnModelCreating method. For one client could be more clientscopes - for this purpose it probable use List with ClientScope. ClientScope must be connected with one Client. Feel free to paste your idea of model. Thank you.
Just make two distinct models and that's it ! You have said that when you want to update ClientScope same time you you do not want to update Client model isn't it ? And here we go just create two separate class models and everything will be ok. If anyway you want that Client model and ClientScope model should have similar property for joining data later. You can do it in SQL Server(or any DB you are using). Either way i do not see a purpose for dependency on property level.
You mean - create separate models and create foreign key and etc. out of model - for examle on sql management studio manually?
|
0

i think your entities false because your ClientScope has not clientId (Foreign Key).

public string ClientId { get; set; }

2 Comments

I have created the Foreign Key in OnModelCreating section - that I don't paste here - my failt sorry. And ClientId is not primary key - just unique identitfier for Client.
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker.

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.