I'm building a simple ASP.NET API using EF and Oracle Database. When I want to get all elements from a database table the response (500) says "The operation cannot be completed because the DbContext has been disposed".
Well, I've tried to solve this problem before to post it here. But I can't. My Controller Code is as follows.
public class PruebasController : ApiController
{
//Prueba[] pruebas = new Prueba[]
//{
// new Prueba { Name = "Tomato Soup"},
// new Prueba { Name = "Yo-yo"},
// new Prueba { Name = "Hammer"}
//};
public IQueryable<Prueba> GetAllPruebas()
{
Database.SetInitializer(new DropCreateDatabaseAlways<OracleDbContext>());
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas;
}
}
}
(As you see, I have a "pruebas" List and when I return it the http service works)
And this is my OracleDbContext
public class OracleDbContext : DbContext
{
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
You are returning an IQueryable object. Once you return, you exit your Using statement, which closes your Context. You need to enumerate using .ToList() before you exit your using statement. This will execute the query while the context is still open.
Change it to this:
public List<Prueba> GetAllPruebas()
{
using (var ctx = new OracleDbContext())
{
return ctx.Pruebas.ToList();
}
}
Also, you should add your initializer in the constructor of your context, not your GetAllPruebas method, like this:
public class OracleDbContext : DbContext
{
public OracleDbContext()
{
Database.SetInitializer<OracleDbContext>(new DropCreateDatabaseAlways<OracleDbContext>());
}
public DbSet<Prueba> Pruebas { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("DATA");
}
}
Problem Solved. I wrote CreateDatabaseIfNotExists instead DropCreateDatabaseAlways in the Database.SetInitializer and it works.
Related
I got the following error when I try to test an update operation using Entity Framework core:
System.InvalidOperationException : The instance of entity type 'Companies' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
After doing some research, I tried everything that I found:
Create in scope DB context
deattach and attached the object I want to update from the DB context
Return the object to be updated using "AsNoTracking()" , my repository actually do this.
For the testing I am using EF in-memmory database with it fixture, I am using XUnit and .NET 5.
Can I get any help with this please?
Here is my code:
// The repository I am trying to test
public class RepositoryBase<T> : ICrudRepository<T> where T : class, IModel
{
protected PrjDbContext DatabaseContext { get; set; }
public RepositoryBase(PrjDbContext databaseContext) => DatabaseContext = databaseContext;
protected IQueryable<T> FindAll() => DatabaseContext.Set<T>().AsNoTracking();
protected IQueryable<T> FindBy(Expression<Func<T, bool>> expression) => DatabaseContext.Set<T>().Where(expression).AsNoTracking();
public void Create(T entity) => DatabaseContext.Set<T>().Add(entity);
public void Update(T entity) => DatabaseContext.Set<T>().Update(entity);
public void Delete(T entity) => DatabaseContext.Set<T>().Remove(entity);
public async Task<IEnumerable<T>> ReadAllAsync() => await FindAll().ToListAsync().ConfigureAwait(false);
public async Task<T> ReadByIdAsync(int id) => await FindBy(entity => entity.Id.Equals(id)).FirstOrDefaultAsync().ConfigureAwait(false);
}
//The Database context
public partial class PrjDbContext : DbContext
{
public PrjDbContext()
{
}
public PrjDbContext(DbContextOptions<PrjDbContext> options)
: base(options)
{
}
public virtual DbSet<Companies> Companies { get; set; }
}
// This is my fixture with the in-memory Database
public sealed class PrjSeedDataFixture : IDisposable
{
public PrjDbContext DbContext { get; }
public PrjSeedDataFixture(string name)
{
string databaseName = "PrjDatabase_" + name + "_" + DateTime.Now.ToFileTimeUtc();
DbContextOptions<PrjDbContext> options = new DbContextOptionsBuilder<PrjDbContext>()
.UseInMemoryDatabase(databaseName)
.EnableSensitiveDataLogging()
.Options;
DbContext = new PrjDbContext(options);
// Load Companies
DbContext.Companies.Add(new Companies { Id = 1, Name = "Customer 1", Status = 0, Created = DateTime.Now, LogoName = "FakeLogo.jpg", LogoPath = "/LogoPath/SecondFolder/", ModifiedBy = "Admin" });
DbContext.Companies.AsNoTracking();
DbContext.SaveChanges();
}
public void Dispose()
{
DbContext.Dispose();
}
}
The test method "Update_WhenCalled_UpdateACompanyObject", is not working for me.
// And finally, this is my test class, Create_WhenCalled_CreatesNewCompanyObject pass the test, but Update_WhenCalled_UpdateACompanyObject isn't passing the test.
public class RepositoryBaseCompanyTests
{
private Companies _newCompany;
private PrjDbContext _databaseContext;
private RepositoryBase<Companies> _sut;
public RepositoryBaseCompanyTests()
{
_newCompany = new Companies {Id = 2};
_databaseContext = new PrjSeedDataFixture("RepositoryBase").DbContext;
_sut = new RepositoryBase<Companies>(_databaseContext);
}
[Fact]
public void Create_WhenCalled_CreatesNewCompanyObject()
{
//Act
_sut.Create(_newCompany);
_databaseContext.SaveChanges();
//Assert
Assert.Equal(2, _databaseContext.Companies.Where( x => x.Id == 2).FirstOrDefault().Id);
}
[Fact]
public async void Update_WhenCalled_UpdateACompanyObject()
{
//Arrange
var company = await _sut.ReadByIdAsync(1);
company.Name = "Customer 2";
//_databaseContext.Entry(company).State = EntityState.Detached;
//_databaseContext.Attach(company);
//_databaseContext.Entry(company).State = EntityState.Modified;
//Act
_sut.Update(company);
await _databaseContext.SaveChangesAsync();
//Assert
Assert.Equal("Customer 2", _databaseContext.Companies.Where(x => x.Id == 1).FirstOrDefault().Name);
}
}
If you are using EF Core 5.0 then call DbContext.ChangeTracker.Clear() (or go through DbContext.Entries collection and set state to Detached for earlier ones) after DbContext.SaveChanges(); in PrjSeedDataFixture ctor. Adding/Updating an entry makes it tracked and you are reusing the context that created an entry with Id = 1, so when _sut.Update(company); is called it will try to track it again (since ReadByIdAsync should return an untracked one).
P.S.
Adding an extra repository abstraction layer around EF can be considered as antipattern (because EF already implements repository/UoW patterns) and the issue you are having can be one of the examples of why that is true and why this abstraction can be a leaky one. So if you still decide that having one is a good idea - you need to proceed with caution.
I'm using Moq to provide a mocking context for my Oracle db. But when I call _context.Entry with the mocked context, I get an InvalidOperationException.
"No connection string named 'Entities' could be found in the application config file."
I'm already providing a mocked context, so not sure why it's still trying to read connection string to create the context.
// generated code for oracle db
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<ACTIVITY_CODE> ACTIVITY_CODE { get; set; }
}
// my code
public partial class Entities : System.Data.Entity.DbContext
{
public Entities(string scon) : base(scon) { }
}
// my code
public partial class ActivityCodeService
{
private Entities _context;
public ActivityCodeService(Entities context)
{
this._context = context;
}
public ACTIVITY_CODE Update(ACTIVITY_CODE item)
{
ACTIVITY_CODE ret = null;
var found = Read(item.ACT_ID);
if (found != null)
{
_context.Entry<ACTIVITY_CODE>(found).CurrentValues.SetValues(item); // throws InvalidOperationException "No connection string named 'Entities' could be found in the application config file."
_context.SaveChanges();
ret = item;
}
return ret;
}
}
// test code
[TestMethod]
public void activity_code_update_test()
{
// arrange
var mockSet = new Mock<DbSet<ACTIVITY_CODE>>();
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.Provider).Returns(testData.Provider);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.Expression).Returns(testData.Expression);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable<ACTIVITY_CODE>>().Setup(o => o.GetEnumerator()).Returns(testData.GetEnumerator());
var mockContext = new Mock<Entities>();
mockContext.Setup(c => c.ACTIVITY_CODE).Returns(mockSet.Object);
var expected = new ACTIVITY_CODE() { ACT_ID = 1, ACT_CODE = "code 2", ACT_DESC = "desc 2" };
var target = new ActivityCodeService(mockContext.Object);
// act
target.Update(expected);
}
But if I don't use _context.Entry, then the test runs fine which is expected. So does that mean _context.Entry is creating another internal context and ignoring my mocked context?
// my code
public ACTIVITY_CODE Update(ACTIVITY_CODE item)
{
var ret = _context.ACTIVITY_CODE.FirstOrDefault(o => o.ACT_ID == item.ACT_ID);
if (ret != null)
{
ret.ACT_CODE = item.ACT_CODE;
ret.ACT_DESC = item.ACT_DESC;
_context.SaveChanges(); // this will work fine with Moq's mocked context
}
return ret;
}
Entry isn't, and can't be, mocked by Moq as it's not virtual so it is still going to try to use the database that it believes is there. That's why it's looking for a connection string.
What I have been able to do which has worked well is to abstract that function call into a virtual method that I had enough control over to actually mock.
Alternatives:
There are some tools based on answers to other questions that have the same base problem. Looks like TypeMock and JustMock may be able to work around the issue.
Additionally, it looks like MS Fakes should be able to shim it. After a little investigation it looks like it'd work something like this:
ShimDbEntityEntry<TestModel> entryMock = new ShimDbEntityEntry<TestModel>();
ShimDbPropertyValues mockValues = new ShimDbPropertyValues();
mockValues.SetValuesObject = (newValues) => { }; // Manually do something here
entryMock.CurrentValuesGet = () => mockValues;
ShimDbContext.AllInstances.EntryOf1M0<TestModel>((ctx, target) => entryMock);
I'm trying to understand how to dynamically create the connection string for my DbContext, but my application says it has no connection string in the app.config (and that's correct because I don't want to use it in the app.config or web.config). This is what I have:
In my solution I have a project called InterfaceApp. It is a ASP.NET MVC 5 application. When I put my connection string in the web.config all seems to be working fine.
In my solution I have an other project called InterfaceApp.Connector.Erp1. Here I want to connect to an ERP application and fetch some items. So in my repository I have:
namespace InterfaceApp.Connector.Erp1.Repository
{
internal class ItemRepository : IItemRepository
{
public IEnumerable<Item> Items
{
get
{
List<Item> items = new List<Item>();
using (Models.Entities context = new Models.Entities())
{
var itemList = context.Items.ToList();
foreach(var item in itemList)
{
items.Add(new Item() { Id = item.ID, Description = item.Description, ItemCode = item.ItemCode });
}
}
return items.ToList();
}
}
}
}
I've created a partial class to connect to the database:
namespace InterfaceApp.Connector.Erp1.Models
{
public partial class Entities
{
public Entities(string connectionString)
: base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = "MyServer", //When this works it will be dynamic
InitialCatalog = "XXX", //When this works it will be dynamic
PersistSecurityInfo = true,
IntegratedSecurity = true,
MultipleActiveResultSets = true,
};
var entityConnectionStringBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/Models.Erp1Model.csdl|res://*/Models.Erp1Model.ssdl|res://*/Erp1Model.msl",
ProviderConnectionString = sqlBuilder.ConnectionString
};
return entityConnectionStringBuilder.ConnectionString;
}
}
}
The Context class that is auto-generated by EF6 (Db First) looks like this:
namespace InterfaceApp.Connector.Erp1.Models
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Items> Items { get; set; }
}
}
When I run my application, the debugger stops at the auto-generated class, but not at my partial class. Because it cannot find the connection string Entities in my app.config and web.config it generates an error saying that the connection string is not found in the application config file. What am I doing wrong?
When you are calling the DbContext, you're calling the empty constructor (new Models.Entities()). Thus, it will call the auto-generated DbContext. If you want to call your partial class, you need to call it explicitly with the parameter.
Remember when you create a partial class, the compiler merges them, so you have this when compiled :
public partial class Entities : DbContext
{
public Entities()
: base("name=Entities")
{
}
public Entities(string connectionString)
: base(ConnectionString())
{
}
private static string ConnectionString()
{
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder
{
DataSource = "MyServer", //When this works it will be dynamic
InitialCatalog = "XXX", //When this works it will be dynamic
PersistSecurityInfo = true,
IntegratedSecurity = true,
MultipleActiveResultSets = true,
};
var entityConnectionStringBuilder = new EntityConnectionStringBuilder
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/Models.Erp1Model.csdl|res://*/Models.Erp1Model.ssdl|res://*/Erp1Model.msl",
ProviderConnectionString = sqlBuilder.ConnectionString
};
return entityConnectionStringBuilder.ConnectionString;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Items> Items { get; set; }
}
What you probably need a a method to create your DbContext and call it instead of calling a new DbContext.
public static Entities Create()
{
return new Entities(ConnectionString());
}
Then you can use it this way :
using (var context = Entities.Create())
{
//...
}
This seems like an obvious question, but is CreateDatabaseIfNotExists.InitializeDatabase atomic?
Currently I have code similar to below. This an attempt to make sure that either database creation completes fully, or the database doesn't exist.
public class MyContext : CreateDatabaseIfNotExists<MyContext>
{
public override void InitializeDatabase(MyContext context)
{
base.InitializeDatabase(context);
//Removed custom initialization code
}
protected override void Seed(MyContext context)
{
//Removed adding / updating seed data
context.SaveChanges();
base.Seed(context);
}
}
public static class DbCreator
{
public static void CreateDatabase()
{
using (MyContext dbContext = new MyContext())
{
try
{
var dbIntializer = new MyDbInitializer();
dbIntializer.InitializeDatabase(dbContext);
}
catch (Exception ex)
{
dbContext.Database.Delete();
throw;
}
}
}
}
I've looked in some obvious places for an answer such as https://msdn.microsoft.com/en-us/library/gg696403(v=vs.113).aspx.
I am using Entity Framework 6.1.3
I found a decent article to get me started on unit testing my Entity Framework-based application using Moq: https://msdn.microsoft.com/en-us/data/dn314429.aspx
This issue I'm having is that the SaveChanges method of the Mock does not appear to trigger the ValidateEntity method like it normally would. None of the validation settings I configured in the EntityTypeConfiguration are being thrown as a DbEntityValidationException.
For example, my AddRoles_Fails_For_Empty_Name tests to make sure that the service cannot add a role with an empty name. Either the IsRequired() configuration is not being applied, or the ValidateEntity method is not being called. I should mention that it works correctly if I use the actual context in the web app.
I've included some of my relevant unit testing, DbContext, and Service code below.
Am I doing something incorrectly? Are there any known issues or workarounds?
Role DB Map
public class RoleMap : EntityTypeConfiguration<Role>
{
public RoleMap()
{
ToTable("bm_Roles");
HasKey(r => r.Id);
Property(r => r.Name).IsRequired().HasMaxLength(100).HasIndex(new IndexAttribute("UX_Role_Name") { IsUnique = true });
Property(r => r.Description).HasMaxLength(500);
}
}
DbContext
public class BlueMoonContext : DbContext, IBlueMoonContext
{
public BlueMoonContext() : base("name=BlueMoon")
{
}
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(typeof(BlueMoonContext).Assembly);
}
public void MarkAsModified<T>(T entity) where T : class
{
entity.ThrowIfNull("entity");
Entry<T>(entity).State = EntityState.Modified;
}
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
if (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified)
{
// Perform validations that require database lookups
if (entityEntry.Entity is Role)
{
ValidateRole((Role)entityEntry.Entity, result);
}
else if (entityEntry.Entity is User)
{
ValidateUser((User)entityEntry.Entity, result);
}
}
return result;
}
private void ValidateRole(Role role, DbEntityValidationResult result)
{
if (role.Name.HasValue() && !Roles.NameAvailable(role.Name, role.Id))
{
result.ValidationErrors.Add(new DbValidationError("Name", "Already in use"));
}
}
private void ValidateUser(User user, DbEntityValidationResult result)
{
if (user.UserName.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
{
result.ValidationErrors.Add(new DbValidationError("UserName", "Already in use"));
}
if (user.Email.HasValue() && !Users.UserNameAvailable(user.UserName, user.Id))
{
result.ValidationErrors.Add(new DbValidationError("Email", "Already in use"));
}
}
}
Account Service
public class AccountService : BaseService, IAccountService
{
private IPasswordHasher _passwordHasher;
public AccountService(IBlueMoonContext context, IPasswordHasher passwordHasher) : base(context)
{
_passwordHasher = passwordHasher;
}
public ServiceResult CreateRole(Role role)
{
role.ThrowIfNull("role");
Context.Roles.Add(role);
return Save();
}
// Copied from base service class
protected ServiceResult Save()
{
var result = new ServiceResult();
try
{
Context.SaveChanges();
}
catch (DbEntityValidationException validationException)
{
foreach (var validationError in validationException.EntityValidationErrors)
{
foreach (var error in validationError.ValidationErrors)
{
result.AddError(error.ErrorMessage, error.PropertyName);
}
}
}
return result;
}
}
Unit Test
[TestFixture]
public class AccountServiceTests : BaseTest
{
protected Mock<MockBlueMoonContext> _context;
private IAccountService _accountService;
[TestFixtureSetUp]
public void Setup()
{
_context = new Mock<BlueMoonContext>();
var data = new List<Role>
{
new Role { Id = 1, Name = "Super Admin" },
new Role { Id = 2, Name = "Catalog Admin" },
new Role { Id = 3, Name = "Order Admin" }
}.AsQueryable();
var roleSet = CreateMockSet<Role>(data);
roleSet.Setup(m => m.Find(It.IsAny<object[]>())).Returns<object[]>(ids => data.FirstOrDefault(d => d.Id == (int)ids[0]));
_context.Setup(m => m.Roles).Returns(roleSet.Object);
// _context.Setup(m => m.SaveChanges()).Returns(0);
_accountService = new AccountService(_context.Object, new CryptoPasswordHasher());
}
[Test]
public void AddRole_Fails_For_Empty_Name()
{
var role = new Role { Id = 4, Name = "" };
var result = _accountService.CreateRole(role);
Assert.False(result.Success);
}
}
SaveChanges is a virtual method which means you invoke a fake method....
You can create your mock CallBase = true, but it is not a good idea(it miss the idea of UT):
_context = new Mock<BlueMoonContext>(){ CallBase = true };
The above code will use the real implementation of BlueMoonContext for any method/property which is not explicitly setup.
RoleMap is responsible for your DB stracture, you should test it as a part of integration test(with DB).
In my opinion you should create an integration tests to verify the integrity(for example; cover RoleMap) with your DB, And create a UT using the Throw setup to cover the catch section(it's a part of your unit):
_contest.Setup(x => x.SaveChanges())
.Throws(new DbEntityValidationException());
Edit to answer the OP question in the comment
no, you don't have to separate the built in validation, you have to create another test(integration test). In this test you'll verify the validation behaviour: insert an illegal entity, expect that exception will raise(using ExpectedExceptionAttribute) and then verify that the DB is empty... to apply this behaviour use this pattern:
try
{
\\...
\\try to commit
}
catch(DbEntityValidationException ex)
{
\\do some validation, then:
throw;\\for ExpectedExceptionAttribute
}
I looked over the api of EntityTypeConfiguration, I didn't saw any contact which allows to UT the rules(unless you use tools like MsFakes, TypeMock Isolator there is no way to verify the ToTable/HasKey/Property was called). The class is being in used inside EntityFramework(which is a part of the BCL) in the integration test you don't have to verify that EntityFramework work properly, you are going to verify that your custom rules was integrated and works as you expect(In this answer you can read the reason not to test a BCL classes).
So use Moq in the UTs of AccountService. Create an integration tests for BlueMoonContext and RoleMap(without Moq).
By the way #LadislavMrnka offer an interesting way to test(integration test) EntityTypeConfiguration