Is there a way to get a field from a navigation property through change tracking in a generic way?

Viewed 12

I have an application that generates email notifications when a record changes that includes the previous and current values of the field. So far, I've been relying on EF change tracking to determine which fields have changed and what the original and new values are. This works well for normal record properties. However, for ID fields functioning as a foreign key to another record, while I can easily identify when the ID has changed, I am having trouble taking that Id and getting a field from the dependent record in a generic manner. Each of the relevant dependent fields implement a common interface, and I'd be looking to get the same field for each field.

Here's an example of the data structures:

public class PrimaryRecord {
    public int Id { get; set; }
    public string SelfProp1 { get; set; }
    public int DepRec1Id { get; set; }
    public DepRec1 DepRec1 { get; set; }
    public int DepRec2Id { get; set; }
    public DepRec2 DepRec2 { get; set; }
}

public interface ISetupRecord {
    public int Id { get; set; }
    public string Value { get; set; }
    public string Description { get; set; } // what I want to display
}

public class DepRec1 : ISetupRecord {
}

public class DepRec2 : ISetupRecord {
}

From EF change tracking, I know that, say, DepRec1Id has changed from 1 to 2, and I want to be able to display the Description field from DepRec1 (which implements ISetupRecord). Similarly, if DepRec2Id changed from 3 to 4, I'd like to display the Description field from DepRec2 for each of those two Id values.

So far, I've been going down a reflection route, but am having trouble executing the appropriate LINQ extension methods. I may just need a hint there or perhaps a better way to approach this if this isn't ideal.

Expanding on https://stackoverflow.com/a/64952442 I added this Set method to the db context:

public object Set(Type t)
        {
            return this.GetType().GetMethods().First(m => m.Name == "Set" && m.IsGenericMethod).MakeGenericMethod(t).Invoke(this, null);
        }

which is used in this method that attempts to accomplish what I'm looking at

private static string ConvertValueToDescIfId(PropertyEntry prop, string value, DBContext dbContext)
        {
            if (value == null) throw new ArgumentNullException("Value cannot be null.");
            if (prop == null) throw new ArgumentNullException("Prop cannot be null.");

            if (!prop.Metadata.Name.EndsWith("Id")) return value;

            if (!prop.Metadata.IsForeignKey()) throw new InvalidOperationException("The provided Id property is not a foreign key.");

            if (!Int32.TryParse(value, out var id)) throw new ArgumentException("Value must be int parseable.");

            var fk = prop.Metadata.GetContainingForeignKeys().First();

            var entityType = fk.PrincipalEntityType;
            var clrType = entityType.ClrType;

            var dbSet = dbContext.Set(clrType);
            var firstOrDefaultMethod = dbSet.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(m => m.Name == "FirstOrDefaultAsync" && m.GetParameters().Count() == 2);

            // around here is where I need to do something different if continuing with this approach because firstOrDefaultMethod returns null 

            var whereExprParam = Expression.Parameter(clrType, "o");
            var whereExprIdProp = Expression.Property(whereExprParam, "Id");
            var whereExpr = Expression.Equal(whereExprIdProp, Expression.Constant(id));

            var resp = firstOrDefaultMethod.Invoke(dbSet, new object[] { whereExpr });

            // still more to do to get the Description field....

            return value;
        }
0 Answers
Related