Entity Framework ObjectSet detach method functionality - c#

Well, first of all I'll explain the whole situation. I have simple POCO domain model which I'm persisting with EF 4.0. For the first time I used only navigation properties and no FK properties. But later due to some binding purposes I decided to add FK properties to my model (Company_ID in the code below). Here are two classes from that model:
public class Company:EntityObject<Int32>, {
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Fax { get; set; }
public virtual IList<Customer> Customers { get; set; }
}
public class Customer:EntityObject<Int32> {
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Company Company { get; set; }
public virtual int Company_ID { get; set; }
}
I simplified this model a little bit just to highlight the main issue. After adding FK properties I regenerated the EDMX with FK inclusion. So now some of my test code doesn't work anymore. The problem is with detach method of ObjectSet (Repository Detach is a wrapper around it). Here is the test code:
using (IEntityModelContext context = new EFDataContext()) {
var compFact = context.GetFactory<Company>();
var custFact = context.GetFactory<Customer>();
Company comp = compFact.CreateObject();
comp.Fax = "111111";
comp.Name = "Testcomp";
comp.Phone = "222222";
context.CompanyRepository.Add(comp);
context.SaveChanges();
Customer cust = custFact.CreateObject();
cust.FirstName = "John";
cust.LastName = "Smith";
comp.Customers.Add(cust);
context.SaveChanges();
context.CompanyRepository.Detach(comp);
Company newComp = context.CompanyRepository.Load(com => com.Name == "Testcomp");
Assert.IsNotNull(newComp);
Assert.IsFalse(newComp.IsTransient);
Assert.AreEqual(comp.Fax, newComp.Fax);
Assert.AreEqual(industryList.Values[0], newComp.Industry);
Assert.AreEqual(comp.Name, newComp.Name);
Assert.AreEqual(comp.Phone, newComp.Phone);
Assert.AreEqual(sizeList.Values[0], newComp.Size);
Assert.AreEqual(1, newComp.Customers.Count);
The problem rises when newComp object is loaded: Customers property is empty, moreover it's null (I checked the DB - Customer was successfully saved). So the last assertion fails. This code worked quite well until I added FK property. So is there any explanation of this behavior?

Assuming it was successfully saved to the db (so the customer/company relationship exists in db)
The problem lies in the fact that you did not load the reference to Customers. So either:
A) Include Customers when you retrieve company from the context, or
B) Lazy Load it before you check the reference.

Related

Are DBSet<> object's ids set when assigning one to the other?

I have two entities with relation one-many in DB and corresponding classes generated by reverse engineering the database.
public partial class Assets
{
public int AssetsId { get; set; }
public string Description { get; set; }
public int? PersonId { get; set; }
public virtual Persons Person { get; set; }
}
public partial class Persons
{
public Persons()
{
Assets = new HashSet<Assets>();
}
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Assets> Assets { get; set; }
}
When I assign one entity to the other do the corresponding ID's are set automatically (i am curious if they are set before SaveChanges is called, so i could use it without committing all current changes).
static void Main(string[] args)
{
Context c = new Context();
var a = new Assets();
a.AssetsId = 1;
var p = new Persons();
p.PersonId = 2;
c.Add(a);
c.Add(p);
a.Person = p; //does it set a.PersonId = 2
p.Assets.Add(a); //does it set a.PersonId = 2
c.SaveChanges();
}
(You can just check the answer to your question in the debugger or with a simple test using your provided code.)
When I assign one entity to the other do the corresponding ID's are set automatically [before SaveChanges is called later?]
No, they are not. The reason is, that your navigation properties here are really just POCO properties (meaning simple .NET properties). The Assets.Persons property is really just a property of type Persons and the Persons.Assets property contains really just the HashSet<Assets> instance that you assigned in the constructor. There is no magic here.
The IDs are being synchronized automatically during change tracking, which happens for example when SaveChanges() is called, but can also manually be triggered by calling context.ChangeTracker.DetectChanges().
As a side note, be aware that you should not set IDs in your code explicitly, if you are using identity/auto increment columns for primary keys, because this can lead to exceptions when saving (depending on the database server being used), due to EF Core trying to insert the explicitly assigned ID to the identity/auto increment column (e.g. this is not allowed by default for SQL Server).

Populate associated entities on DBContext Find

I have a Product which needs to have some fields in multiple languages. Therefore I have made a ProductLanguage table which has the composite key and language specific fields (ProductID, LanguageID, Name).
In my Product class I tried something like this:
[Table("Product")]
public class Product
{
DBContext db = new DBContext();
public Product()
{
this.Multimedias = new List<Multimedia>();
this.ProductLanguages = new List<ProductLanguages>();
this.ProductLanguage = db.ProductLanguages.Find(this.ID, Global.Language) ?? new ProductLanguage();
}
public int ID { get; set; }
public string Code { get; set; }
public virtual ICollection<Multimedia> Multimedias { get; set; }
public virtual ICollection<ProductLanguage> ProductLanguages { get; set; }
[NotMapped]
public virtual ProductLanguage ProductLanguage { get; set; }
}
So I could immediately access the language specific fields without needing to go through the collection - the problem is the object obviously doesn't have the ID yet.
Is there any way so when I do
Product product = db.Products.Find(id);
in the controller it will automatically populate my ProductLanguage property?
You can move your assignment of ProductLanguage in the Get for that property.
You don't need the property
[NotMapped]
public virtual ProductLanguage ProductLanguage { get; set; }
The ProductLanguages collection will be populated via lazy loading when you hit it. What you need is a method like this, that will return a ProductLanguage by id:
public ProductLanguage GetProductLanguageById(int id)
{
if (ProductLanguages != null)
{
return ProductLanguages.Where(pl => pl.Id == id).FirstOrDefault();
}
}
This is not confirming to any practice/usage i have seen before.., regardless I would consider it very!! very!! bad practice. Don't make an instance of your context within the Entity(table).
Also why are you doing this... I suggest you read up on Lazy and eager loading.
Quote
"in the controller it will automatically populate my ProductLanguage property?"
Yes.... use eager loading.
Product product = db.Products.Include("ProductLanguage").Find(id);
But I would highly suggest that you don't do all that other weird stuff in the initialization of your entity.
DBContext db = new DBContext();
Product product = db.Products.Include("ProductLanguage").Find(id);

Why does my ASP.NET MVC 4 application create new entities instead of updating the old ones?

EDIT: The solution I selected probably wasn't the best, but it definitely worked. I'll be going through my code over the next week (once this project is done) and I'll update my question when I understand what went wrong.
I'm using the ASP.NET MVC 4 framework with Entity 5. Here's some code:
The class to be instantiated and saved (fresh) in the database:
public class ClassCancellation
{
[Key]
public int Id { get; set; }
public Faculty Professor { get; set; }
public DateTime CancelledOn { get; set; }
public Course Course { get; set; }
[Required]
public ClassDate ClassCancelled { get; set; }
public Message CancellationMessage { get; set; }
[Required]
public List<Student> Students { get; set; }
}
It's mapped from the viewmodel called CancellationFull (with AutoMapper):
public class CancellationForList
{
[Key]
public int Id { get; set; }
public CourseForList Course { get; set; }
public ClassDateForList ClassCancelled { get; set; }
}
public class CancellationFull : CancellationForList
{
public CancellationFull()
{
this.Students = new List<StudentForList>();
}
public FacultyForList Professor { get; set; }
public MessageForList CancellationMessage { get; set; }
public DateTime CancelledOn { get; set; }
public List<StudentForList> Students { get; set; }
}
This is the repo method that turns a CancellationFull into a ClassCancellation and then saves it to the database:
public CancellationFull createClassCancellation(CancellationFull c)
{
ClassCancellation newCancellation = Mapper.Map<ClassCancellation>(c);
dc.ClassCancellations.Add(newCancellation);
dc.SaveChanges();
return Mapper.Map<CancellationFull>(dc.ClassCancellations.FirstOrDefault(cc => cc.Id == newCancellation.Id));
}
Why, for the love of god why, does the database create new objects for Faculty and Course when the Id (primary key) of each's existing entity counterpart is provided? It might also be doing the same with Student objects but I haven't looked that closely.
Before the ClassCancellation instance is saved to the database the debugger shows that it's attributes Professor of type Faculty and Course of type Course have the correct primary key - that is, the primary key of the already existing entities of those types that I'm trying to update with a reference to the new ClassCancellation object.
Driving me nuts. Feel free to ask for clarification!
EDIT:
Here's the logic where the CancellationFull viewmodel is constructed from form data and viewmodels about existing objects retrieved from their respective repos:
newCancellation = new CancellationFull();
newCancellation.CancelledOn = DateTime.Now;
newCancellation.ClassCancelled = repoClass.getClassDateForListById(Int32.Parse(classIds[i]));
newCancellation.Course = repoCourse.getForList(newCancellation.ClassCancelled.Course.Id);
newCancellation.CancellationMessage = repoMessage.getMessageForList(newMessage.Id);
newCancellation.Professor = repoFac.getFacultyForList((int)Session["facId"]);
var students = repoStudent.getStudentsForListByCourse(newCancellation.Course.Id);
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
repoCancellation.createClassCancellation(newCancellation);
Here's an example of one of those repo methods (the rest are very similar):
public CourseForList getForList(int? id)
{
return Mapper.Map<CourseForList>(dc.Courses.FirstOrDefault(c => c.Id == id));
}
What I find the easiest solution is when updating a model, clear any related entities, then re add them.
ie:
newCancellation.Students.Clear();
foreach ( var student in students )
{
newCancellation.Students.Add(student);
}
Try using Attach() instead of Add()
dc.ClassCancellations.Attach(newCancellation);
dc.SaveChanges();
Add() is used for new objects that do not already exist in the database. Attach() is used for creating relationships to entities that already exist in the database.
EDIT
Without seeing your code, the best solution I can recommend to attach is to create a 'stub' instance and then attach that to your newCancellation:
var existingCourse = new Course{ Id = newCancellation.ClassCancelled.Course.Id };
db.Courses.Attach(existingCourse);
newCancellation.Course = existingCourse;
The problem is that you have multiple contexts, or units of work. When you add the newCancellation to the dc context, it also adds any related entity in the object graph that is not tracked in the dc context. I think your best option is:
dc.ClassCancellations.Add(newCancellation);
dc.Entry(newCancellation.Course).State = EntityState.Unchanged;
dc.Entry(newCancellation.Faculty).State = EntityState.Unchanged;
See Julie Lerman's article on this issue for an explanation and other options.
In my opinion, EF should recognize entities that have autonumbered keys and not insert them if the key is assigned.

Pattern required for adding/updating entities in EF5 including relationships

I am using VS 2010 with Entity Framework 5 code first and C# and have a web application (hence disconnected entities). I am used to working with SQL queries directly but am very new to EF and code first.
I have two classes:
public class User
{
public int UserID {get; set;}
public string UserName { get; set; }
public bool IsSuspended { get; set; }
public int UnitID { get; set; }
public virtual MyTrust MyTrusts { get; set; }
}
public class MyTrust
{
public int MyTrustID { get; set; }
public string MyTrustName { get; set; }
public string Region { get; set; }
public bool DoNotUse { get; set; }
}
and my DbContext class contains:
public DbSet<MyTrust> MyTrust { get; set; }
public DbSet<User> Users { get; set; }
modelBuilder.Entity<User>()
.HasRequired(m => m.MyTrust);
The MyTrust entity will not be changed
There are three scenarios I am interested in:
Adding a user with an existing MyTrust
Updating a user with no change to the trust
Updating a user with a change to the trust
When the website returns the data the MyTrust object has only the MyTrustID set. When I update/add the user the MyTrust record is also updated.
CLARIFICATION The relationship in the User object is NOT updated; the actual MyTrust object is updated with the data returned from the website; as most fields are empty this is corrupting the object AND not achieving the required update of the User record.
In fact, the problem seems to boil down to the fact that the wrong end of the relationship is being updated.
I have looked at some many examples I cannot see a simple solution.
Can anyone please suggest a straightforward pattern for this (it was so easy in the SQL days).
UPDATE
I resolved this by adding specific keys to the User and MyTrust classes.
public int NHSTrustID { get; set; }
and a matching key in the MyTrust class.
In retrospect the question was wrong. I wasn't after patterns but the solution to a specific problem.
I've given some examples below - I've done them from memory but hopefully will give you a good starting point:
Adding a user with an existing MyTrust
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Added
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.Entry(myUser.MyTrusts).Property(x => x.MyTrustName).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.Region).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.DoNotUse).IsModified = false;
context.SaveChanges();
}
Updating a user with no change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Unchanged;
context.SaveChanges();
}
Updating a user with a change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.SaveChanges();
}

How to handle projections in RavenDB

Given domain model...
public class Entity
{
public int Id { get; set; }
public Category Category { get; set; }
}
public class Category
{
public string Title { get; set; }
}
... I want to project results of a select query to this view model:
public class EntityViewModel
{
public int Id { get; set; }
public string CategoryTitle { get; set; }
}
I have tried the following query:
var viewModel = (from entity in _documentSession.Query<Entity>()
select new EntityViewModel
{
Id = entity.Id,
CategoryTitle = entity.Category.Title
}.ToList();
The result of this is only partially correct: the Id is set, the CategoryTitle is not. I understand this behaviour is by design, but I suspect there is an API to handle this scenario.
How should such a projection be handled in RavenDB?
Update: I am using build 1.0.573 in embedded mode.
Updated 2: I have forked RavenDB repository, added a failing test to demonstrate this behaviour and created a pull request (#444). Will post more info as I find out.
Looks like it is actually a bug. See pull request #444 for more information.
I will update this answer when this is fixed in a stable release.
Fixed in the current stable release.

Categories

Resources