Mark Bell
Mark Bell

Reputation: 29735

EF Code First: How do I make a virtual collection private while still having it correctly create my database model?

I am using Code First to automatically generate my database, and this works perfectly, generating an Orders table and an OrderLines table as expected when I add some test data.

I have the following Order class:

public class Order
{
    public int OrderID { get; set; }

    public void AddItem(string productCode, int quantity)
    {
        var existingLine = OrderLines.FirstOrDefault(x => x.ProductOption.ProductCode == item.ProductCode);

        if (existingLine == null)
            OrderLines.Add(new OrderLine { ProductOption = item, Quantity = quantity });
        else
            existingLine.Quantity += quantity;
    }

    public void RemoveItem(string productCode)
    {
        OrderLines.Remove(OrderLines.Where(x => x.ProductOption.ProductCode == productCode).FirstOrDefault());
    }

    public virtual ICollection<OrderLine> OrderLines { get; set; }

    public Order()
    {
        OrderLines = new List<OrderLine>();
    }
}

What I really want is to encapsulate the OrderLines collection, making it impossible for consumers of the class to directly add and remove items to/from it (using the Add / Remove methods of ICollection) and instead forcing them to use my custom AddItem and RemoveItem methods.

Normally I could just make the collection private, but I can't do that because it needs to be virtual for EF to correctly create the OrderLines table/foreign keys.

This answer seems to suggest that making the property internal would do the trick, but I tried, and in that case no OrderLines table is created.

Is there any way that this can be accomplished, or should I have designed this differently somehow? Any help much appreciated!

Update

After a bit more searching, I found this question which is rather more clearly stated than mine; however, it's still unanswered. The poster does link to this post which seems to suggest it can't really be done in the way I'm thinking of, but does anyone have any more up-to-date information?

Upvotes: 3

Views: 1289

Answers (3)

Well what you can do is set your collection as private and make the relationship using fluent API in the OnModelCreating, as shown below, I don't know if this will work, just make a try:

public class YourContext : DbContext
{
   public DbSet<Order> Orders { get; set; }
   public DbSet<OrderLine> OrderLines { get; set; }

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
       modelBuilder.Entity<Order>() 
           .HasMany(o => o.OrderLines) 
           .WithRequired(l => l.OrderId) 
           .HasForeignKey(l => l.OrderId);

   }
}

This will make your OrderLines as readonly:

public class YourContext : DbContext
{
   public DbSet<Order> Orders { get; set; }

   public DbSet<OrderLine> OrderLines 
   { 
      get { return set<OrderLine>(); }
   }
}

I hope this can help you, please take a look a this blog post: EF Feature CTP5: Fluent API Samples

Upvotes: 0

KallDrexx
KallDrexx

Reputation: 27803

I don't know if it's possible to do what you are asking or not, but I'm not sure it's the best design. The problem that I am seeing is you are firmly integrating your business logic into your business entities, and I think this will turn into confusion down the road.

Take the following scenario under consideration. Say you have a new requirement where you want users to be able to remove all items from an order. The only way to do it with your entity is to create a new RemoveAllItems() method to your Order class which does that. Now say you have a new requirement to Remove all items from an order that are in a specific category. That then means that you have to add yet another method.

This causes really bloated classes, and there is one major issue you will come up with. If you (or another developer) want to look at an entity and determine it's data structure, you can't at a glance because it's so intertwined with business logic.

What I would suggest is that you keep your entities as pure data structures, keeping all their relationships public. Then you need to create a service layer, which can consist of small or big classes (however you want to organize them) that actually perform the business functions. So for example, you can have a OrderItemService class, which has methods for adding, editing, and removing items from an order. All your business logic is performed in this class, and you just have to enforce that only service classes are allowed to interact with db entities.

Now, if you are looking for how a particular business process is performed, you know to look in the service layer classes, and if you want to look at how a data structure or entity is organized, you look at the entity. This keeps everything clean and very mantainable.

Upvotes: 1

lancscoder
lancscoder

Reputation: 8768

I'm far from an expert on code first and I haven't tried the following but is it possible to use the ReadOnlyCollectionBase and create a read only list similar to this MSDN article?

Upvotes: 0

Related Questions