How can avoid repeat same where in Entity Framework - asp.net-mvc

In my project I don't use a physical delete, I just use logical "soft delete" for all tables.
I use this clause for all queries:
.Where(row => row.IsDeleted == false)
I want a way to avoid to repeat this where clause in all queries.
I have method like this to get data :
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
and I call it like this :
_categories = _uow.Set<Category>();
How can I do this ?
First Idea:
Add a base class and put Deleted column in that and inherit all classes from this base class. Is this a good way?
I use UnitOfWork and code-first.

I would not be creating a base class only for the sake of reuse a single property. Instead, I would create an interfase and an extension method to encapsulate and reuse the where statement. Something like the following:
public static class EntityFrameworkExtentions
{
public static ObservableCollection<TEntity> Alive<TEntity>(this DbSet<TEntity> set)
where TEntity : class, ISoftDeleteAware
{
var data = set.Where(e => e.IsDeleted == false);
return new ObservableCollection<TEntity>(data);
}
}
The interface declaration
public interface ISoftDeleteAware
{
bool IsDeleted { get;set;}
}
Usage:
var coll = DbContext.Categories.Alive();

public class GenericRepository<TEntity> where TEntity : class
{
internal MyContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(MyContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IQueryable<TEntity> GetNonDeleted(Expression<Func<TEntity, bool>> filter = null)
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
query = query.Where(row => row.IsDeleted == false);
return query;
}
// Other methods
}

If you never delete anything, then you can use base entity class and IsDelete property with it. After, use this base entity for Generic Repository. It looks like;
public abstract class BaseModel
{
public BaseModel()
{
IsDelete = false;
CreateDate = DateTime.Now;
}
[Key]
public int Id { get; set; }
public bool IsDelete{ get; set; }
public virtual DateTime CreateDate { get; set; }
public virtual DateTime UpdateDate { get; set; }
}
public class YourClassHere : BaseModel
{
//
}
public class Repository<T> : IRepository<T> where T : BaseModel
{
private readonly IDbContext _context;
private IDbSet<T> _entities;
public Repository(IDbContext context)
{
this._context = context;
}
public T GetByIdByIgnoringDeleteStatus(int id)
{
return this.Entities.Find(id);
}
public T GetById(int id)
{
return this.Entities.Single(item => item.Id == id && !item.IsDelete);
}
public void Create(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
entity.CreateDate = DateTime.Now;
this.Entities.Add(entity);
//this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
var msg = string.Empty;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
msg += string.Format("Property: {0} Error: {1}",
validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine;
}
}
var fail = new Exception(msg, dbEx);
throw fail;
}
}
public void Update(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
entity.UpdateDate = DateTime.Now;
this._context.SetModified(entity);
//this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
var msg = string.Empty;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}",
validationError.PropertyName, validationError.ErrorMessage);
}
}
var fail = new Exception(msg, dbEx);
throw fail;
}
}
public void Delete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
entity.UpdateDate = DateTime.Now;
this.Entities.Remove(entity);
//this._context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
var msg = string.Empty;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}",
validationError.PropertyName, validationError.ErrorMessage);
}
}
var fail = new Exception(msg, dbEx);
throw fail;
}
}
public virtual IQueryable<T> GetAll()
{
return this.Entities;
}
private IDbSet<T> Entities
{
get
{
if (_entities == null)
{
_entities = _context.Set<T>();
}
return _entities;
}
}
}
Now, you can use your class methods with Delete Status.

Related

xUnit with Moq. Test CRUD operation (generic dataservice) Entity Framework Core 6

