Entity Framework 6 - modify nested collection by using DbCommandInterceptor

46 Views Asked by At

I need to update incoming data from database before they are send into application. I'm using DbCommandInterceptor when I can override ReaderExecuted method for this. Here I can change incoming data. This works well for properties directly in entity.

Here is an example:

public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
    var dbContext = interceptionContext.DbContexts.FirstOrDefault();
    if (dbContext != null)
    {
        var entities = dbContext.ChangeTracker.Entries().Where(p => p.State == EntityState.Unchanged);
        foreach (var entity in entities)
        {
            if (entity.Entity.GetType().BaseType == typeof(T01_Object))
            {
                entity.Property("ObjectName").CurrentValue = "test name";
                entity.Property("ObjectName").OriginalValue = "test name";
            }
        }
    }

    base.ReaderExecuted(command, interceptionContext);
}

This works, changed data are propagated into application.

But now I need to change data in nested collection. Entity T01_Object has property T102_Child which is collection of T102_Child

I've tried this:

public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
    var dbContext = interceptionContext.DbContexts.FirstOrDefault();

    if (dbContext != null)
    {
        var entities = dbContext.ChangeTracker.Entries().Where(p => p.State == EntityState.Unchanged);

        foreach (var entity in entities)
        {
            var typeAttributesRawCollection = entity.Collection("T102_Child");

            if (typeAttributesRawCollection != null)
            {
                foreach (var t102Entity in (IEnumerable)typeAttributesRawCollection.CurrentValue)
                {
                    var test = (T102_Child)t102Entity;
                    test.ChildName = "Test name";

                }
            }
        }
    }

    base.ReaderExecuted(command, interceptionContext);
}

But in this case I can not see modify data in application. There are original values. How to solve this problem. Should I call some method to let to entity framework know the data are changed? This is only one way modification without saving into database.

The entity type is some background logic (data encryption/decryption) and entity T102_Child is used in many places of the application. So I need have this logic in one place in application. The given example is only a simplification of the problem.

1

There are 1 best solutions below

0
Mateus Zampol On BEST ANSWER

I tried writing a demo and it works as expected. Does this help?

Please note that any SaveChanges() will persist the overwritten values, which is not what you want, I assume.

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure.Interception;
using System.Linq;

namespace ProvaFramework
{
    internal class Program
    {
        static void Main(string[] args)
        {
            DbInterception.Add(new MyInterceptor());

            var conn = Effort.DbConnectionFactory.CreateTransient();

            var db = new MyDb(conn, true);

            var parent1 = new Parent()
            {
                ParentId = 1,
            };
            var child1 = new Child()
            {
                ChildId = 1,
                ParentId = 1,
                Prop = "Child 1",
                Parent = parent1,
            };
            var child2 = new Child()
            {
                ChildId = 2,
                ParentId = 1,
                Prop = "Child 2",
                Parent = parent1,
            };

            parent1.Children.Add(child1);
            parent1.Children.Add(child2);

            db.Parents.Add(parent1);
            db.Children.Add(child1);
            db.Children.Add(child2);
            db.SaveChanges();

            foreach (var parent in db.Parents)
            {
                foreach (var child in parent.Children)
                {
                    Console.WriteLine($"Child {child.ChildId} has Prop {child.Prop}");
                }
            }

            db.SaveChanges();

            foreach (var parent in db.Parents)
            {
                foreach (var child in parent.Children)
                {
                    Console.WriteLine($"Child {child.ChildId} has Prop {child.Prop}");
                }
            }

            Console.ReadLine();
        }
    }

    class MyInterceptor : DbCommandInterceptor
    {
        public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            var db = interceptionContext.DbContexts.Single();

            var parents = db.ChangeTracker.Entries<Parent>().Where(p => p.State == EntityState.Unchanged);

            foreach (var parent in parents)
            {
                var children = parent.Collection(p => p.Children);

                foreach (var child in children.CurrentValue)
                {
                    if (child.Prop == "OVERRIDE")
                    {
                        child.Prop = "SAVED!!! NO!!!!";
                    }
                    else
                    {
                        child.Prop = "OVERRIDE";
                    }
                }
            }

            base.ReaderExecuted(command, interceptionContext);
        }
    }
    class MyDb : DbContext
    {
        public MyDb(DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection) { }

        public virtual DbSet<Parent> Parents { get; set; }
        public virtual DbSet<Child> Children { get; set; }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Child>().HasKey(x => x.ChildId);
            modelBuilder.Entity<Parent>().HasKey(x => x.ParentId);
            modelBuilder.Entity<Parent>().HasMany(p => p.Children).WithRequired(c => c.Parent).HasForeignKey(p => p.ParentId);

            base.OnModelCreating(modelBuilder);
        }
    }
    class Parent
    {
        public int ParentId { get; set; }
        public virtual ICollection<Child> Children { get; set; } = new HashSet<Child>();
    }
    class Child
    {
        public int ChildId { get; set; }
        public int ParentId { get; set; }
        public string Prop { get; set; }
        public virtual Parent Parent { get; set; }
    }
}