I'm using Entity Framework 6.1.3 in an MVC 5 project, and I'm having issues with ICollection navigation properties being null when unit testing. I am hitting an actual test database in SQL Express, so this may be more like an integration test for you purists. Regardless of what you call it, it is a problem that I would like to solve.
I have read many answers to similar sounding questions, but none of them seem to hit the same problem that I am having here. I understand EF for the most part, I have lazy loading enabled, my classes are public, and I'm using virtual on my navigation properties.
Here is a simplified example of what I am trying to do:
Models:
public class Session
{
public int Id { get; set; }
public string Name { get; set; }
// Navigation Property
public virtual ICollection<File> Files { get; set; }
}
public class File
{
public int Id { get; set; }
public string Name { get; set; }
public int SessionId { get; set; }
public virtual Session Session { get; set; }
}
Test methods:
[TestMethod]
public void Test_TotalFileCount1()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session with no files
var session = new Session() { Name = "Session1" };
context.Sessions.Add(session);
context.SaveChanges();
// This line blows up because session.Files == null
Assert.AreEqual(0, session.Files.Count);
}
[TestMethod]
public void Test_TotalFileCount2()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session
var session = new Session() { Name = "Session2" };
context.Sessions.Add(session);
context.SaveChanges();
// Create file for session
var file = new File() { Name = "File1", Session = session };
context.Files.Add(file)
context.SaveChanges();
// This test passes because session.Files is a
// collection of one file
Assert.AreEqual(1, session.Files.Count);
}
The first test above fails because session.Files throws an ArgumentNullException. However, when I call this same code in the full MVC application, session.Files is not null and instead is an empty collection with Count = 0. The second test passes because session.Files is a collection of one File as I would expect. The navigation properties are clearly doing what they're supposed to in the second case, but not in the first case.
Why is EF behaving like this?
I was able to get around this problem by initializing Files as an empty list in the constructor. I know I could do this conditionally in the getter instead, but I don't think I should have to do either of these things because it just works when it's running normally.
public Session()
{
this.Files = new List<File>();
}
Does anyone have any insight into what is going on here?
If you are working with lazy loading enabled and if you want that the navigation property gets populated after adding the object with the foreign key property to the context you must use the Create method of DbSet (instead of instantiating the object with new):
var session = context.Sessions.Create();
With active lazy loading this will create a proxy object which ensures that the navigation property gets loaded.
[TestMethod]
public void Test_TotalFileCount1()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session with no files
var session = context.Sessions.Create();
session.Name = "Session1";
context.Sessions.Add(session);
context.SaveChanges();
// This line blows up because session.Files == null
Assert.AreEqual(0, session.Files.Count);
}
[TestMethod]
public void Test_TotalFileCount2()
{
ApplicationDbContext context = new ApplicationDbContext();
// Create session
var session = context.Sessions.Create();
session.Name = "Session2";
session.Files = new List<File>()
{
new File() { Name = "File1" }
};
context.Sessions.Add(session);
context.SaveChanges();
// This test passes because session.Files is a
// collection of one file
Assert.AreEqual(1, session.Files.Count);
}
See more about Proxies
Related
Having these two entities, I fetch them, map them to viwemodels/dtos before I pass them to the UI.
I also had to ignore reference loop handling, in my startup.cs file, to map them to DTO's correctly.
public class Matter
{
public int Id { get; set; }
public ICollection<MatterExposure> Exposures { get; set; }
// other properties
}
public class MatterExposure
{
public int Id { get; set; }
public Matter Matter { get; set; }
// other properties
}
When I save the form (which includes a table of 'MatterExposure's) in the UI I pass everything back to the controller to be saved. INFO - not saving child entities 'MatterExposure' yet in below controller call and it works fine!
[HttpPut("{id}")]
public async Task<IActionResult> UpdateData(string id, MatterForClaimDetailedDto generalMatterDto)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var matter = await _autoRepo.GetMatter(id);
// fill some matter data and add a child then save and it works fine
if (await _autoRepo.SaveAll())
return NoContent();
}
public class MatterForClaimDetailedDto
{
public int Id { get; set; }
public GeneralMatterDto MatterData { get; set; }
public ICollection<MatterExposure> Exposures { get; set; }
// other properties
}
Now I want to add the update of MatterExposure entities, as I could have made changes to them in the UI. So I try to use UpdateRange like this
[HttpPut("{id}")]
public async Task<IActionResult> UpdateData(string id, MatterForClaimDetailedDto generalMatterDto)
{
var user = await _userManager.GetUserAsync(HttpContext.User);
var matter = await _autoRepo.GetMatter(id);
matter.EditedDate = DateTime.Now;
matter.FirstName = generalMatterDto.FirstName;
matter.LastName = generalMatterDto.LastName;
_autoRepo.UpdateRange<List<MatterExposure>>(generalMatterDto.Exposures.ToList());
await _autoRepo.SaveAll()
}
public void UpdateRange<T>(T entity) where T : class
{
_autoContext.UpdateRange(entity);
}
But on calling UpdateRange I get this exception message:
"The entity type 'List MatterExposure' was not found. Ensure that the entity type has been added to the model."
In my context I have this:
public DbSet<MatterExposure> MatterExposure { get; set; }
I then tried below with no luck
public DbSet<List<MatterExposure>> MatterExposure { get; set; }
I thought I would try updating each individual 'MatterExposure' entity to see if that would change anything. So I tried removing the UpdateRange call and tried with individual 'MatterExposure' entities
foreach(var exposure in generalMatterDto.Exposures) {
_autoRepo.Update<MatterExposure>(exposure);
}
// in my repo I have this with different things I tried
public void Update<T>(T entity) where T : class
{
// _autoContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
//_autoContext.Entry(entity).State = EntityState.Detached;
_autoContext.Update(entity);
// _autoContext.ChangeTracker.
}
On the first loop through each 'MatterExposure' Update call to the repo I get this exception
"The instance of entity type 'MatterExposure' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
After the exception above I tried I put the loop at the top of the controller method to see if the other entity stuff was interfering.
// at top of controler method before the other entity actions are performed
foreach(var exposure in generalMatterDto.Exposures) {
_autoRepo.Update<MatterExposure>(exposure);
}
And moving the for loop to the top of the controller, runs through the 1st iteration but then fails on the second giving me the same error message again
"The instance of entity type 'MatterExposure' cannot be tracked because
another instance with the same key value for {'Id'} is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
QUESTION - am I not updating the child entities correctly or is it something else?
I am using code first with EF6 but cannot seem to get lazy loading to work. Eager loading is working fine. I have the following classes:
public class Merchant : User
{
...
public virtual ICollection<MerchantLocation> MerchantLocations { get; set; }
}
public class MerchantLocation : BaseEntity
{
...
public int MerchantId { get; set; }
public virtual Merchant Merchant { get; set; }
}
public class User : BaseEntity
{
...
}
public class BaseEntity
{
...
public int Id { get; set; }
}
I test my lazy loading of the locations via the following code (which fails):
public void Test_Lazy_Loading() {
using (var context = new MyDbContext()) {
var merchant = context.Users.OfType<Merchant>.First();
merchant.MerchantLocations.ShouldNotBeNull(); // fails
}
}
However eager loading works fine:
public void Test_Eager_Loading() {
using (var context = new MyDbContext()) {
var merchant = context.Users.OfType<Merchant>.Include("MerchantLocations").First();
merchant.MerchantLocations.ShouldNotBeNull(); // passes
}
}
MerchantLocations is marked as public virtual so I'm not sure what the problem is. I have also added the following in my DbContext constructor:
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
edit: I have also noticed that the merchant object being returned in the above tests is not an EF proxy. It is a plain Merchant. I suspect that this is causing the problem.
I realized that the problem was that the Merchant class did not meet requirements for proxy generation. Specifically, I needed to add a protected parameterless constructor. I only had a private one.
Another thing that can cause lazy loading to fail is navigation properties that are not virtual. That was not the case for OP, but this question is a top Google result so it may help some.
And yet another possible cause is a mapped database column that doesn't exist. I was surprised to see that break lazy loading rather than throw a database exception.
let's say I have an application, for example a web site, where my objectcontext leaves during the time of a request. Some datas I load with EF should be cached to avoid to read in DB and improve performance.
Ok, I read my datas with EF, I put my object in cache (says AppFabric, not in memory cache), but related datas that can be lazy loaded are now null (and access to this property results in a nullreferenceexception). I don't want to load everything in one request, because it's going to be too long, so I want to keep the loading on demand and as soon as it's read, I would like to complete the cache with the new fetched datas.
Note :
only read operations, no create/update/delete.
Don't want to use second level cache like "EF Provider Wrappers" made by Jarek Kowalski
How can I do that ?
EDIT : I've built this samples with northwind database, it's working :
class Program
{
static void Main(string[] args)
{
// normal use
List<Products> allProductCached = null;
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
allProductCached = db.Products.ToList().Clone<DbSet<Products>>();
foreach (var product in db.Products.Where(e => e.UnitPrice > 100))
{
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
// try to use cache, but missing Suppliers
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
{
if (product.Suppliers == null)
product.Suppliers = db.Suppliers.FirstOrDefault(s => s.SupplierID == product.SupplierID).Clone<Suppliers>();
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
// try to use full cache
using (NORTHWNDEntities1 db = new NORTHWNDEntities1())
{
foreach (var product in allProductCached.Where(e => e.UnitPrice > 100))
{
Console.WriteLine(product.ProductName + " => " + product.Suppliers.CompanyName);
}
}
}
}
public static class Ext
{
public static List<Products> Clone<T>(this List<Products> list)
{
return list.Select(obj =>
new Products
{
ProductName = obj.ProductName,
SupplierID = obj.SupplierID,
UnitPrice = obj.UnitPrice
}).ToList();
}
public static Suppliers Clone<T>(this Suppliers obj)
{
if (obj == null)
return null;
return new Suppliers
{
SupplierID = obj.SupplierID,
CompanyName = obj.CompanyName
};
}
}
The problem is that I have to copy everything (without missing a property) and test everywhere if the property is null and load the needed property. My code is of course more and more complex, so that will be a problem if I miss something. No other solution ?
You cannot access the database in EF without an ObjectContext or a DbContext.
You can still use caching effectively, even if you don't have the original context any more.
Maybe your scenario is something like this... Imagine that you have some reference data that you use frequently. You do not want to hit the database each time you need it, so you store it in a cache. You also have per-user data that you don't want to cache. You have navigation properties from your user data to your reference data. You want to load your user data from the database, and have EF automatically "fix up" the navigation properties to point to the reference data.
For a request:
Create a new DbContext.
Retrieve reference data from the cache.
Make a deep copy of the reference objects. (You probably don't want to have the same entities attached to multiple contexts simultaneously.)
Attach each of the reference objects to the context. (e.g. with DbSet.Attach())
Execute whatever queries are required to load the per-user data. EF will automatically "fix up" the references to the reference data.
Identify newly loaded entities that could be cached. Ensure that they contain no references to entities that should not be cached, then save them to the cache.
Dispose of the context.
Cloned Objects and Lazy Loading
Lazy loading in EF is usually accomplished using dynamic proxies. The idea is that you make all properties that could potentially be loaded dynamically virtual. Whenever EF creates an instance of your entity type, it actually substitutes a derived type instead, and that derived type has the lazy loading logic in its overridden version of your properties.
This is all well and good, but in this scenario you are attaching entity objects to the context that were not created by EF. You created them, using a method called Clone. You instantiated the real POCO entity, not some mysterious EF dynamic proxy type. That means you won't get lazy loading on these entities.
The solution is simple. The Clone method must take an additional argument: the DbContext. Don't use the entity's constructor to create a new instance. Instead, use DbSet.Create(). This will return a dynamic proxy. Then initialize its properties to create a clone of the reference entity. Then attach it to the context.
Here is the code you might use to clone a single Products entity:
public static Products Clone(this Products product, DbContext context)
{
var set = context.Set<Products>();
var clone = set.Create();
clone.ProductName = product.ProductName;
clone.SupplierID = product.SupplierID;
clone.UnitProce = product.UnitPrice;
// Initialize collection so you don't have to do the null check, but
// if the property is virtual and proxy creation is enabled, it should get lazy loaded.
clone.Suppliers = new List<Suppliers>();
return clone;
}
Code Sample
namespace EFCacheLazyLoadDemo
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
class Program
{
static void Main(string[] args)
{
// Add some demo data.
using (MyContext c = new MyContext())
{
var sampleData = new Master
{
Details =
{
new Detail { SomeDetail = "Cod" },
new Detail { SomeDetail = "Haddock" },
new Detail { SomeDetail = "Perch" }
}
};
c.Masters.Add(sampleData);
c.SaveChanges();
}
Master cachedMaster;
using (MyContext c = new MyContext())
{
c.Configuration.LazyLoadingEnabled = false;
c.Configuration.ProxyCreationEnabled = false;
// We don't load the details here. And we don't even need a proxy either.
cachedMaster = c.Masters.First();
}
Console.WriteLine("Reference entity details count: {0}.", cachedMaster.Details.Count);
using (MyContext c = new MyContext())
{
var liveMaster = cachedMaster.DeepCopy(c);
c.Masters.Attach(liveMaster);
Console.WriteLine("Re-attached entity details count: {0}.", liveMaster.Details.Count);
}
Console.ReadKey();
}
}
public static class MasterExtensions
{
public static Master DeepCopy(this Master source, MyContext context)
{
var copy = context.Masters.Create();
copy.MasterId = source.MasterId;
foreach (var d in source.Details)
{
var copyDetail = context.Details.Create();
copyDetail.DetailId = d.DetailId;
copyDetail.MasterId = d.MasterId;
copyDetail.Master = copy;
copyDetail.SomeDetail = d.SomeDetail;
}
return copy;
}
}
public class MyContext : DbContext
{
static MyContext()
{
// Just for demo purposes, re-create db each time this runs.
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
}
public DbSet<Master> Masters { get { return this.Set<Master>(); } }
public DbSet<Detail> Details { get { return this.Set<Detail>(); } }
}
public class Master
{
public Master()
{
this.Details = new List<Detail>();
}
public int MasterId { get; set; }
public virtual List<Detail> Details { get; private set; }
}
public class Detail
{
public int DetailId { get; set; }
public string SomeDetail { get; set; }
public int MasterId { get; set; }
[ForeignKey("MasterId")]
public Master Master { get; set; }
}
}
Here is a sample model, different from yours, that shows how to get this working in principle.
I have a situation, which I will try to greatly simplify, in which a method on an object should create a new persistent object of a different class. How can I do this while maintaining repository ignorance and encapsulation at the same time?
In this example, we have widgets and containers of widgets. When a widget is sold or purchased, a WidgetEvent is created (somewhere else) and added to the list of WidgetEvents for the container. We can always query the container for the number of pieces on hand by summing the collection of WidgetEvents.At some point, a worker calls and says the container is empty even though there should still be some widgets in the container. In that case, we call the "SetComplete" method which creates a final WidgetEvent to zero out the container.
public class TestContext : DbContext
{
public DbSet<WidgetEvent> WidgetEvents { get; set; }
public DbSet<WidgetContainer> WidgetContainers { get; set; }
}
public class WidgetEvent
{
public int Id { get; set; }
public int Amount {get;set;}
public WidgetContainer Container {get;set;}
}
public class WidgetContainer
{
public int Id { get; set; }
public virtual ICollection<WidgetEvent> WidgetEvents {get;set;}
public int GetPiecesOnHand()
{
return WidgetEvents.Sum(a=> a.Amount);
}
public void SetComplete()
{
if (GetPiecesOnHand() != 0)
{
WidgetEvents.Add(new WidgetEvent() { Amount = -GetPiecesOnHand() });
}
}
}
It seems to me that proper encapsulation would keep the creation of this event within the class definition. It makes it more understandable to someone reading the code. But I also see there is no way to make this new WidgetEvent persistent without introducing some repository knowledge into the class.
How should I do this? You can assume that I have a Factory for the WidgetEvents somewhere else. In case you were wondering, SaveChanges does not realize that the WidgetEvent has been created.
Edit: This returns
25
25
0
0
which is correct. I guess this leaves me a bit confused about track changes, but its good to know this works. I did something like this a couple of days ago and thought it did not work.
using (var context = new TestContext())
{
WidgetContainer acontainer = new WidgetContainer();
acontainer.WidgetEvents = new List<WidgetEvent>();
context.WidgetContainers.Add(acontainer);
acontainer.WidgetEvents.Add(new WidgetEvent() { Container = acontainer, Amount = 25 });
Console.WriteLine(acontainer.GetPiecesOnHand());
context.SaveChanges();
}
using (var context = new TestContext())
{
WidgetContainer acontainer = context.WidgetContainers.Find(1);
Console.WriteLine(acontainer.GetPiecesOnHand());
acontainer.SetComplete();
Console.WriteLine(acontainer.GetPiecesOnHand());
context.SaveChanges();
}
using (var context = new TestContext())
{
WidgetContainer acontainer = context.WidgetContainers.Find(1);
Console.WriteLine(acontainer.GetPiecesOnHand());
}
It should actually work if the WidgetContainer is attached to (=tracked by) the Entity Framework context without giving the class a reference to a context (or a repository).
For example a code like this...
var widgetContainer = context.WidgetContainers.Find(1);
// or:
// var widgetContainer = context.WidgetContainers.Create();
// widgetContainer.Id = 1;
// context.WidgetContainers.Attach(widgetContainer);
widgetContainer.SetComplete();
context.SaveChanges();
...should insert the new WidgetEvent into the database with a foreign key relationship to the widgetContainer because EF change tracking will recognize the new event that has been added to the WidgetEvents collection when SaveChanges is called.
The important piece is that the widgetContainer must be attached to the context before SetComplete is called.
I have a situation where I have an object that is loaded back from a form to MVC controller via an action. We do not use FormCollection, but the one that use directly the class.
[HttpPost]
public ActionResult AjaxUpdate(Customer customer) { ...
The Customer object contain an object called customer which seem to be updated but when using SaveDatabase() on the context simply doesn't work.
To make it works I had to use in the action:
myDbContext.Customers.Attach(customer)
//...Code here that set to the customer.SubObject a real object from the database so I am sure that the SubObject contain an id which is valid and the datacontext is aware of it...
myDbContext.Entry(customer).State = EntityState.Modified;
Still, I had an exception concerning the "Store update, insert, or delete statement affected an unexpected number of rows (0)" that I were able to remove by using:
Database.ObjectContext().Refresh(RefreshMode.ClientWins,customer);
So, to warp up my question, why do I have to Attach + change the state + call Refresh. Isn't there a better way to update an object that contain object that are referenced in an other table. I am using Code first Entity Framework (Poco object). Also, I do not like to use Refresh since it's hidden from my Databasecontext.
I've made a console test project with EF 4.3.1. The code is my guess what you mean with the commented line and your comments below the question (but my guess is probably wrong because the program doesn't reproduce your error):
You can copy the code into program.cs and add a reference to EF 4.3.1:
using System.Data;
using System.Data.Entity;
namespace EFUpdateTest
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int SubObjectId { get; set; }
public SubObject SubObject { get; set; }
}
public class SubObject
{
public int Id { get; set; }
public string Something { get; set; }
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<SubObject> SubObjects { get; set; }
}
class Program
{
static void Main(string[] args)
{
int customerId = 0;
int subObject1Id = 0;
int subObject2Id = 0;
using (var ctx = new CustomerContext())
{
// Create customer with subobject
var customer = new Customer { Name = "John" };
var subObject = new SubObject { Something = "SubObject 1" };
customer.SubObject = subObject;
ctx.Customers.Add(customer);
// Create a second subobject, not related to any customer
var subObject2 = new SubObject { Something = "SubObject 2" };
ctx.SubObjects.Add(subObject2);
ctx.SaveChanges();
customerId = customer.Id;
subObject1Id = subObject.Id;
subObject2Id = subObject2.Id;
}
// New context, simulate detached scenario -> MVC action
using (var ctx = new CustomerContext())
{
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
}
}
}
}
This works without exception. The question is: What is different in your code which could cause the error?
Edit
To your comment that you don't have a foreign key property SubObjectId in the customer class: If I remove the property in the example program above I can reproduce the error.
The solution is to load the original subobject from the database before you change the relationship:
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Load original SubObject from database
ctx.Entry(customer).Reference(c => c.SubObject).Load();
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
Without a foreign key property you have an Independent Association which requires that the object including all references must represent the state in the database before you change it. If you don't set the reference of SubObject in customer EF assumes that the original state in the database is that customer does not refer to any subobject. The generated SQL for the UPDATE statement contains a WHERE clause like this:
WHERE [Customers].[Id] = 1 AND [Customers].[SubObject_Id] IS NULL
If the customer has a subobject in the DB [SubObject_Id] is not NULL, the condition is not fulfilled and the UPDATE does not happen (or happens for the "unexpected number of rows 0").
The problem does not occur if you have a foreign key property (Foreign Key Association): The WHERE clause in this case is only:
WHERE [Customers].[Id] = 1
So, it doesn't matter what's the original value of SubObject and of SubObjectId. You can leave the values null and the UPDATE works nonetheless.
Hence, the alternative solution to loading the original subobject is to introduce a foreign key property in Customer:
public int SubObjectId { get; set; }
Or, in case the relationship is not required:
public int? SubObjectId { get; set; }