Entity Framework 6.2.0 Lazy Loading After Insert - entity-framework-6

An odd thing happened after an Add
context.Activities.Add(activity);
context.SaveChanges();
immediately after trying to use the navigation fields
CommandId = activity.CommandId;
if (CommandId != 0)
{
CommandName = activity.Command.CommandName;
}
ActivityCategoryId = activity.ActivityCategoryId;
if (ActivityCategoryId != 0)
{
ActivityCategoryName = activity.ActivityCategory.Name;
}
"Command" is null and "ActivityCategory" is not. Both were created as part of the "Update Model from Database" and both have public null argument constructors. Both work on normal queries say to populate a table. The proxies are present for this. However, after an Add, the proxy for command is not present.
generated code definitions
public virtual ActivityCategory ActivityCategory { get; set; }
public virtual Command Command { get; set; }
If I explicitly "Include", works fine.
public static IList<DAL.Activity> GetActivitiesByCommandI(DAL.MSMTEntities context, int id)
{
IList<DAL.Activity> list = context.Activities
.Include(a => a.Command)
.FilterActivitiesByCommandId(id)
.ToList();
return list;
}
public static IQueryable<DAL.Activity> FilterActivitiesByCommandId(this IQueryable<DAL.Activity> query, int id)
{
IQueryable<DAL.Activity> result = query
.Where(act => act.CommandId == id);
return result;
}
Not sure what to look for next. Will drill down into the context to looks for clues. Appreciate any guidance.

Sorry, this turned out to be a stray entitydatasource in the markup. Why it hacked up on the spatial type, I have not a clue. I downloaded the ef6 from nuget until I can exercise the demon.
Thanks

Related

Cannot map LINQ to Entities

I'm not very clear with writing linq queries. I write a query to select only certain columns from a table using linq lambda expression and I get the error that linq cannot be constructed to entities. The same query when I write using linq to select all columns I don't get any errors and I get all the columns, which i later filter out in the view. But I want to use the lambda to select only certain columns.
Code snippet:
ViewModel:
public class StaggingInternalCashExceptionViewModel
{
public OutputCash OutputCash { get; set; }
public IEnumerable<StaggingInternalException> StaggingInternalException { get; set; }
//list of results of Stagginginternalcashexception
}
Controller:
public ActionResult Exceptionstest(string dd1, string dd2, string dd3)
{
StaggingInternalExceptionViewModel _app = new StaggingInternalExceptionViewModel();
_app.StaggingInternalException = db2.StaggingInternalExceptions.Where(x => x.Level1 == dd1 && x.Level2 == dd2 ).Select(i => new StaggingInternalException
{
StaggingInternalRowID = i.StaggingInternalRowID,
Category = i.Category,
EnterText1 = i.EnterText1,
InternalAmount = i.InternalAmount,
ExternalAmount = i.ExternalAmount
});
_app.StaggingInternalException = (from p in db2.StaggingInternalExceptions
where p.LoadID==loadid && p.Level1 == dd1 && p.Level2 == dd2 select p);
}
In the above code, the lambda expression throws an error when I'm trying to select only certain columns from the table or if we are speaking in terms of entity classes, only certain properties. But the query returns all the columns. Should I be using DTOS? I'm not sure what the use of data transfer objects is. Some explanation on this would be great. Thanks.
You need to use a DTO.
A dto is just an object that you map your result to. In your case it would be
public class StaggingInternalExceptionViewModel
{
public int StaggingInternalRowID { get; set; }
public int Category { get; set; }
... //rest of properties
}
You need to change your StaggingInternalCashExceptionViewModel to use the StaggingInternalException DTO
public class StaggingInternalCashExceptionViewModel
{
public OutputCash OutputCash { get; set; }
public IEnumerable<StaggingInternalExceptionViewModel> StaggingInternalException { get; set; }
//list of results of Stagginginternalcashexception
}
Then your expression stays the basically the same but you select a new StaggingInternalExceptionViewModel instead of StaggingInternalException
StaggingInternalExceptionViewModel _app = new StaggingInternalCashExceptionViewModel();
_app.StaggingInternalException = db2.StaggingInternalExceptions.Where(x => x.Level1 == dd1 && x.Level2 == dd2 ).Select(i => new StaggingInternalExceptionViewModel
{
StaggingInternalRowID = i.StaggingInternalRowID,
Category = i.Category,
EnterText1 = i.EnterText1,
InternalAmount = i.InternalAmount,
ExternalAmount = i.ExternalAmount
});
Linq to Entities doesn't let you project a query using an entity type because you can end up losing information at loading an entity partially and trying later to save that entity to your DB. So, you must project your queries when you need partial information of an entity whether using a DTO or an anonymous type.
If you need to use the entity type, then don't project using Select method, the only thing is you're going to load all the properties, but I think this is not the case because you don't need all the data ;).

