Search code examples
c#ef-core-2.0

Why reference properties works only through context


I have two classes Order and OrderDetail:

  public class Order : Entity
{
    public Order(KitchenAppContext context) : base(context)
    {

    }
    public Order() : base()
    {

    }
    public DateTime Date { get; set; }
    public Guid MenuId { get; set; }
    public virtual Menu Menu { get; set; }
    public bool IsClosed { get; set; }
    public decimal Price { get; set; }
    public virtual int PeopleCount { get { return Details.Count; } }
    public virtual List<OrderDetail> Details { get; set; } = new List<OrderDetail>();
}

 public class OrderDetail : Entity
{
    public OrderDetail(KitchenAppContext context) : base(context)
    {

    }

    public OrderDetail() : base()
    {

    }
    public Guid UserId { get; set; }
    public virtual User User { get; set; }
    public virtual List<PaymentDetail> Payments { get; set; } = new List<PaymentDetail>();
    public virtual Order Order { get; set; }
    public Guid OrderId { get; set; }
}

They are mapped like this:

  void OrderMapping(ModelBuilder builder)
    {
        var etBuilder = builder.Entity<Order>();
        etBuilder.HasKey(m => new { m.Id });
        etBuilder.HasOne(o => o.Menu).WithMany(a => a.Orders).HasForeignKey(f => f.MenuId);
        etBuilder.HasMany(o => o.Details).WithOne(d => d.Order).HasForeignKey(f => f.OrderId);
    }

  void OrderDetailMapping(ModelBuilder builder)
    {
        var etBuilder = builder.Entity<OrderDetail>();
        etBuilder.HasKey(m => new { m.Id });
        etBuilder.HasOne(o => o.User).WithMany(u => u.Details).HasForeignKey(f => f.UserId);
        etBuilder.HasOne(o => o.Order).WithMany(u => u.Details).HasForeignKey(f => f.OrderId);
        etBuilder.HasMany(o => o.Payments).WithOne(d => d.OrderDetail).HasForeignKey(f => f.OrderDetailId);
    }

When I create order and order details :

  var order = new Order(Context);
        Context.Orders.Add(order);
        var oderDetail = new OrderDetail(Context) { Order = order };

Details of order empty there and OrderId of orderdetails is null also. When I add created order detail in context then it will be added to Details and OrderId becomes Id of created order. Why it works only when I add it to context? I want that it works without adding it to context. Maybe, I should do something in consctructor of classes (with Context parameter)? How can I do this?

EDIT: Order and OrderDetails classes inherited from abstact class Entity:

 public abstract class Entity
{
    Guid id;
    public Guid Id
    {
        get
        {
            if (id == null || id == Guid.Empty)
            {
                id = Guid.NewGuid();
            }
            return id;
        }
        set
        {
            id = value;
        }
    }

    public Entity(KitchenAppContext context)
    {
        Context = context;
    }

    public Entity()
    {

    }
    public MainContext Context;
}

Also, as you see, I have constructor without parameter. I created them because EF shows this message when I'm getting entities from context:

System.InvalidOperationException: 'A parameterless constructor was not found on entity type 'Order'. In order to create an instance of 'Order' EF requires that a parameterless constructor be declared.'

How can I avoid this error without creating conscrtuctors without parameter?


Solution

  • I solved second issue

    A parameterless constructor was not found...

    like this:

    1. I set the default constructor of entity class and sub entities as protected
    2. When I load entity from DbContext property of entities will be null, because EF uses default constructor. That's why I created my own IQueryable collection. It sets context property when it's not set:

    Code:

    class IContextable<T> : IQueryable<T> where T : Entity
    {
        public IQueryable<T> SourceQuery { get; set; }
        public KitchenAppContext Context { get; set; }
    
        public IContextable(IQueryable<T> query, KitchenAppContext context)
        {
            SourceQuery = query;
            Context = context;
        }
    
        public Type ElementType => SourceQuery.ElementType;
        public Expression Expression => SourceQuery.Expression;
        public IQueryProvider Provider => SourceQuery.Provider;
    
        public IEnumerator<T> GetEnumerator()
        {
            foreach (var entity in SourceQuery)
            {
                if (entity.Context == null || entity.Context != Context)
                    entity.Context = Context;
    
                yield return entity;
            }
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
    

    And my GetEntities method in my Context class:

    public IQueryable<T> GetEntities<T>() where T : Entity
    {
        IQueryable<T> query = Set<T>();
        return new IContextable<T>(query, this);
    }
    

    Maybe, there was better ways, but I couldn't found them. It works now, but I'm still waiting for good answer