I am trying to test my project using ef 6. here is the situation: I have the mold class and have some generic services like GetAll,GetById,Create,Update, Delete. trying to test these methods using xUnit. Stuck in mocking.
public partial class TblPtsVMold :IMoldObject
{
public int MoldId { get; set; }
public string? MoldType { get; set; }
public string? Plant { get; set; }
}
public partial class PTSProdContext : DbContext
{
private IConfiguration _configuration { get; set; }
public PTSProdContext()
{
}
public PTSProdContext(DbContextOptions<PTSProdContext> options)
: base(options)
{
}
public virtual DbSet<TblPtsVMold> TblPtsVMolds { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer
(_configuration.GetConnectionString
(_configuration["EnvionmentString:ENVIRONMENT"]));
}
}
}
public class PTSContextFactory
{
public string DbConnectionString { get; set; }
public PTSContextFactory(string dbConnectionString)
{
DbConnectionString = dbConnectionString;
}
public PTSProdContext CreateDbContext()
{
DbContextOptionsBuilder<PTSProdContext> options = new DbContextOptionsBuilder<PTSProdContext>();
options.UseSqlServer(DbConnectionString);
return new PTSProdContext(options.Options);
}
public interface IDataService<T>
{
IEnumerable<T> GetAll();
IEnumerable<T> GetAllWithID(List<int> Ids);
T Get(int id);
T Create(T entity);
}
generic dataservice class
public class GenericDataService<T> : IDataService<T> where T : class, IDomainObject
{
public readonly PTSContextFactory _contextFactory;
public GenericDataService(PTSContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
public GenericDataService()
{
}
public IEnumerable<T> GetAllWithID(List<int> Ids)
{
using (PTSProdContext context = _contextFactory.CreateDbContext())
{
IEnumerable<T> entities = context.Set<T>().Where(e => Ids.Contains(e.Id)).ToList();
if (entities == null)
{
WeakReferenceMessenger.Default.Send(new LookupExceptionMessage(string.Format("Can't find record.")));
return default;
}
return entities;
}
}
public IEnumerable<T> GetAll()
{
using (PTSProdContext context = _contextFactory.CreateDbContext())
{
IEnumerable<T> results = context.Set<T>().ToList();
if (results == null)
{
return default;
}
return results;
}
}
Test:
[Fact]
public void GetAll_TestClassObjectPassed_ProperMethodCalled()
{
// Arrange
var testObject = new TblPtsVMold() { Id = 1 };
var testList = new List<TblPtsVMold>() { testObject };
var data = new List<TblPtsVMold>
{
new TblPtsVMold { MoldType = "test",Plant="testpass1" },
new TblPtsVMold { MoldType = "ZZZ",Plant="testpass2" },
new TblPtsVMold { MoldType = "AAA",Plant="testpass3" },
}.AsQueryable();
var dbSetMock = new Mock<DbSet<TblPtsVMold>>();
dbSetMock.As<IQueryable<TblPtsVMold>>().Setup(x => x.Provider).Returns(testList.AsQueryable().Provider);
dbSetMock.As<IQueryable<TblPtsVMold>>().Setup(x => x.Expression).Returns(testList.AsQueryable().Expression);
dbSetMock.As<IQueryable<TblPtsVMold>>().Setup(x => x.ElementType).Returns(testList.AsQueryable().ElementType);
dbSetMock.As<IQueryable<TblPtsVMold>>().Setup(x => x.GetEnumerator()).Returns(testList.AsQueryab`your text`le().GetEnumerator());
var context = new Mock<PTSProdContext>();
context.Setup(x => x.Set<TblPtsVMold>()).Returns(dbSetMock.Object);
// Act
var context = new Mock<PTSContextFactory>();
context.Setup(x => x.DbConnectionString).Returns(string.Empty);
var service = new GenericDataService<TblPtsVMold>(context.Object);
var result = service.GetAll();
// Assert
Assert.Equal(testList, result.ToList());
}
context.Setup(x => x.DbConnectionString).Returns(string.Empty); is not correct.
getting error:pportedException : Unsupported expression: x => x.DbConnectionString Non-overridable members (here: PTSContextFactory.get_DbConnectionString) may not be used in setup verification expressions.
How to test GetAll or create method. Is anything wrong here?

"The INSERT statement conflicted with the FOREIGN KEY" exception downs full system for all users

I have an asp.net mvc web application where I am getting
"The INSERT statement conflicted with the FOREIGN KEY"
This is not the main problem. I know why I am getting this exception. but the problem is it downs the full system for all users.
My Repository Interface:
public interface IRepository<TEntity> : IDisposable where TEntity : class
{
IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
TEntity GetByID(int id);
int Insert(TEntity entity);
int Delete(int id);
int Delete(TEntity entityToDelete);
int Update(TEntity entityToUpdate);
DbRawSqlQuery<TEntity> SQLQuery<TEntity>(string sql, params object[] parameters);
int Save();
}
Repository Implementation:
public class Repository<TEntity> : IRepository<TEntity>, IDisposable where TEntity : class
{
internal DbContext context;
internal DbSet<TEntity> dbSet;
public Repository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
try
{
IQueryable<TEntity> query = dbSet.AsNoTracking();
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
catch (Exception)
{
return null;
}
}
public virtual TEntity GetByID(int id)
{
try
{
return dbSet.Find(id);
}
catch (Exception)
{
return null;
}
}
public virtual int Insert(TEntity entity)
{
try
{
dbSet.Add(entity);
return 1;
}
catch (Exception ex)
{
return 0;
}
}
public virtual int Delete(int id)
{
try
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
return 1;
}
catch (Exception ex)
{
return 0;
}
}
public virtual int Delete(TEntity entityToDelete)
{
try
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
return 1;
}
catch (Exception ex)
{
return 0;
}
}
public virtual int Update(TEntity entityToUpdate)
{
try
{
context.Set<TEntity>().AddOrUpdate(entityToUpdate);
return 1;
}
catch (Exception ex)
{
return 0;
}
}
public DbRawSqlQuery<TEntity> SQLQuery<TEntity>(string sql, params object[] parameters)
{
try
{
var result = context.Database.SqlQuery<TEntity>(sql, parameters);
return result;
}
catch (Exception ex)
{
throw ex;
}
}
public int Save()
{
try
{
context.SaveChanges();
return 1;
}
catch (Exception ex)
{
//foreach (var entry in context.ChangeTracker.Entries())
//{
// switch (entry.State)
// {
// case EntityState.Modified:
// case EntityState.Deleted:
// entry.State = EntityState.Modified; //Revert changes made to deleted entity.
// entry.State = EntityState.Unchanged;
// break;
// case EntityState.Added:
// entry.State = EntityState.Detached;
// break;
// }
//}
//return 0;
throw ex;
}
//}
}
#region Disposal Methods
// Dispose Methods
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Repository()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// free other managed objects that implement
// IDisposable only
}
// release any unmanaged objects
// set the object references to null
_disposed = true;
}
#endregion
}
Dependency Injection Class:
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
container.RegisterType(typeof(IRepository<>), typeof(Repository<>), new InjectionConstructor(new DbContext("PLLEntities")));
container.RegisterType(typeof(IBaseService<>), typeof(BaseService<>));
container.RegisterType<IErrorEngine, ErrorEngine>();
container.RegisterType<ILoggingEngine, LoggingEngine>();
container.RegisterType<IIdentityManager, IdentityManager>();
Can't figure it out where is the main issue is.
The problem is most probably related to missing Primary Key definition. Check the related entity and be sure that you defined primary key using [Key] attribute in the entity class. Otherwise you encounter "The INSERT statement conflicted with the FOREIGN KEY constraint ..." error.
public class YourEntity
{
[Key]
public int Id { get; set; }
//other properties
}