List Property not being updated by EF

Ok, this is probably a concept that i've got wrong, but anyways...
(MVC3) I have an entity with a list property on it. My CRUD views work sending a JSon representation to the controller, via an Ajax post. Everything is working great, except that when i'm posting an update of that entity, the list property is not being updated at all. All the simple properties of the entity are updated, but (as I imagine) the update tree is not including the List property. How can I make the EF aware of those changes on the list?
Here's some of the code so far:
[HttpPost]
public JsonResult Edit(Lote lote)
{
//Given the IDs present in lote.Documentos, load the List of Documentos
if (lote.Documentos != null)
{
List<Documento> ldoc = new List<Documento>();
foreach (var d in lote.Documentos)
{
ldoc.Add(db.Documentos.Find(d.IDDocumento));
}
lote.Documentos.Clear();
foreach (var d in ldoc)
{
lote.Documentos.Add(d);
}
}
//Now, clear all the previous errors
foreach (var modelValue in ModelState.Values)
{
modelValue.Errors.Clear();
}
//And re-validate the model
ValidateModel(lote);
if (ModelState.IsValid)
{
if (lote.IDLote > 0)
{
//Updating
db.Entry(lote).State = EntityState.Modified;
}
else
{
//Inserting
db.Lotes.Add(lote);
}
db.SaveChanges();
CustomMessages.Sucesso(TempData, "Informações salvas com sucesso.", 10000);
return Json(new { Success = 1, IDProprietario = lote.IDLote, ex = "" });
}
else
{
return Json(new { Success = 0, ex = "Falha na rotina de armazenamento das informações"});
}
And those are the classes themselves:
public class Lote
{
[Key]
public int IDLote { get; set; }
(... lots of properties ...)
[Display(Name = "Documentos")]
public List<Documento> Documentos { get; set; }
}
public class Documento
{
//---=== ATRIBUTOS ===---
[Key]
public int IDDocumento { get; set; }
[Required]
[MaxLength(60)]
public string Nome { get; set; }
public List<Lote> Lotes { get; set; }
}
As this is a Many-to-Many relationship, i also got this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove
<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
modelBuilder.Entity<Lote>()
.HasMany(t => t.Documentos)
.WithMany(t => t.Lotes)
.Map(m =>
{
m.ToTable("LoteDocumento");
m.MapLeftKey("IDLote");
m.MapRightKey("IDDocumento");
});
(... and some other stuff)
Any help on this?
Try changing this line:
ldoc.Add(db.Documentos.Find(d.IDDocumento));
to
ldoc.Add(db.Documentos.Include("Lotes").FirstOrDefault(x => x.IDDocumento == d.IDDocumento));
You need to make sure that the relatioship/objects that you are changing are in fact attached to your current DB context. Otherwise entity framework wont be able to track changes made to them.
This link explains it in terms of object context, but I think the same rules apply to DBcontext.
If that doesn't work, let me know cause I am really trying to get better at understanding the way EF works.
Okay, found some more link that will help:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
Entity Framework and Connection Pooling
When you execute Include("Lotes") that adds your lotes related to your Documentos to the DBContext. Entity framework is now tracking these objects. Further down in your code you are re-adding them to the context with this line db.Entry(lote).State = EntityState.Modified; That's my guess anyway.
based on the links above i would try and re-write what you have like this (not compiled):
[HttpPost]
public JsonResult Edit(Lote lote)
{
//get old Lote from DB
var oldLote = db.Lotes.include("Documentos").FirstOrDefault(x => x.IDLote == lote.IDLote);
//update
if(oldLote != null)
{
//refresh any other properties that you may have changed on it.
db.Entry(oldLote).CurrentValues.SetValues(lote);
//not sure if you will even need this section any more but then you can...
oldLote.Documentos.Clear();
foreach (var d in lote.Documentos)
{
oldLote.Documentos.Add(db.Documentos.include("Lotes").FirstOrDefault(x => x.IDDocumento == d.IDDocumento));
}
}
else //add
{
//not sure if this will work
foreach (var d in lote.Documentos)
{
db.Entry(d).State = EntityState.Modified;
}
db.Lotes.Add(lote);
}
//then just save changes. EF is already tracking all your objects and changes.
db.SaveChanges();
....

Associated entities not attaching to parent object when saving

I am working on an application that uses EF 4.2 and database-first development, using the standard T4 template to generate a DbContext and POCOs. The T4 templates generate entities something like this:
public class Address
{
public int AddressId { get;set; }
public string Address1 { get;set; }
public string City { get;set; }
}
public class Account
{
public int AccountId { get;set; }
public string Name { get;set; }
public int AddressId { get;set; }
public Address BillingAddress { get;set; }
}
When I create a billing address for an existing account, my code is something like this:
public void Save(Account updated)
{
var existing = DbContext.Find(updated.AccountId);
MyContext.Entry(existing).CurrentValues.SetEntry(updated);
existing.Address = updated.Address;
MyContext.SaveChanges();
}
Watching SQL Server Profiler, I can see the Address entry being inserted into the database, but unfortunately, it is occurring after the Account entry is updated, so the address is detached from its parent account, and when I next load the account, the billing address is empty again.
A workaround is to add the following code after the call to SaveChanges():
if (existing.AddressId == null && existing.Address != null)
{
existing.AddressId = existing.Address.AddressId;
MyContext.SaveChanges();
}
which, while it may work, requires a second SQL UPDATE to the database, and as the entity grows and adds more associations, requires more and more hacks. Is there something obvious that I'm missing?
** UPDATE **
Following Ladislav's answer below, I added a call to the following method in the WriteNavigationProperty to my T4 template:
void WriteKeyAttribute(CodeGenerationTools code, NavigationProperty navigationProperty, MetadataTools ef)
{
var dependentProperties = navigationProperty.GetDependentProperties();
if (dependentProperties.Any())
{
var keys = new List<string>();
foreach (var key in dependentProperties)
{
keys.Add(String.Format("\"{0}\"", key.Name));
}
#>
[ForeignKey(<#= String.Join(", ", keys) #>)]
<#+
}
}
Hope that helps!
It sounds like your BillingAddress is incorrectly mapped because AddressId is not handled as the FK of the relation.
Try to add this attribute to your navigation property:
[ForeignKey("AddressId")]
public Address BillingAddress { get;set; }
If you are using EDMX with database first make sure that the there is correctly configured relation between those classes. EF uses this information in its store mapping and store mapping defines sequence of the operations. If you don't have correctly configured relation entities are processed in alphabetical order of their type names => Account is processed prior to Address.
Btw. are you sure that your Account is not duplicated during your SaveChanges call?

Lazy Loading and RequiredAttribute problem: The XXX field is required

I have a Comment class with, among others, an Author property:
public class Comment : IEquatable<Comment>
{
public int ID { get; set; }
[Required]
public virtual User Author { get; set; }
// More properties here
}
I want users to be able to "like" a comment, much like here at StackOverflow. To that end I have the following action in my CommentController:
public virtual ActionResult Like(int id)
{
var comment = _session.Single<Comment>(c => c.ID == id);
comment.Likes++;
_session.CommitChanges();
return Json(new { comment.Likes });
}
Whenever I invoke this action I get the following Validation Error:
The Author field is required.
The Comment object comes from the db, so it does have an author. The "funny" thing is, whenever I use the Visual Studio debugger to check whether the Author really is missing, the validation error does not fire.
Am I correct in assuming here that the problem is that the lazy loading of the Author property never takes place? If so, how can I, for this situation only, force all navigation properties to be filled in? (I want to keep working with lazy loading otherwise)
What's the neatest way to solve this? Am I even on the right track?
And why isn't lazy loading happening while EF clearly requires it to save the entity?
Any help will be appreciated.
You could use Include to eagerly fetch a relation on the data context.
context.Comments.Include("Author").Single<Comment>(c => c.ID == id);
I have had a similar problem. The below should work (with LazyLoading enabled).
public virtual ActionResult Like(int id)
{
var comment = _session.Single<Comment>(c => c.ID == id);
comment.Likes++;
if(TryUpdateModel(comment))
{
_session.CommitChanges();
}
return Json(new { comment.Likes });
}

MVC 3 Master / Detail UpdateModel inserts new detail records instead of updating existing records

Ok, I've read Phil Haack's article on binding to a list and I've got that working fine on one view. But what I'm stuck when doing it off a master record.
I've got a really simple form for this object
public class Master
{
public int ID { get; set; }
public string MasterTitle { get; set; }
public virtual IList<Detail> Details { get; set; }
}
public class Detail
{
public int ID { get; set; }
public string DetailName { get; set; }
public virtual Master Master { get; set; }
}
The form collection comes back with the expected prefixes:
[0] ""
[1] "ID"
[2] "MasterTitle"
[3] "Details[0].ID"
[4] "Details[0]"
[5] "Details"
[6] "Details[0].DetailName"
[7] "Details[1].ID"
[8] "Details[1]"
[9] "Details[1].DetailName" string
And the Controller.UpdateModel(master) binds all the properties correctly. But when I call dbContext.SaveChanges it issues the follow sql from sql profiler (psuedo code)
update detail1 set masterID = null
update detail2 set masterID = null
update master set masterName = 'newname'
insert detail1 ...
insert detail2 ...
I've got a work around that works but it's pretty hackish and I'm currently not matching up the keys so it's dependent on everything coming back in the right order. Plus I've got to include all the fields that I want updated.
public ActionResult Edit(FormCollection collection)
{
try
{
using (var ctx = new PlayContext())
{
var id = int.Parse(collection["ID"]);
Master master = ctx.Master.Find(id);
UpdateModel(master, new [] {"MasterTitle"});
for (int i = 0; i < master.details.Count; i++)
{
UpdateModel(master.details[i], "Details[" + i + "]", new[] { "DetailName" });
}
ctx.SaveChanges();
return View(master);
}
}
catch (Exception e)
{
ModelState.AddModelError("", e);
}
return View();
}
I've got a feeling that UpdateModel is somehow removing and re-adding the children.
Has anyone else got this to work? Of course, I could throw in the towel and parse the indexed field names myself, but I'm so close!
It should work - I haven't had any problems with similar code in MVC2.
I'm worried about this line though:
[5] "Details"
What's it sending back in details? I expect this could be causing the problem - Not entirely sure how the model binder works in MVC 3, but I'd expect this line would cause it to set the Details collection to NULL.
You shouldn't have to rely on the fields returning in a specific order - the binder would be designed to handle them in any order.

Resources