locked
Code First CTP4 - Many to One (inverse) relationship not working with existing DB RRS feed

  • Question

  • Hi

    I have a problem defining the correct mappings using the fluent interface against an existing DB. I have a one to many relationship between a customer and a orderrecord in the DB. The tables are mapped to POCO classes called Client and Order. The Client class has a Orders property that represents a list of orders and the Order class has a Client property.

    My problem is that while I am able to access orders from from a client when I retrieve the client first, I cannot  access a Client(always null) when retrieving the Order first. i.e. The one to many side works, but not the many to one side.

    Stated another way..I can navigate to Client.Orders[0].Client successfully but not to Order.Client.

    I have tried many variations(WithRequired, HasConstraint etc) to get this working without any luck. Please advise on the correct way to map this. All the associated code is below.


    DB Tables:

    orderrecord
    --------------
    id , int, PK
    customerid, int, FK to customer table
    orderdate, datetime

    customer 
    ------------
    id, int, PK
    name, varchar(50)

    POCO
    -----------

    public class Order
    {
    public int Id { get; set; }
    public DateTime DateOrdered { get; set; }
    public virtual Client Client { get; set; }
    }
    
    public class Client
    {
    public int Id { get; private set; }
    public string Name { get; set; }
    public virtual IList<Order> Orders { get; set; }
    }
    
    


    The mapping code is as follows: 

    modelBuilder.Entity<Client>()
    .HasKey(client => client.Id)
    .MapSingleType(client => new
    {
      id = client.Id,
      name = client.Name
    }).ToTable("customer"); 
    
    modelBuilder.Entity<Client>()
      .HasMany(client => client.Orders); 
    
    modelBuilder.Entity<Order>()
    .HasKey(order => order.Id)
    .MapSingleType(order => new
    {
      id = order.Id,
      customerid = order.Client.Id,
      orderdate = order.DateOrdered
    }).ToTable("orderrecord"); 
    
    modelBuilder.Entity<Order>()
      .HasRequired(order => order.Client); 
    
    

    Thanks
    William 

     

    Thursday, November 18, 2010 7:00 AM

Answers

  • Hi William,

    The private constructor is an issue because EF creates a proxy that derives from your type in order to perform lazy loading (we override your navigation property getter and insert some logic to load on first read).

    It's not possible to derive from a class with only private constructors, if you make the constructor protected you should be good to go.

    ~Rowan

    Friday, November 19, 2010 11:11 PM
    Moderator