error 'The operation cannot be completed because the DbContext has been disposed.'

I am using generic repository and unit of work in asp.net MVC. when I want to access the context in controller I get this error:
'The operation cannot be completed because the DbContext has been disposed.'
however when I comment this line:
'Database.SetInitializer(new MigrateDatabaseToLatestVersion());'
in Global.asax the error will fix. but as soon as i uncomment that line the error still happening.
here is generic repository:
public class GenericRepository<TEntity> where TEntity : class
{
internal ZarinParse_Context context;
internal DbSet<TEntity> dbSet;
public GenericRepository(ZarinParse_Context context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters)
{
return dbSet.SqlQuery(query, parameters).ToList();
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entity)
{
if (context.Entry(entity).State == EntityState.Detached)
{
dbSet.Attach(entity);
}
dbSet.Remove(entity);
}
}
and unit of work:
public class UnitOfWork : IDisposable
{
private ZarinParse_Context context;
private RoleRepository roleRepository;
private UserRepository userRepository;
private UserRoleRepository userRoleRepository;
public UnitOfWork()
{
context = new ZarinParse_Context();
}
public RoleRepository RoleRepository
{
get
{
if (this.roleRepository == null)
{
this.roleRepository = new RoleRepository(context);
}
return roleRepository;
}
}
public UserRepository UserRepository
{
get
{
if (this.userRepository == null)
{
this.userRepository = new UserRepository(context);
}
return userRepository;
}
}
public UserRoleRepository UserRoleRepository
{
get
{
if (this.userRoleRepository == null)
{
this.userRoleRepository = new UserRoleRepository(context);
}
return userRoleRepository;
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
RoleRepository Class:
public class RoleRepository : GenericRepository<Role>
{
public RoleRepository(ZarinParse_Context context)
: base(context)
{
}
}
my Context:
public class ZarinParse_Context : DbContext
{
public IDbSet<User> Users { get; set; }
public IDbSet<Role> Roles { get; set; }
public IDbSet<UserRole> UserRoles { get; set; }
public IDbSet<Product> Products { get; set; }
public IDbSet<Tag> Tags { get; set; }
public IDbSet<TagProduct> TagProducts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new UserConfig());
modelBuilder.Configurations.Add(new RoleConfig());
modelBuilder.Configurations.Add(new UserRoleConfig());
modelBuilder.Configurations.Add(new ProductConfig());
modelBuilder.Configurations.Add(new TagConfig());
modelBuilder.Configurations.Add(new TagProductConfig());
}
}
my Controller:
public class HomeController : Controller
{
UnitOfWork unitOfWork = new UnitOfWork();
public ActionResult Index()
{
var model = new Role() { Name = "User" };
unitOfWork.RoleRepository.Insert(model);
unitOfWork.Save();
return View();
}
protected override void Dispose(bool disposing)
{
unitOfWork.Dispose();
base.Dispose(disposing);
}
}
and Global.asax File:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ZarinParse_Context, Configuration>());
}
}
and Configuration :
public class Configuration : DbMigrationsConfiguration<ZarinParse_Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
ContextKey = "ZarinParse.DomainClasses.Context.ZarinParse_Context";
}
protected override void Seed(ZarinParse_Context context)
{
InitializeAdmin(context: context,
roleName: "Admin",
userName: "soheil91141121#gmail.com",
password: "stb_91141121",
fullName: "سهیل تقی زاده بنگر",
mobileNumber: "09356336121",
newsEamilEnabled: true);
}
private void InitializeAdmin(ZarinParse_Context context, string roleName, string userName, string password, string fullName, bool newsEamilEnabled, string mobileNumber)
{
//Create Role Admin if it does not exist
var roleExist = context.Roles.Any(p => p.Name == roleName);
if (!roleExist)
{
var role = Role.Create();
role.Name = roleName;
context.Roles.Add(role);
context.SaveChanges();
}
//Create User if it does not exist
var userExist = context.Users.Any(p => p.UserName == userName);
if (!userExist)
{
var user = User.Create();
user.UserName = userName;
user.Email = userName;
user.Password = password.Encrypt();
user.FullName = fullName;
user.NewsEmailEnabled = newsEamilEnabled;
user.MobileNumber = mobileNumber;
context.Users.Add(user);
context.SaveChanges();
}
//Create UserRole if it does not exist
var role2 = context.Roles.Single(p => p.Name == roleName);
var user2 = context.Users.Single(p => p.UserName == userName);
var userRoleExist = context.UserRoles.Any(p => p.RoleId == role2.RoleId && p.UserId == user2.UserId);
if (!userRoleExist)
{
var userRole = UserRole.Create();
userRole.Role = role2;
userRole.User = user2;
context.UserRoles.Add(userRole);
context.SaveChanges();
}
context.Dispose();
}
}
The last line of InitializeAdmin disposes a context that is passed into the method.
Only dispose of objects that you have created, since you own the object's lifetime.
In other words, don't dispose of objects you don't own.

