I have a read-only view (dbo.HRINFO). I built the below object to represent it. I also have a "User" table that contains a FK to this view, though no official constraint can be defined. I'm trying to establish a navigation property so that whenever I have a "User" object, I can access the "UserInfo/view" data.
[Table("dbo.HRINFO")]
public class UserInfo
{
[Key] // But it's not really a key, there is no key, but it is null or unique
public string EMAIL_KEY { get; set; }
... more properties
}
And a table I'm creating that I want to related to the above view:
public class User
{
[Key]
public int Id { get; set; }
public string Email_Key { get; set; } // can be a FK to above view
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual UserInfo UserInfo { get; set; }
... more properties
}
I tried using the fluent API to no avail (the below, as well as many variations):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserInfo>().HasKey(t => t.EMAIL_KEY).ToTable("dbo.HRINFO");
modelBuilder.Entity<User>().HasKey(t => t.Id).HasRequired(t => t.UserInfo);
}
I'm at the point that I'm not what I want to do can be done, since I can't add any constraints to the view. Can I instead put a public virtual UserInfo { get { [sql select * from the view where EMAIL_KEY = this.Email_Key] } } on the User object? Or something similar? How?
This is the best I've come up with. I don't like the new context/inner select. I'm sure it will come back to bite me in the future. I'd welcome other ideas.
public class User
{
[Key]
public int Id { get; set; }
public string Email_Key { get; set; }
[NotMapped]
public virtual UserInfo UserInfo
{
get
{
return new Context().UserInfoes.Single(u => u.EMAIL_KEY == Email_Key);
}
}
}
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.
I have some design-problems based on a lack of best pratices. I have a given Model containing some user information that I cant change as I have read-only access via OData-Service-Call. For example:
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set: }
}
Now I'm interested in adding some user-related information and save this information in a local database using EntityFramework. The final goal is to get an extended model like the following:
public class ExtendedUser
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set: }
public char FavoriteChar { get; set; } // <-- additional Information
}
Because there are many users available and only a very few do have this additional Information (FavoriteChar) it might be a good idea to create another model for saving only the addiotional information. Espcially some values (e.g. email) might change and I do not want to sync those values all the time. So I thought about such a model:
public class FavoriteCharAssignment
{
public int UserId { get; set; }
public char FavoriteChar { get; set; }
}
And now the tricky part: How do I merge those both models (User and FavoriteCharAssignment) to get the ExtendedUser[]?
Users[] users = odata.Users.ToArray();
using(db = new Context())
{
FavoriteCharAssignment[] assignments = db.FavoriteCharAssignments.ToArray();
/*
* Here I need some magic to merge those two arrays to an ExtendedUser[]
*/
}
In SQL-words: I need to do a kindof cross-join between an OData-Serice and the EntityFramwork. :)
I am working on an ASP.NET MVC application and I'm using entity framework and linq.
I am having an issue were when I try to update a record it does not update in the database.
This is the class I'm working with:
public class Customer
{
[Key]
[Required]
public int Id { get; set; }
[Required]
public string firstName { get; set; }
[Required]
public string surname { get; set; }
[Required]
public string userName { get; set; }
[Required]
public string password { get; set; }
[Required]
public bool subscription { get; set; }
public List<int> Articles { get; set; }
public void AddArticle(int id)
{
if (Articles != null)
{
Articles.Add(id);
}
else
{
Articles = new List<int> {id};
}
}
}
I have created a new entry based on the above class and saved it to the database and this has worked fine. I have left the ListArticles null for the time being.
Now I get the record:
var customer = context.Customers.SingleOrDefault(o => o.Id == 1);
Here the customer.Articles == null
Now I add to it:
customer.AddArtical(0);
Looking into this I can see that it has updated the variable customer now I need to save these updates in the database.
I have tried all the examples in here but none seem to save to the DB
Entity Framework 5 Updating a Record
context.Customers.Attach(query);
context.Entry(query).State = EntityState.Modified;
context.SaveChanges();
After this code is finished another area of the project is called and performs the same query:
var customer = context.Customers.SingleOrDefault(o => o.Id == 1);
however the List<int> Articles is still null.
Any ideas?
I suppose you have one entity Article in your project. After all, having just id's of articles and not articles themselves is not that usefull. The point is that this model of yours have just one list of ints, the list of id's and not the list of the articles.
Entity Framework Code First is convention based, so to establish relationships between entities you add reference between the entities themselves and not their id's. Think for a while, you have a list of ints, how EF could know these ints correspond to id's referencing other entities? It's not like that.
If you have this, instead:
public class Customer
{
[Key]
[Required]
public int Id { get; set; }
[Required]
public string firstName { get; set; }
[Required]
public string surname { get; set; }
[Required]
public string userName { get; set; }
[Required]
public string password { get; set; }
[Required]
public bool subscription { get; set; }
public List<Article> Articles { get; set; }
public Customer()
{
this.Articles = new List<Article>();
}
}
Then to add an article you would add the entity itself using the add method from the list collection. In your case, the addition method you write seems superflous. You are using it to avoid null reference exceptions, but if you instantiate the list on the constructor there will be no problems. You would need an adition method if there were some business logic on the addition of an article.
Doing things in this way you are sticking to EF conventions. When you add an article, EF will know what it must be done. Try doing this.
I'm using the MVC4 Internet Application and I am trying to change the account model to use my context. I cannot seem to get it to work. I tried to delete the context and include the user profile in my context and I could log on etc but when I check for profiles by user id it returns a null value.
public DbSet<FightCard> FightCards { get; set; }
public DbSet<Fight> Fights { get; set; }
public DbSet<Fighter> Fighters { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
When I try to do this:
public ActionResult Profile(int id)
{
UserProfile profile = uc.UserProfiles.Find(id);
ViewBag.Name = profile.UserName;
}
I get a null value return. Anyone know how to successfully use the simple membership with a different context?
Solved it there. But to be honest I'm not exactly sure what fixed it. Here is the link I used and one part of it solved my problem.
http://dansgreenshoes.com/2013/03/10/mvc4usertable/