Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why there's no AddRange/RemoveRange method in IDbSet interface in Entity 6? [closed]

Tags:

In Entity Framework 6 AddRange method has been introduced. It's great for big inserts because DbSet.Add method always trigger DetectChanges which extremely slows down the process. I've just wanted to use some existing code based on IDbSet interface when realized that it doesn't have AddRange method. It exists only in DbSet class.

I googled a little bit and found this discussion - http://forums.asp.net/t/1978828.aspx?Why+is+there+no+AddRange+method+for+System+Data+Entity+IDbSet+T+ - but there's no clear conclusion about the reason why actually AddRange method does not exist in IDbSet interface.

Is it a bug or is there some good reason for it not to be there? Any ideas?

UPDATE

Here https://entityframework.codeplex.com/workitem/2781 Microsoft gave me an answer:

This is by design. The interface approach wasn't a good one for DbSet because adding members breaks any existing applications that implement the interface.

Given we want to be able to add members to DbSet, we swapped to a base class approach where DbSet is a base class that you can directly mock or inherit.

Here are some links that show how to use DbSet rather than IDbSet:

https://msdn.microsoft.com/en-us/data/dn314429

https://msdn.microsoft.com/en-us/data/dn314431

like image 325
Arkadiusz Kałkus Avatar asked Jul 21 '15 13:07

Arkadiusz Kałkus


2 Answers

From the Entity Framework Design Meeting Notes, on May 16, 2013:

The team recognized the potential for breaking changes:

The DbSet classes (generic and non-generic) inherit from a (generic or non-generic) IDbSet interface. IDbSet is intended only for creating test doubles, be these mocks or fakes.

However, in EF6 DbSet has changed in four ways that if reflected in equivalent changes for IDbSet would be breaking changes:

  • FindAsync added
  • AddRange/RemoveRange added
  • Local return type changed to DbLocalView (this change may be reverted anyway)

They discussed a bunch of potential changes in detail, but ultimately decided to avoid the breaking change, and to "make DbSet more mockable":

The decision was to make DbSet more mockable. However, we will not obsolete IDbSet because this would create work for those currently using IDbSet who don’t need to use the new members. We will add guidance to IDbSet indicating that using DbSet is the way to go for new code and, depending on feedback, we may choose to obsolete IDbSet in a future release.

And if you look at the code for IDbSet, they added comments to the top of the interface:

IDbSet was originally intended to allow creation of test doubles (mocks or fakes) for DbSet. However, this approach has issues in that adding new members to an interface breaks existing code that already implements the interface without the new members.

Therefore, starting with EF6, no new members will be added to this interface and it is recommended that DbSet be used as the base class for test doubles.

like image 181
Grant Winney Avatar answered Nov 10 '22 11:11

Grant Winney


This is definitely not a bug and is done like this by design. It is hard to answer this type of question without being a developer on the .NET library but my guess is that they wanted to keep interfaces simple. Maybe someone on the .NET team will see this and chime in. Backwards compatibility issues will be tied to the interface/concrete implementation, which is also definitely a possibility in this scenario. However, this reasoning probably will not carry over to why IList/ICollection does not define those.

One argument is that by adding AddRange (as well as RemoveRange/InsertRange) to the interface, you are forcing the implementer to define those methods. Interfaces should be simple and easy to define. AddRange, etc methods should really only exist on concrete collections where you can see performance improvements. For example, a List can optimize its AddRange by properly increasing its internal capacity, etc. Where a simple loop over items and calling Add maybe be more costly (e.g. via an Extension method).

They probably do this for the same reason IList, ICollection, etc do not have an AddRange on the interface directly but do on the concrete implementations.

There is a workaround for this, and that is to abstract both the interface and the concrete class using your own interface/class. You can give your interface an AddRange and extend DBSet/implement your interface in another class. This may or may not work depending on how you are using IDBSet/DBSet in your code. Although, a little refactoring can make this work. Something like this --

public class MyDbSet<TEntity> : DbSet<TEntity>, IMyDbSet<TEntity> where TEntity : class { }  public interface IMyDbSet<TEntity> : IDbSet<TEntity> where TEntity : class {     IEnumerable<TEntity> AddRange(IEnumerable<TEntity> items); } 

Now, you can just use IMyDbSet in your code. No need to implement AddRange as it is already implemented by extending DbSet. External methods that only accept IDbSet/DbSet should still accept MyDbSet/IMyDbSet.

like image 38
Tom Avatar answered Nov 10 '22 11:11

Tom