Proper disposal of repository object inside controller

I am having a hard time figuring this out. I have implemented Repository and Unit of Work patterns as a data access abstraction for my database. I am using Dependency Injection to inject them into the controller. The problem is when the controller is disposed, it disposes the repository, but then when I refresh the page, DbContext object inside repository is null (it shouldn't be, I think). The implementation of classes is shown below.
This is the database Repository class:
public class ConferenceCrawlyDbRepository : IConferenceCrawlyDbRepository
{
private ConferenceCrawlyDbContext _context = null;
private static ConferenceCrawlyDbRepository _instance = null;
public IRepository<AcademicDegree> AcademicDegrees { get; private set; }
public IRepository<Conference> Conferences { get; private set; }
public IRepository<ConferenceComment> ConferenceComments { get; private set; }
public IRepository<ConferenceRating> ConferenceRatings { get; private set; }
public IRepository<ConnectionConferenceRecommendation> ConnectionConferenceRecommendation { get; private set; }
public IRepository<Country> Countries { get; private set; }
public IRepository<ImportantDate> ImportantDates { get; private set; }
public IRepository<Topic> Topics { get; private set; }
public IRepository<UserProfile> UserProfiles { get; private set; }
public IRepository<UserProfileSettings> UserProfileSettings { get; private set; }
public IRepository<UsersConnection> UserConnections { get; private set; }
public IRepository<Venue> Venues { get; private set; }
private ConferenceCrawlyDbRepository(ConferenceCrawlyDbContext context)
{
_context = context;
AcademicDegrees = new Repository<AcademicDegree>(context);
Conferences = new Repository<Conference>(context);
ConferenceComments = new Repository<ConferenceComment>(context);
ConferenceRatings = new Repository<ConferenceRating>(context);
ConnectionConferenceRecommendation = new Repository<ConnectionConferenceRecommendation>(context);
Countries = new Repository<Country>(context);
ImportantDates = new Repository<ImportantDate>(context);
Topics = new Repository<Topic>(context);
UserProfiles = new Repository<UserProfile>(context);
UserProfileSettings = new Repository<UserProfileSettings>(context);
UserConnections = new Repository<UsersConnection>(context);
Venues = new Repository<Venue>(context);
}
public static ConferenceCrawlyDbRepository Instance
{
get
{
if (_instance == null)
_instance = new ConferenceCrawlyDbRepository(new ConferenceCrawlyDbContext());
return _instance;
}
}
public bool CommitChanges()
{
try
{
return _context.SaveChanges() > 0;
}
catch
{
// TODO: Add logging
return false;
}
}
public void Dispose()
{
if (_context != null)
_context.Dispose();
}
And this is the generic repository class:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private ConferenceCrawlyDbContext _context = null;
private DbSet<TEntity> _dbSet = null;
public Repository(ConferenceCrawlyDbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
public DbSet<TEntity> DbSet
{
get
{
return _dbSet;
}
}
public bool Add(TEntity entity)
{
try
{
DbSet.Add(entity);
return true;
}
catch (Exception ex)
{
// TODO: Add logging
return false;
}
}
public bool Update(TEntity entity)
{
try
{
var entry = _context.Entry<TEntity>(entity);
DbSet.Attach(entity);
entry.State = EntityState.Modified;
return true;
}
catch(Exception ex)
{
// TODO: Add logging
return false;
}
}
public bool Remove(TEntity entity)
{
try
{
DbSet.Remove(entity);
return true;
}
catch (Exception ex)
{
// TODO: Add logging
return false;
}
}
public IQueryable<TEntity> GetAll()
{
return DbSet;
}
public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Where(predicate);
}
public TEntity FindById(params object[] keys)
{
return DbSet.Find(keys);
}
public bool Contains(Expression<Func<TEntity, bool>> predicate)
{
return _dbSet.Any(predicate);
}
public bool CommitChanges()
{
try
{
return _context.SaveChanges() > 0;
}
catch
{
// TODO: Add logging
return false;
}
}
public void Dispose()
{
if (_context != null)
_context.Dispose();
}
}
Here is my custom controller factory for dependency injection:
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType != null)
{
if (controllerType.GetConstructor(new Type[] { typeof(IConferenceCrawlyDbRepository) }) != null)
return Activator.CreateInstance(controllerType, new object[] { ConferenceCrawlyDbRepository.Instance }) as Controller;
else if (controllerType.GetConstructor(new Type[] { typeof(IConferenceCrawlyDbRepository), typeof(IMailService) }) != null)
return Activator.CreateInstance(controllerType, new object[] { ConferenceCrawlyDbRepository.Instance,
#if DEBUG
MockMailService.Instance
#else
MailService.Instance
#endif
}) as Controller;
}
return base.GetControllerInstance(requestContext, controllerType);
}
And finally, controller with its action:
public class HomeController : Controller
{
private IConferenceCrawlyDbRepository _dbRepository;
private IMailService _mailService;
public HomeController(IConferenceCrawlyDbRepository dbRepository, IMailService mailService)
{
_dbRepository = dbRepository;
_mailService = mailService;
}
//
// GET: /
public ActionResult Index()
{
var countries = _dbRepository.Countries.GetAll().ToList();
return View(countries);
}
//
// GET: /Home/About
public ActionResult About()
{
return View();
}
//
// GET: /Home/Contact
public ActionResult Contact()
{
return View(new Contact());
}
//
// POST: /Home/Contact
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Contact(Contact contact)
{
if (ModelState.IsValid)
{
if (await _mailService.SendMail(contact.Email, contact.Title,
String.Format("{1}{0}{2}", Environment.NewLine, contact.Message, String.IsNullOrEmpty(contact.Name) ? "Anonimous" : contact.Name)))
{
ViewBag.SuccessMessage = "Comment has been successfully sent.";
return View(new Contact());
}
else
ViewBag.ErrorMessage = "Couldn't send your email. Please try again later";
}
return View(contact);
}
protected override void Dispose(bool disposing)
{
if (_dbRepository != null)
_dbRepository.Dispose();
base.Dispose(disposing);
}
}
The Exception is thrown inside index action when I call the repository, but only after the first time accessing this controller and it says that DbContext object inside repository. I am obviously doing something wrong, but I can't figure it out.
I figure it out after long staring and debugging of code. It's so simple, because I'm disposing context inside ConferenceCrawlyDbRepository I should also set _instance object which is using it to null, and then it will be recreated along with DbContext object. I fell so stupid right now...

