locked
CTP2- Odd behaviour of ChangeInterceptor for entities with navegation properties RRS feed

  • General discussion

  • Hello,
    I',m testing the new CTP2 with its new features about navegation properties, using Entity Framework 4.1 with Code First, and i found something that is a little odd for me. The behaviour of the a entity with a navegation property depends on the way i create the entities.
    The behaviour i expected for the ChangeInterceptor method when i create an entity and one of its navegation properties in a batch is:
    1. Creation of the navegation entity.
    2. Creation of the entity with its navegation property not null (property reference to the navegation entity).
    The reason for that is when an entity is created through the Data Service and the navegation property already exists the behaviour is as point 2 just above-mentioned.
    For testing I've used the next two entities (where the first is a navegation property of the second):
        public class AccountAddress
        {
            [Key]
            public long AddressId { get; set; }
          
            public string City { get; set; }
          
            public string Country { get; set; }
            public ICollection<Account> Accounts { get; set; }
        }
        public class Account
        {
            [Key]
            public long AccountId { get; set; }
            public string Code { get; set; }
            public string Description { get; set; }
            public long AddressId { get; set; }
            
            [ForeignKey("AddressId")]
            public AccountAddress Address { get; set; }
        }
    I created the Context:
        public class MyContext : DbContext
        {
            public MyContext()
                : base("MyContext")
            {
            }
            public DbSet<AccountAddress> AddressSet { get; set; }
            public DbSet<Account> AccountSet { get; set; }
        }
    Using the CTP2 i created my DataService definition:
        class CTPDataService : DataService<ObjectContext>
        {
            MyContext context = new MyContext();
            protected override ObjectContext CreateDataSource()
            {
                context.Configuration.ValidateOnSaveEnabled = true;
                context.Configuration.LazyLoadingEnabled = true;
                context.Configuration.ProxyCreationEnabled = false;
                return ((IObjectContextAdapter)context).ObjectContext;
            }
            public static void InitializeService(DataServiceConfiguration config)
            {
                config.SetEntitySetAccessRule("AccountSet", EntitySetRights.All);
                config.SetEntitySetAccessRule("AddressSet", EntitySetRights.All);
                config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
                config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
                config.DataServiceBehavior.AcceptProjectionRequests = true;
                config.UseVerboseErrors = true;
            }
            [ChangeInterceptor("AccountSet")]
            [ChangeInterceptor("AddressSet")]
            public void OnUpdateOperation(object source, UpdateOperations operation)
            {
                if (operation == UpdateOperations.Add)
                {
                    Account account = source as Account;
                    if (account != null)
                    {
                        if (account.AddressId == 0 && account.Address == null)
                            throw new DataServiceException(402, "Account requires an Address");
                    }
                }
            }
        }
    For Testing purposes i created a Console application to expose the  DataService:
        class Program
        {
            static private Uri address = new Uri("http://localhost:2272/MyCTP2DataSService.svc");
            static private DataServiceHost host;
            static void Main(string[] args)
            {
                host = new DataServiceHost(typeof(CTPDataService), new Uri[] { address });
                host.Open();
                Console.WriteLine("Web Service Host");
                Console.ReadKey();
                host.Close();
            }
        }
    After that i create two scenarios:
    1. Create both entities at the same time (Account and AccountAddress) with a SetLink.
    2. Create a new Account with a SetLink to an existing AccountAddress.
    For both scenarios i expected the same behaviour for account entity on my "ChangeInterceptor" method:
    In the first scenario i expected intercepting the "Add operation" for of the AccountAddress and next the interception of the "Add operation" for the Account entity, that already happend; but, i expected in the navegation property "Address" the AccountAddress i linked not a null, and a new interception with a "Change operation".  In the second scenario the behaviour was that one i expected, interception of the account with navegation property "Address" with the AccountAddress i linked on my test project.
    For test these scenarios i created a new project with the next source code:
        class Program
        {
            static private Uri address = new Uri("http://localhost:2272/MyCTP2DataService.svc");
            static void Main(string[] args)
            {
                DataServiceContext ctx = new DataServiceContext(address);
                var query = ctx.CreateQuery<AccountAddress>("AddressSet");
                foreach (AccountAddress item in query)
                {
                }
                //Scenario 1:
                // Both entities in a batch
                AccountAddress addr = new AccountAddress()
                {
                    City = "SOS",
                    Country = "ARAGON"
                };
                ctx.AddObject("AddressSet", addr);
                Account account = new Account()
                {
                    Code = "MyFirstAccount",
                    Description = "My First account"
                };
                ctx.AddObject("AccountSet", account);
                ctx.SetLink(account, "Address", addr);
                try
                {
                    ctx.SaveChanges(SaveChangesOptions.Batch);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.InnerException.Message);
                }
                //Scenario 2:
                //Creation of address first, account in second step.
                ctx = new DataServiceContext(address);
                addr = new AccountAddress()
                {
                    City = "SOS",
                    Country = "ARAGON"
                };
                ctx.AddObject("AddressSet", addr);
                ctx.SaveChanges();
                account = new Account()
                {
                    Code = "MyFirstAccount",
                    Description = "My First account"
                };
                ctx.AddObject("AccountSet", account);
                ctx.SetLink(account, "Address", addr);
                ctx.SaveChanges(SaveChangesOptions.Batch);
                Console.ReadKey();
            }
        }
    I think the behaviour should be the same in both scenarios; if I want apply some business rules it cannot depens on the way a create entities. If instead of using a SaveChanges with SaveChangesOptions.Batch i use SaveChangesOptions.None it works as i expect in the first scenario (because from Data Service point of view both scenarios are the same).
    Thanks in advance,
    Jose






    Tuesday, April 19, 2011 7:33 AM