All replies

  • Hi William,

    Using the setup you supplied if I run the following code then order.Client is correctly assigned;

    using (var context = new OrderContext())
    {
      var order = context.Orders.FirstOrDefault();
      Debug.Assert(order.Client != null);
    }

     

    Are you able to post up some code that reproduces the issue?

    ~Rowan

    Thursday, November 18, 2010 7:37 PM
    Moderator
  • Hi Rowan

    Thanks for the reply. Here is the code that reproduces the issue. Please let me know if you need anything else.

    public class TestModel : DbContext
     {
      public DbSet<Order> Orders { get; set; }
      public DbSet<Client> Clients { get; set; }
    
      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
       modelBuilder.IncludeMetadataInDatabase = false;
    
    
       modelBuilder.Entity<Client>()
        .HasKey(client => client.Id)
        .MapSingleType(client => new
        {
         id = client.Id,
         name = client.Name
    
        }).ToTable("customer");
    
       modelBuilder.Entity<Client>().HasMany(client => client.Orders);
    
       modelBuilder.Entity<Order>()
        .HasKey(order => order.Id)
        .MapSingleType(order => new
        {
         id = order.Id,
         customerid = order.Client.Id,
         orderdate = order.DateOrdered
        }).ToTable("orderrecord");
    
       modelBuilder.Entity<Order>().HasRequired(order => order.Client);
      }
     }
    

     

    public class ClientRepository : IRepository<Client>
     {
      TestModel _context = new TestModel();
    
      #region IRepository Members
    
      public Client Get(string clientId)
      {
       int id = Convert.ToInt32(clientId);
       return _context.Clients.SingleOrDefault(c => c.Id == id);
      }
    
      public void Add(Client client)
      {
       throw new System.NotImplementedException();
      }
    
      public void Save()
      {
       throw new System.NotImplementedException();
      }
    
      #endregion
    
     }
    

     

    public class OrderRepository : IRepository<Order>
     {
      TestModel _context = new TestModel();
      Order _order = null;
    
      #region IRepository Members
    
      public Order Get(string orderNumber)
      {
       int id = Convert.ToInt32(orderNumber);
       _order = _context.Orders.SingleOrDefault(o => o.Id == id);
       return _order;
      }
    
      public void Add(Order order)
      {
       throw new System.NotImplementedException();
      }
    
      public void Save()
      {
       throw new System.NotImplementedException();
      }
    
      #endregion
    
     }
    
    public interface IRepository<TEntity>
     {
      TEntity Get(string Id);
      void Add(TEntity entity);
      void Save();
     }
    

    Tests:

     

      [TestMethod]
      public void CanGetOrderClient()
      {
       var orderRepository = RepositoryFactory.Get<Order>();
       Order order = orderRepository.Get("100");
       Assert.IsNotNull(order);
       Assert.IsNotNull(order.Client); //this fails
      }
    
      [TestMethod]
      public void CanGetClient()
      {
       var clientRepository = RepositoryFactory.Get<Client>();
       Client client = clientRepository.Get("1");
       Assert.IsNotNull(client);
      }
    
    Thanks
    William
    Thursday, November 18, 2010 11:17 PM
  • Hi William,

    Those two tests pass for me, can you confirm that the Order.Client property is marked as virtual in your code?

    Here is a little standalone console app that shows lazy loading working with the same classes and config, any chance you can run this and make sure it works for you? If it passes the only thing I can think of is that Order 100 doesn't have a Client assigned in the database?

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.ModelConfiguration;
    using System.Diagnostics;
    
    namespace LazyLoadRepro
    {
      class Program
      {
        static void Main(string[] args)
        {
          int orderId;
          using (var ctx = new TestModel())
          {
            var order = new Order
            {
              DateOrdered = DateTime.Today,
              Client = new Client { Name = "Bob" }
            };
            ctx.Orders.Add(order);
            ctx.SaveChanges();
    
            orderId = order.Id;
          }
    
          using (var ctx = new TestModel())
          {
            var order = ctx.Orders.Find(orderId);
            Debug.Assert(order.Client != null);
          }
        }
      }
    
      public class TestModel : DbContext
      {
        public DbSet<Order> Orders { get; set; }
        public DbSet<Client> Clients { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
          modelBuilder.IncludeMetadataInDatabase = false;
    
    
          modelBuilder.Entity<Client>()
           .HasKey(client => client.Id)
           .MapSingleType(client => new
           {
             id = client.Id,
             name = client.Name
    
           }).ToTable("customer");
    
          modelBuilder.Entity<Client>().HasMany(client => client.Orders);
    
          modelBuilder.Entity<Order>()
           .HasKey(order => order.Id)
           .MapSingleType(order => new
           {
             id = order.Id,
             customerid = order.Client.Id,
             orderdate = order.DateOrdered
           }).ToTable("orderrecord");
    
          modelBuilder.Entity<Order>().HasRequired(order => order.Client);
        }
      }
    
      public class Order
      {
        public int Id { get; set; }
        public DateTime DateOrdered { get; set; }
        public virtual Client Client { get; set; }
      }
    
      public class Client
      {
        public int Id { get; private set; }
        public string Name { get; set; }
        public virtual IList<Order> Orders { get; set; }
      }
    }
    

     

    ~Rowan

    Friday, November 19, 2010 6:26 PM
    Moderator
  • Hi Rowan,

    Thanks for the reply. I tested your sample and it works perfectly. This helped me trouble shoot my code. I discovered that my Order class had a private default constructor. Unfortunately I ommited that in the second post I made.

    I can reproduce the problem in your sample if change it as follows:

        static void Main(string[] args)
        {
    
          using (var ctx = new TestModel())
          {
            var order = ctx.Orders.Find(100);
            Debug.Assert(order.Client != null);
          }
        }
    

    and then make the Order's default contructor private:

      public class Order
      {
        private Order() { }
        public int Id { get; set; }
        public DateTime DateOrdered { get; set; }
        public virtual Client Client { get; set; }
      }
    

    Whats strange to me is that having the orders default constructor set to private affects the lazy loading of one of its properties. At this stage I just want to get a better understanding of why that is the case. Any insights would be appreciated.

    Thanks for your time on this so far.

    William

    Friday, November 19, 2010 7:33 PM
  • Hi William,

    The private constructor is an issue because EF creates a proxy that derives from your type in order to perform lazy loading (we override your navigation property getter and insert some logic to load on first read).

    It's not possible to derive from a class with only private constructors, if you make the constructor protected you should be good to go.

    ~Rowan

    Friday, November 19, 2010 11:11 PM
    Moderator
  • Thanks. That helps.
    Friday, November 19, 2010 11:42 PM