An entity object cannot be referenced by multiple instances of IEntityChangeTracker when saving changes

I have two domain objects, both identical but with different PK properties:
public partial class Maintenance : MaintenanceBase
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int MaintenanceId { get; set; }
public virtual Employee Employee { get; set; }
}
public partial class MyMaintenance : MaintenanceBase
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int RowId { get; set; }
public virtual Employee Employee { get; set; }
}
The rest of the properties are inherited from a base class. The problem I am having is when try to call save changes in my post controller, I am getting the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
This is (basically) my controller method:
[HttpPost]
public ActionResult SubmitMyMaintenance(IList<MyMaintenance> myMaintenanceList, string userName)
{
foreach (var result in myMaintenanceList)
{
var m = iMyMaintenanceRepository.GetSingle(result.RowId);
Maintenance maintenance = new Maintenance();
// Use Injector to handle mapping between viewmodel and model
maintenance.InjectFrom(m);
try
{
if (ModelState.IsValid)
{
// save the maintenance item
iMaintenanceRepository.Add(maintenance);
iMaintenanceRepository.Save();
// delete the item in MyMaintenance
iMyMaintenanceRepository.Delete(m);
iMyMaintenanceRepository.Save();
}
}
catch (DataException ex)
{
message = ex.InnerException.ToString();
}
}
// refresh the view
var mvm = new MyMaintenanceListViewModel
{
MyMaintenanceList = iMyMaintenanceRepository.FindBy(v => v.CreatedBy.Equals(userName)).ToList(),
Message = "Your maintenance items were successfully added."
};
return View("MyMaintenance", mvm);
}
I suspect this is because I have instances of respositories (iMaintenanceRepository & iMyMaintenanceRepository) for both domain objects in the same controller post method, and both have a reference to the Employee entity.
For instance, when I dispose the iMyMaintenanceRepository and create a new instance (before refreshing the view at the end), I get en error about inserting a null value in the Employee table, which I am not inserting anything. That is the reason I suspect the Employee entity exists in two different data contexts. I am not sure how to resolve it though. None of the solutions I have found seem to apply and I am thinking it is more of an implementation problem on my part.
EDIT: Repositories
namespace EMMS.Models.Interfaces
{
public interface IMyMaintenanceRepository : IGenericRepository<MyMaintenance>
{
MyMaintenance GetSingle(int RowId);
}
}
namespace EMMS.Models.Repositories
{
public class MyMaintenanceRepository : GenericRepository<AppDBContext, MyMaintenance>, IMyMaintenanceRepository
{
public MyMaintenance GetSingle(int RowId)
{
var query = GetAll().FirstOrDefault(x => x.RowId == RowId);
return query;
}
}
}
namespace EMMS.ViewModels.Repositories
{
public class GenericRepository<C, T> : IDisposable, IGenericRepository<T>
where T : class
where C : DbContext, new()
{
private C _entities = new C();
public C Context
{
get { return _entities; }
set { _entities = value; }
}
public virtual IQueryable<T> GetAll()
{
IQueryable<T> query = _entities.Set<T>();
return query;
}
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _entities.Set<T>().Where(predicate);
return query;
}
// enforce referential itegrity
public bool ValueInUse(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _entities.Set<T>().Where(predicate);
int count = query.Count();
return count > 0 ? true : false;
}
public virtual void Add(T entity)
{
_entities.Set<T>().Add(entity);
}
public virtual void Delete(T entity)
{
_entities.Entry(entity).State = System.Data.EntityState.Deleted;
}
public virtual void Edit(T entity)
{
_entities.Entry(entity).State = System.Data.EntityState.Modified;
}
public virtual void Save()
{
_entities.SaveChanges();
}
private bool disposed = false; // to detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (_entities != null)
{
_entities.Dispose();
}
}
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
namespace EMMS.ViewModels.Interfaces
{
public interface IGenericRepository<T> where T : class
{
IQueryable<T> GetAll();
IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
bool ValueInUse(System.Linq.Expressions.Expression<Func<T, bool>> predicate);
void Add(T entity);
void Delete(T entity);
void Edit(T entity);
void Save();
void Dispose();
}
}
You're absolute correct about the issue. Actually, in particular, it's because each repository has it's own instance of your context object, and you're trying to pass an Employee that was originally retrieved via one instance and saving it via different instance.
The easiest solution is to track all like things in one repository. In other words, just use one MaintenanceRepository can have calls to return both Maintenance and MyMaintenance. Though that stretches the idea of a "repository" a bit. This is why repositories are typically combined with a Unit of Work class, which would house the context for the repositories to share. However, at that point, you're basically just recreating the structure Entity Framework already implements. So, holding everything in just one "repository" makes more sense, but now you're really talking about a "service" pattern rather than a repository pattern. It's just semantics though.
UPDATE
Disclaimer: This is what I'm using currently in a project and it works for me. This may not be the best practice and reasonable people could very well disagree with my approach.
IService Interface
public interface IService<TContext, TEntity>
where TContext : DbContext
where TEntity : class
{
IEnumerable<TEntity> GetAll(
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
TEntity GetById(int id, string includeProperties = "");
TEntity GetOne(
Expression<Func<TEntity, bool>> filter = null,
string includeProperties = "");
TEntity GetFirst(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
TEntity GetLast(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
void Create(TEntity entity);
void Update(TEntity entity);
void Delete(int id);
void Delete(TEntity entity);
int Count(Expression<Func<TEntity, bool>> filter = null);
bool Any(Expression<Func<TEntity, bool>> filter = null);
}
Service, Implementation of IService
public class Service<TContext, TEntity> : IService<TContext, TEntity>
where TContext : DbContext
where TEntity : class
{
internal TContext context;
internal DbSet<TEntity> dbSet;
public Service(TContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> GetAll(
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
return Get(null, orderBy, includeProperties);
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.Distinct().ToList();
}
}
public virtual TEntity GetById(int id, string includeProperties = "")
{
return dbSet.Find(id);
}
public virtual TEntity GetOne(
Expression<Func<TEntity, bool>> filter,
string includeProperties = "")
{
return Get(filter, null, includeProperties).SingleOrDefault();
}
public virtual TEntity GetFirst(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
return Get(filter, orderBy, includeProperties).FirstOrDefault();
}
public virtual TEntity GetLast(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
return Get(filter, orderBy, includeProperties).LastOrDefault();
}
public virtual void Create(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(int id)
{
var entity = GetById(id);
Delete(entity);
}
public virtual void Delete(TEntity entity)
{
if (context.Entry(entity).State == EntityState.Detached)
{
dbSet.Attach(entity);
}
dbSet.Remove(entity);
}
public virtual void Update(TEntity entity)
{
if (context.Entry(entity).State == EntityState.Detached)
{
dbSet.Attach(entity);
}
context.Entry(entity).State = EntityState.Modified;
}
public virtual int Count(Expression<Func<TEntity, bool>> filter = null)
{
return Get(filter).Count();
}
public virtual bool Any(Expression<Func<TEntity, bool>> filter = null)
{
return Count(filter) > 0;
}
}
ServiceGroup, Abstract container for services
public abstract class ServiceGroup<TContext> : IDisposable
where TContext : DbContext
{
protected TContext context;
public virtual void Save()
{
try
{
context.SaveChanges();
}
catch (DbEntityValidationException validationException)
{
string validationErrorMessage = DbEntityValidationMessageParser.GetErrorMessage(validationException);
Console.WriteLine(validationErrorMessage);
}
}
#region Disposable
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public virtual void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
So, the way I use this all is whenever I want to create a collection of like things to work with, I subclass ServiceGroup like so:
public class SampleService : ServiceGroup<MyDbContext>
{
public SampleService()
{
this.context = new MyDbContext();
}
private Service<MyDbContext, SomeModel> someModels;
public Service<MyDbContext, SomeModel> SomeModels
{
get
{
if (someModels == null)
{
someModels = new Service<MyDbContext, SomeModel>(context);
}
return someModels;
}
}
private Service<MyDbContext, AnotherModel> anotherModels;
public Service<MyDbContext, AnotherModel> AnotherModels
{
get
{
if (anotherModels == null)
{
anotherModels = new Service<MyDbContext, AnotherModel>(context);
}
return anotherModels;
}
}
// rinse and repeat
}
This makes sure everything is using the same context instance. So to actually use it, you just do:
var service = new SampleService();
someModels = service.SomeModels.GetAll();
So I was searching the web for a complete example of a code first MVC implementation with repositories, unit of work, viewmodels, etc, and I found exactly what I was looking for here:
EFMVC - ASP.NET MVC 4, Entity Framework 5 Code First and Windows Azure
This is a great demo web app that does everything I need from an architectural standpoint. I don't understand half of it, though, (yet) and it took me about 4 hours of retooling my app, but man was it worth it! It also solved my IEntityChangeTracker error.

Resources