Artem Valmus
Artem Valmus

Reputation: 33

Can I get entity relationships dynamically in SaveChanges()?

TargetFramework: netstandard2.0

EntityFrameworkCore: 2.2.6

I have the following code in OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<SlotOrder>(entity =>
    {
        entity.Property(e => e.Id).HasConversion(
            v => v.ToString(),
            v => new Guid(v));
    });

    modelBuilder.Entity<SlotOrderDetail>(entity =>
    {
        entity.Property(e => e.Id).HasConversion(
            v => v.ToString(),
            v => new Guid(v));

        entity.HasOne<SlotOrder>()
            .WithMany()
            .HasForeignKey(c => c.SlotOrderId);
    });
}

I do not use navigation properties and need to load all relationships of a particular entity in SaveChangesAsync. In my case if the entity is SlotOrder I need to determine that it has a child entity SlotOrderDetail:

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    ChangeTracker.DetectChanges();

    var utcNow = DateTime.UtcNow;

    var added = ChangeTracker.Entries()
        .Where(t => t.State == EntityState.Added)
        .Select(t => t.Entity)
        .ToList();

    added.ForEach(entity =>
    {
        if (entity is IAuditable auditable)
        {
            auditable.CreatedAt = utcNow;
            auditable.UpdatedAt = utcNow;
        }

        // var relationships = ...
    });

    return base.SaveChangesAsync(cancellationToken);
}

Any clue how to do that?

Upvotes: 3

Views: 743

Answers (2)

Asherguru
Asherguru

Reputation: 1741

I don't think it's possible to do that but I just got another idea, something like this. Create new function to do SaveChanges then load all.

In any class you create you like.

public IQueryable<T> CommitLoad<T>() where T : class
    {
        db.SaveChanges();
        var list = db.Set<T>().AsQueryable();

        var key = db.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.FirstOrDefault();
        var foreignkeys = key.GetContainingPrimaryKey().GetReferencingForeignKeys();

        if (foreignkeys.Count() > 0)
        {
            foreach (var item in foreignkeys)
                list = list.Include<T>(item.DeclaringEntityType.DisplayName());
        }

        return list;
    }

Any class or page

public IQueryable<SlotOrder> GetTest()
    {
        //Save record to table

        //After saving record, savechanges + load all
        var list = CommitLoad<SlotOrder>();

        return list;
    }

Here is result screenshot

See this result screenshot

Upvotes: 2

Ivan Stoev
Ivan Stoev

Reputation: 205539

The relationship metadata is provided by IForeignKey interface.

Given an IEntityType, there are two methods that you can use to obtain information for entity relationships - GetForeignKeys which returns the relationships where the entity is the dependent, and GetReferencingForeignKeys which return the relationships where the entity is the principal.

In your case, don't select the .Entity property, use the EntityEntry which gives you access to the IEntityType via Metadata property, e.g.

var addedEntries = ChangeTracker.Entries()
    .Where(t => t.State == EntityState.Added)
    .ToList();

addedEntries.ForEach(entry =>
{
    if (entry.Entity is IAuditable auditable)
    {
        auditable.CreatedAt = utcNow;
        auditable.UpdatedAt = utcNow;
    }

    var foreignKeys = entry.Metadata.GetForeignKeys();
    var referencingForeignKeys = entry.Metadata.GetReferencingForeignKeys();
});

Upvotes: 3

Related Questions