Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format with another type or relation by FK #526

Closed
knopa opened this issue Sep 1, 2022 · 16 comments
Closed

Format with another type or relation by FK #526

knopa opened this issue Sep 1, 2022 · 16 comments

Comments

@knopa
Copy link

knopa commented Sep 1, 2022

I have two question regarding EntityFramework for .net core 6 version

  1. Is it possible to set format for Changes Old, New Value like in EntityFramework-Plus

.Format<OrderItem>(x => x.Price, x => x.ToString("$#.00"));
As example for enum types which stored as int but you want to show them as string.
2. Is possible to grab more info for related object into to changes
Like I have
User -> UserProfile
UserProfile -> MentorId -> User
User -> UserGroup
If I have changed MentorId - be able to Format value to use Mentor.Name or fetch it

@knopa
Copy link
Author

knopa commented Sep 1, 2022

I gues with first found a solution but I think itsn't not useful - I think better to do more flexible current format

_auditContext.EntitySettings = new()
                {
                    {
                        typeof(UserProfile), 
                        new EfEntitySettings()
                        {
                            FormatProperties = new Dictionary<string, Func<object, object>>()
                            {
                                {nameof(UserProfile.UserType), val => ((UserType)val).ToString()}
                            }
                        }
                    }
                };

@thepirat000
Copy link
Owner

thepirat000 commented Sep 1, 2022

For the format, there is a Format method on the fluent API:

https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.EntityFramework/README.md#settings-high-level-interceptor

Audit.EntityFramework.Configuration.Setup()
    .ForContext<MyContext>(config => config
        .ForEntity<OrderItem>(_ => _
            .Format(x => x.Price, x => x.ToString("$#.00"))));

@knopa
Copy link
Author

knopa commented Sep 1, 2022

Well it doesn't work as expected because it use same Type as prop not object one

Format<TProp>(Expression<Func<TEntity, TProp>> property, Func<TProp, TProp> format);

Something like that will helps I think

public IContextEntitySetting<TEntity> Format<TProp>(Expression<Func<TEntity, TProp>> property, Func<TProp, object> format)
{
    var name = GetMemberName(property);
    FormatProperties[name] = entity => format.Invoke((TProp)entity);
    return this;
}

@knopa
Copy link
Author

knopa commented Sep 1, 2022

I have created PR with that change if you don't mind #527

@thepirat000
Copy link
Owner

Oh, you're right

Of course I don't mind, thanks a lot!

Will include the changes on the next release

@knopa
Copy link
Author

knopa commented Sep 1, 2022

What is ETA for that?

Any suggestion on second question?

@thepirat000
Copy link
Owner

Maybe today.

Could you elaborate a little bit more on your second question? I think I don't fully understand

@knopa
Copy link
Author

knopa commented Sep 1, 2022

You are amazing.

As for second question:
I need a convinient way
how to use fields from FK entity even if need to load it explicitly.
as example
I have model

public class User {
    public string Name { get; set; }
    public int TeacherId { get; set; } // FK to User
    public User Teacher { get; set; }
}

I need something like that or very close to it

Format(x => x.TeacherId, t => t.Teacher.Name, (x, context) => context.Users.FirstOrDefault(u => u.Id == x.TecherId).Name)

Clients be able to see not usual TeacherId from 25 to 234 as their names

@thepirat000
Copy link
Owner

Which data provider are you using to store the audit events?

@knopa
Copy link
Author

knopa commented Sep 1, 2022

Audit.EntityFramework

@thepirat000
Copy link
Owner

thepirat000 commented Sep 1, 2022

Are you storing the audits on tables in the same database you are auditing?
Could you share your data provider configuration?
Audit.Core.Configuration.Setup().UseEntityFramework(...)

It sounds like the AuditEntityAction is the place to do the override of the property.

For example, assuming a lot of things like you have an entity to store the audits called UserAudit, you could do:

Audit.Core.Configuration.Setup()
    .UseEntityFramework(_ => _
        .AuditTypeMapper(t => typeof(UserAudit))
        .AuditEntityAction<UserAudit>((ev, entry, audit) =>
        {
            audit.Id = 0;  // Just to be sure the automatic mapping User->UserAudit should not map User.Id to UserAudit.Id
            if (entry.GetEntry().Entity is User user)
            {
                var ctx = (MyContext)ev.GetEntityFrameworkEvent().GetDbContext();
                audit.TeacherName = ctx.Teachers.First(t => t.Id == user.TeacherId).TeacherName;
            }
        }));

@knopa
Copy link
Author

knopa commented Sep 1, 2022

It looks like that

Audit.Core.Configuration
            .AddCustomAction(ActionType.OnScopeCreated, scope =>
            {
                var id = Guid.NewGuid();
                scope.SetCustomField("AuditScopeId", id);
            });
Audit.Core.Configuration.Setup()
    .UseEntityFramework(_ =>_
        .AuditTypeMapper(_ => typeof(AuditLog))
        .AuditEntityAction<AuditLog>(Handle)
        .IgnoreMatchedProperties());


private static void Handle(AuditEvent ev, EventEntry entry, AuditLog target)
    {
        if (ev.CustomFields.TryGetValue("AuditScopeId", out object value))
        {
            target.TransactionId = (Guid)value;
        }

        AuditConfig config = new();
        if (ev.CustomFields.TryGetValue(nameof(AuditConfig), out object configValue))
        {
            config = (AuditConfig)configValue;
        }

        target.Event = config.Event;
        target.RootEntityId = config.RootEntityId;
        target.RootEntityType = config.RootEntityType;
        target.PerformedById = config.ResponsibleId;

        target.EntityType = entry.EntityType.Name;
        target.AuditDate = DateTime.UtcNow;
        target.ActionType = entry.Action;
        target.EntityId = entry.PrimaryKey.First().Value.ToString();

        target.Changes = new();
        if ((target.ActionType == "Insert"
         || target.ActionType == "Delete")
            && entry.ColumnValues != null)
        {
            target.Changes.AddRange(entry.ColumnValues.Select(c => new PropertyChange()
            {
                Name = c.Key,
                New = c.Value
            }));
        }

        if (entry.Changes != null)
        {
            target.Changes.AddRange(entry.Changes.Select(c => new PropertyChange()
            {
                Name = c.ColumnName,
                New = c.NewValue,
                Old = c.OriginalValue
            }));
        }
    }

@thepirat000
Copy link
Owner

thepirat000 commented Sep 1, 2022

If you really need to do it with a method like Format(), which replaces the values on Changes and ColumnValues collection, then I think it could be possible to provide another overload for Override() that takes a func of EntityEntry.

So you could do:

  .ForEntity<User>(cfg => cfg
    .Override(u => u.UserName, 
      entry => ((MyContext)entry.Context).Teachers.First(t => t.Id == ((User)entry.Entity).TeacherId).TeacherName

To replace the value of "UserName" column with the Teacher Name (not a very nice example, sorry)

The sad part is that you'll probably need to do some casting.

Do you think something like that will work for you?

@knopa
Copy link
Author

knopa commented Sep 2, 2022

Looks not bad. You be able to grab value from entry relation or from context if it not included

thepirat000 added a commit that referenced this issue Sep 2, 2022
…` methods for Entity Framework config fluent API (#526)
@thepirat000
Copy link
Owner

This was implemented in version 19.4.0, please upgrade your references and re-test.

  • Relaxed Format() method to allow returning any type.
  • New overload for Override() method taking an EntityEntry

@knopa
Copy link
Author

knopa commented Sep 2, 2022

Great

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants