Entity Framework properties how does it work - c#

Here are four different approaches to defining an Entity class in Entity Framework. Can someone tell me what is the difference in the way each approach works and also recommend which of these approaches to use?
// Approach 1
public class User
{
public int Id { get; set; }
public Address Address { get; set; }
}
// Approach 2
public class User
{
public int Id { get; set; }
public Address Address { get; set; }
public User()
{
this.Address = new Address();
}
}
// Approach 3
public class User
{
public int Id { get; set; }
public virtual Address Address { get; set; }
}
// Approach 4
public class User
{
public int Id { get; set; }
public virtual Address Address { get; set; }
public User()
{
this.Address = new Address();
}
}
Can I please ask for any good explanation of the differences?
Are the differences related to Lazy loading vs. Eager loading?
Which is better and why?

Here is how it should look like:
public class User
{
public int Id { get; set; }
public int AddressId { get; set; }
public virtual Address Address { get; set; }
}
Explanations:
We need to mark our navigation properties as virtual to enable EF lazy loading at runtime. EF creates a user proxy object inheriting from your user class and marking Address as virtual allows EF to override this property and add lazy loading support code.
Having an AddressId as a FK for Address navigation property essentially converts your User-Address association to a "Foreign Key Association". These type of associations are preferred since they are easier to work with when it comes to updates and modifications.
Unless you have a navigation property in the form of collection of objects (e.g. IList<Address>) you don't need to initialize it in your constructor. EF will do that for you automatically if you include them in your queries.

Related

Update Entity from ViewModel in MVC using AutoMapper

I have a Supplier.cs Entity and its ViewModel SupplierVm.cs. I am attempting to update an existing Supplier, but I am getting the Yellow Screen of Death (YSOD) with the error message:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I think I know why it is happening, but I'm not sure how to fix it. Here's a screencast of what is happening. I think the reason I'm getting the error is because that relationship is lost when AutoMapper does its thing.
CODE
Here are the Entities that I think are relevant:
public abstract class Business : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
public virtual ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
public class Supplier : Business
{
public virtual ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public class Address : IEntity
{
public Address()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string Area { get; set; }
public string City { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
public class Contact : IEntity
{
public Contact()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
And here is my ViewModel:
public class SupplierVm
{
public SupplierVm()
{
Addresses = new List<AddressVm>();
Contacts = new List<ContactVm>();
PurchaseOrders = new List<PurchaseOrderVm>();
}
public int Id { get; set; }
[Required]
[Display(Name = "Company Name")]
public string Name { get; set; }
[Display(Name = "Tax Number")]
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
[Display(Name = "Status")]
public bool IsDeleted { get; set; }
public IList<AddressVm> Addresses { get; set; }
public IList<ContactVm> Contacts { get; set; }
public IList<PurchaseOrderVm> PurchaseOrders { get; set; }
public string ButtonText => Id != 0 ? "Update Supplier" : "Add Supplier";
}
My AutoMapper mapping configuration is like this:
cfg.CreateMap<Supplier, SupplierVm>();
cfg.CreateMap<SupplierVm, Supplier>()
.ForMember(d => d.Addresses, o => o.UseDestinationValue())
.ForMember(d => d.Contacts, o => o.UseDestinationValue());
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>()
.Ignore(c => c.Business)
.Ignore(c => c.CreatedOn);
cfg.CreateMap<Address, AddressVm>();
cfg.CreateMap<AddressVm, Address>()
.Ignore(a => a.Business)
.Ignore(a => a.CreatedOn);
Finally, here's my SupplierController Edit Method:
[HttpPost]
public ActionResult Edit(SupplierVm supplier)
{
if (!ModelState.IsValid)
return View(supplier);
_supplierService.UpdateSupplier(supplier);
return RedirectToAction("Index");
}
And here's the UpdateSupplier Method on the SupplierService.cs:
public void UpdateSupplier(SupplierVm supplier)
{
var updatedSupplier = _supplierRepository.Find(supplier.Id);
Mapper.Map(supplier, updatedSupplier); // I lose navigational property here
_supplierRepository.Update(updatedSupplier);
_supplierRepository.Save();
}
I've done a load of reading and according to this blog post, what I have should work! I've also read stuff like this but I thought I'd check with readers before ditching AutoMapper for Updating Entities.
The cause
The line ...
Mapper.Map(supplier, updatedSupplier);
... does a lot more than meets the eye.
During the mapping operation, updatedSupplier loads its collections (Addresses, etc) lazily because AutoMapper (AM) accesses them. You can verify this by monitoring SQL statements.
AM replaces these loaded collections by the collections it maps from the view model. This happens despite the UseDestinationValue setting. (Personally, I think this setting is incomprehensible.)
This replacement has some unexpected consequences:
It leaves the original items in the collections attached to the context, but no longer in scope of the method you're in. The items are still in the Local collections (like context.Addresses.Local) but now deprived of their parent, because EF has executed relationship fixup. Their state is Modified.
It attaches the items from the view model to the context in an Added state. After all, they're new to the context. If at this point you'd expect 1 Address in context.Addresses.Local, you'd see 2. But you only see the added items in the debugger.
It's these parent-less 'Modified` items that cause the exception. And if it didn't, the next surprise would have been that you add new items to the database while you only expected updates.
OK, now what?
So how do you fix this?
A. I tried to replay your scenario as closely as possible. For me, one possible fix consisted of two modifications:
Disable lazy loading. I don't know how you would arrange this with your repositories, but somewhere there should be a line like
context.Configuration.LazyLoadingEnabled = false;
Doing this, you'll only have the Added items, not the hidden Modified items.
Mark the Added items as Modified. Again, "somewhere", put lines like
foreach (var addr in updatedSupplier.Addresses)
{
context.Entry(addr).State = System.Data.Entity.EntityState.Modified;
}
... and so on.
B. Another option is to map the view model to new entity objects ...
var updatedSupplier = Mapper.Map<Supplier>(supplier);
... and mark it, and all of its children, as Modified. This is quite "expensive" in terms of updates though, see the next point.
C. A better fix in my opinion is to take AM out of the equation completely and paint the state manually. I'm always wary of using AM for complex mapping scenarios. First, because the mapping itself is defined a long way away from the code where it's used, making code difficult to inspect. But mainly because it brings its own ways of doing things. It's not always clear how it interacts with other delicate operations --like change tracking.
Painting the state is a painstaking procedure. The basis could be a statement like ...
context.Entry(updatedSupplier).CurrentValues.SetValues(supplier);
... which copies supplier's scalar properties to updatedSupplier if their names match. Or you could use AM (after all) to map individual view models to their entity counterparts, but ignoring the navigation properties.
Option C gives you fine-grained control over what gets updated, as you originally intended, instead of the sweeping update of option B. When in doubt, this may help you decide which option to use.
I searched all stackoverflow answers and google searches. Finally i just added 'db.Configuration.LazyLoadingEnabled = false;' line and it worked perfectly for me.
var message = JsonConvert.DeserializeObject<UserMessage>(#"{.....}");
using (var db = new OracleDbContex())
{
db.Configuration.LazyLoadingEnabled = false;
var msguser = Mapper.Map<BAPUSER>(message);
var dbuser = db.BAPUSER.FirstOrDefault(w => w.BAPUSERID == 1111);
Mapper.Map(msguser, dbuser);
// db.Entry(userx).State = EntityState.Modified;
db.SaveChanges();
}
I've gotten this issue many times and is normally this:
The FK Id on the parent reference doesn't match the PK on that FK entity. i.e. If you have an Order table and a OrderStatus table. When you load both into entities, Order has OrderStatusId = 1 and the OrderStatus.Id = 1. If you change OrderStatusId = 2 but do not update OrderStatus.Id to 2, then you'll get this error. To fix it, you either need to load the Id of 2 and update the reference entity or just set the OrderStatus reference entity on Order to null before saving.
I am not sure if this is going to fit your requirement but I would suggest following.
From your code it surely looks like you are loosing relationship during mapping somewhere.
To me it looks like that as part of UpdateSupplier operation you are not actually updating any of the child details of the supplier.
If that is the case I would suggest to updadate only changed properties from the SupplierVm to the domain Supplier class. You can write a separate method where you will assign property values from SupplierVm to the Supplier object (This should change only non-child properties such as Name, Description, Website, Phone etc.).
And then perform db Update. This will save you from possible messup of the tracked entities.
If you are changing the child entities of supplier, I would suggest to update them independent of suppliers because retrieving an entire object graph from database would require lot of queries to be executed and updating it will also execute unnecessary update queries on database.
Updating entities independently would save lot of db operations and would add to the performance of the application.
You can still use the retrieval of entire object graph if you have to display all the details about the supplier in one screen. For updates I would not recommend update of entire object graph.
I hope this would help resolving your issue.

Lazy loading not working with 1 to 0 or 1 relationship

I have the following table design.
As can be seen here, there is a one to many relationship, with the many on the EpisodePatient side.
Then, I have the following classes.
public class EpisodeModel
{
public int ID { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public int EpisodePatientID { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
I am setting up the relationship, in Entity Framework, to be a one to 0 or many. The reason for this is, I am selecting all EpisodePatients from a View, and I want the Episode to be Lazy loaded when accessed.
This is how I am setting up my relationship.
modelBuilder.Entity<EpisodePatientModel>().HasRequired(r => r.Episode).WithOptional(o => o.EpisodePatient);
I want this to act as a One to zero or many in my code, as an Episode will always have an EpisodePatient, and vice versa.
The problem I am facing is, when I load the EpisodePatient, and try to access the Episode linked item, it is always null, and Lazy loading does not occur.
What am I doing wrong here?
UPDATE
This is how I load the original EpisodePatient items.
this.DbContext.EpisodePatients.AsNoTracking();
I re-created your model but with data annotations like below and it workes fine:
public class EpisodeModel
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public string Name { get; set; }
[Key, ForeignKey("Episode")]
public int EpisodeId { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
Try without AsNoTracking(), because if you use it your context is not tracking and you can't include more data if you need.
And try change to relation one to many.
modelBuilder.Entity<EpisodePatientModel>().HasRequired<Episode>(s => s.Episode).WithMany(s => s.EpisodePatient);

Entity Framework 6 1-1 Navigation Property Always Loads

I Have a Entity Class called Contact:
public class Contact{
public int idContact { get; set; }
public int id_Company { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public string Mobile { get; set; }
public string Email { get; set; }
public bool ShippingLocation { get; set; }
public virtual ICollection<Change> Changes { get; set; }
public virtual ICollection<Entity> Entities { get; set; }
public virtual ICollection<Entity> Entities1 { get; set; }
public virtual Company Company { get; set; }
}
This is all OK. I have LazyLoading enabled in the context.
I wish to find out why, when I retrieve a list of Contacts, the Company property is always filled.
I understand the idea of LazyLoading, and you should specify the navigation properties to load using the Include() extension on the IQueryable (or ensure the Context is still available when accessing the property - otherwise ObjectDisposedException).
I would just like to know why EF is loading the 1-1 automatically, even if I don't specifically ask it to - the side effect of this is I have to now be careful when adding/updating contacts, as I am getting duplicate Company objects in the DB. That is, I have to manually set the Company to null or change its Entity state when updating the related entity.
Edit
This is how I populate the list of contacts:
public Dictionary<int, Contact> GetContacts(Func<T, TKey> selector)
{
using (DbContext db = new Context())
{
return context.Set<Contact>().ToDictionary(selector);
}
}
So, in theory I should get an ObjectDisposedException when I access the Company property, but I don't!

EntityFramwork Many-To-Many Code First

I'm not sure the best way to create this kind of relation ship. I have these two entities for this example.
Person & Address
public class Person
{
public string Name { get; set; }
public virtual ICollection<PersonAddressLink> HomeAddresses { get; set; }
public virtual ICollection<PersonAddressLink> WorkAddresses { get; set; }
}
public class Address
{
public string AddressString {get; set; }
public virtual ICollection<Person> People { get; set; }
}
and a link table, needed because it contains other info.
public class PersonAddressLink
{
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
public int SomeOtherInt { get; set; }
public string SomeOtherString { get; set; }
}
The problem is EF doesn't know how to separate the entities on person.HomeAddresses / person.WorkAddresses. I have tried mergin HomeAddress & WorkAddresses into a single collection like this:
public virtual ICollection<PersonAddressLink> WorkAddresses { get; set; }
but it still won't work.
I'm just looking for advice on how to lay something like this out to get it working with EF Code first.
I hope that makes sense.
Thanks
Late reply but I got the mapping correct by creating the table in SQL Management studio and using the reverse engineer functionality which generated the Code First. I need to use two separate entities.

EF Code First Many to many relation store additional data in link table

How do I store additional fields in the "link table" that is automagically created for me if I have two entities associated as having a many to many relationship?
I have tried going the "two 1 to many associations"-route, but I'm having a hard time with correctly configuring the cascading deletion.
Unless those extra columns are used by some functions or procedures at the database level, the extra columns in the link table will be useless since they are completely invisible at the Entity Framework level.
It sounds like you need to re-think your object model. If you absolutely need those columns, you can always add them later manually.
You will most likely need to expose the association in your domain model.
As an example, I needed to store an index (display order) against items in an many-to-many relationship (Project <> Images).
Here's the association class:
public class ProjectImage : Entity
{
public Guid ProjectId { get; set; }
public Guid ImageId { get; set; }
public virtual int DisplayIndex { get; set; }
public virtual Project Project { get; set; }
public virtual Image Image { get; set; }
}
Here's the mapping:
public class ProjectImageMap : EntityTypeConfiguration<ProjectImage>
{
public ProjectImageMap()
{
ToTable("ProjectImages");
HasKey(pi => pi.Id);
HasRequired(pi => pi.Project);
HasRequired(pi => pi.Image);
}
}
From Project Map:
HasMany(p => p.ProjectImages).WithRequired(pi => pi.Project);
Maps to the following property on project:
public virtual IList<ProjectImage> ProjectImages { get; set; }
Hope that helps
Ben
Suppose there is a many-to-many association between two types: User and Message, and the association class is defined as UserMessageLink with additional properties.
public class User {
public int Id {get;set;}
}
public class Message {
public int Id {get;set;}
}
//The many-to-many association class with additional properties
public class UserMessageLink {
[Key]
[Column("RecieverId", Order = 0)]
[ForeignKey("Reciever")]
public virtual int RecieverId { get; set; }
[Key]
[Column("MessageId", Order = 1)]
[ForeignKey("Message")]
public virtual int MessageId { get; set; }
public virtual User Reciever { get; set; }
public virtual Message Message { get; set; }
//This is an additional property
public bool IsRead { get; set; }
}

Categories

Resources