Associated entities not attaching to parent object when saving - entity-framework-4

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?

Related

When using the OData Client for v4 to create/post a new entity we are not getting the auto-generated (auto-incremented) ID/Key back from the service

When using the OData Client (.NET) for v4 to create/post a new entity we are not getting the auto-generated (auto-incremented) ID/Key back from the service. When we create the new entity, we have no ID assigned (it is an 'int' so the value is '0'). After calling SaveChanges the result JSON response has the new auto-assigned id (e.g. '4662'). The issue is that the Entity on the client side still has '0' for its ID (it's not mapped back to the orig. entity).
I also opened the issue on GitHub: https://github.com/OData/odata.net/issues/775
Assemblies affected
Microsoft.Data.OData - v5.7.0
Microsoft.OData.Client - v6.15.0
Microsoft.OData.Core - v6.15.0
Microsoft.OData.Edm - v6.15.0
Reproduce steps
Save a new entity that will automatically have an ID assigned on the server-side (do not set this ID on the client-side)
After saving, check the ID property on your new entity (on the client-side)
Expected result
The newly created (and saved) entity will have the ID updated to match the server-side response (JSON) that came back to the client.
Actual result
JSON response form the server-side has the correct ID, but the Entity on the client-side is never updated with this new information.
In order to support your scenario, some requirements want to be met.
For a full reference, see my sample project at GitHub.
Given a model:
public class Item {
[Key] //define as key
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //used by EF to auto generate Ids and mapping them back on inserts
public int Id { get; set; }
[Required(AllowEmptyStrings = false)]
public string Name { get; set; }
}
An EntityFramwork-DbContext:
public class SampleContext : DbContext {
public SampleContext()
: base("name=SampleContext") {
}
public DbSet<Person> Persons { get; set; }
public DbSet<Item> Items { get; set; }
}
A straight-forward OData-configuration:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Person>("Persons");
builder.EntitySet<Item>("Items");
config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
and a corresponding ODataController:
public class ItemsController : ODataController {
private readonly SampleContext _Db;
public ItemsController() {
_Db = new SampleContext();
}
...
[ResponseType(typeof(CreatedODataResult<Item>))]
public IHttpActionResult Post(Item p) {
if (!ModelState.IsValid)
return BadRequest(ModelState);
var inserted = _Db.Items.Add(p);
_Db.SaveChanges();
//return entity with any server side changes. This would also work for other DB-generated columns.
return Created(inserted);
}
}
Should produce the desired result:
public void AddingItemTransportsServerGeneratedIdBackToClientSideModel() {
var item = new Item() {
Name = Guid.NewGuid().ToString()
};
var container = new Container(new Uri("http://localhost/ODataAutoId/odata"));
container.AddToItems(item);
container.SaveChanges();
var actual = item.Id;
var unexpected = 0;
Assert.AreNotEqual(unexpected, actual);
}
Your actual implementation might vary or miss some of the requirements. In order to help in your particular scenarion, provide more details, as stated in my comment.

EF lookup items being added when they already exist

I'm pretty sure this isn't a duplicate question (or I don't know how to ask it properly) so here goes. I have an ASP.NET MVC project using EF 6.1. In it I have an entity called Member. The Member has a property called Races that can contain one or more Race entities. The Races property on Member:
public virtual ICollection<Race> Races { get; set; }
The Race entity:
public class Race : ILookupListItem {
public int Id { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
[JsonIgnore]
public virtual ICollection<Member> Members { get; set; }
}
The Race entity is what I have always called a lookup.
My issue arises when I try to save the entity after updates have occurred on the client. My SaveMember controller method is below along with my business logic method for actually saving the entities. (To avoid any confusion, InjectFrom is the ValueInjecter method and the JSONResponseError and JSONResponseSuccess classes are our custom wrappers around a JsonResult.)
public JsonResult SaveMemberInfo(MemberInfoModel model) {
try {
// retrieve member
var member = new Member();
member.InjectFrom(model);
var races = new List<Race>();
races.InjectFromList(model.Races);
member.Races = races;
// save member
var savedMember = _members.UpdateMember(member, out errId);
var savedModel = GetMemberChartViewModel(savedMember);
// notify user of success
return new JsonResponseSuccess(savedModel);
}
catch (Exception ex) {
return new JsonResponseError(message);
}
}
The _members.UpdateMember method is below.
public Member UpdateMember(Member contract, out int? errId) {
try {
using (_db = new ApplicationContext(_currentUserID)) {
// updates entity state in the context using the entity's State field from Julie Lerman's example on MSDN.
_db.FixState();
_db.SaveChanges();
errId = null;
return contract;
}
}
catch (Exception ex)
{
// log error to db and return id
errId = LogError(ex, "MemberLogic.UpdateMember");
return null;
}
}
When I get the data contract back from the UpdateMember method, the Races property has all the races saved to the memeber before the update, plus any that I just saved (so it's adding but not doing a diff to determine which items were already there and which were removed). In addition the Races table will now have duplicate entries for the "new" races. My assumption is that this isn't EF's default behavior but I've never worked with a dropdown list that populated a list of items, usually it's just a single property (like Gender or Country). Is there something obvious I'm missing?
Thanks!
EDIT: It just occurred to me that the EntityConfiguration for the Member and Race intersection table would be useful, added below. Obviously, this is only the part I thought to be relevant to the question.
HasMany(t => t.Races).WithMany(t => t.Members).Map(m =>
{
m.ToTable("MemberRaces", "dbo");
m.MapLeftKey("MemberId");
m.MapRightKey("RaceId");
});

MVC5 How to define class with only getters

I'm using VS2013 and building a simple MVC5 app with EF6 (learning C#/MVC)
The app is simple. There's a table and an SP in an SQL database that serves up sequential job nos to users via web page. This SP will also be called by a another app so the biz logic is in the SP.
The SP takes a username and returns a JobNo (PK), which is derived as Max(JobNo) + 1
I'm using DB First (as I don't currently understand enough about migrations to production with code first and nearly everything I will write has to work with existing DB's and existing SP's)
I created the model from the DB using ADO.NET and chose the table and a few SP's. This created the following:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace JobNoServer.Models
{
using System;
using System.Collections.Generic;
public partial class JobNo
{
public int JobNo1 { get; set; }
public System.DateTime CreateDateTime { get; set; }
public string UserName { get; set; }
}
}
The problem I've got is that when I call the SP (user clicks "Get new Job No"), I only want to pass the username. The CreateDateTime will be current datetime (set in SP) and JobNo is determined in SP.
I tried removing the setter in class members:
public int JobNo1 { get; }
but then I get the error "must declare a body because it is not marked abstract or extern", but can't figure out how to fix this.
The other issue I have is that the controller created this Create method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "JobNo1,CreateDateTime,UserName")] JobNo jobNo)
{
if (ModelState.IsValid)
{
db.JobNo.Add(jobNo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(jobNo);
}
But I can't pass a job no, as it doesn't exist until after Create is called. When I remove the JobNo from the add method, I get a message saying there is no overload that takes zero params. When i look at the definition of Add it's some kind of generic class and the create view is saying job no is mandatory
public class DbSet<TEntity> : DbQuery<TEntity>, IDbSet<TEntity>, IQueryable<TEntity>, IEnumerable<TEntity>, IQueryable, IEnumerable, IInternalSetAdapter where TEntity : class
{
Could someone point me in the right direction of how to have a create method that takes just the UserName, calls the SP and gets the return value?
There are lot's of way to do this let's look at a few options:
//add a post method to the JobNo class (makes controller logic dead simple)...
public partial class JobNo
{
public int JobNo1 { get; set; }
public System.DateTime CreateDateTime { get; set; }
public string UserName { get; set; }
public Task Post(){
using (var db = new My Entities){
//any issues found here are purely Data access related!
db.JobNo.Add(UserName);
await db.SaveChangesAsync();
}
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(JobNo vm)
{
if (ModelState.IsValid)
{
await vm.Post();
RedirectToAction("Index");
}
return View(vm);
}
But first you have to solve the problem of the SP wanting more than just 1 parameter right? Did you map the SP using DB first? Is JobNo class from the SP? There seems to be an "impedance mismatch"
By the looks of it you have your db mapped successfully, however if your db relies on Stored procedures to create PK instead of it's default functionality MVC can get a little confused, which is where you're having troubles.
Have you tried mapping the stored procedure to your context. Basically right click in your edmx diagram and 'update from database'. Instead of adding your tables(which you already did), you'll find the SP under the Stored Procedures and Functions section and add it. Done. Here's the MS how to...
If you map your stored procedure you can simply do something like the following
public JobNo myCreate( DateTime createDT, string uName)
{
int jobNo = 0;
using(Context db = new Context())
{
jobNo = db.yourSP(youParams probably uName, createDT);
}
return new JobNo() { JobNo1 = jobNo, CreateDateTime = createDT, UserName = uName};
}
Then you can reference the Create function in your question and just pass it the JobNo object you just created.
If all else fails and you can't seem to map your SP, remember you could always default to exectuting SQL directly against your context, and call your SP that way.
public JobNo myCreate( DateTime createDT, string uName)
{
int jobNo = 0;
using(Context db = new Context())
{
jobNo = db.Database.SqlQuery<int>("YourProcName #param1, #param2",
new SqlParameter("param1", createDT.toString()),
new SqlParameter("param2", uName));
}
return new JobNo() { JobNo1 = jobNo, CreateDateTime = createDT, UserName = uName};
}

Updating many-to-many relationship entity framework

I have problem with updating entites that have many-to many relationship. Below my User and category class:
public class User : IEntity
{
[Key]
public virtual long Id { get; set; }
private ICollection<Category> _availableCategories;
public virtual ICollection<Category> AvailableCategories
{
get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
set { _availableCategories = value; }
}
}
public class Category : IEntity
{
[Key]
public long Id { get; set; }
/// <summary>
/// Full name or description of a category
/// </summary>
[StringLength(255)]
public string FullName { get; set; }
}
This is code snippet from my repository
public override void Edit(User user)
{
var dbUser = _context.Users.Include(x => x.AvailableCategories)
.Single(x => x.Id == user.Id);
var categories = _context.Categories;
dbUser.AvailableCategories.Clear();
foreach (var cat in user.AvailableCategories)
{
dbUser.AvailableCategories.Add(cat);
}
_context.Entry(dbUser).State = EntityState.Modified;
}
However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.
How can I update User so that I change only categories that already exist in the database?
User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).
When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.
Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:
public List<long> SelectedCategories { get; set; }
But, if you insist on using the entity directly, you can get much the same result by simply doing:
var selectedCategories = user.AvailableCategories.Select(m => m.Id)
Once you have the ids:
var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));
And then finally set that on your user:
dbUser.AvailableCategories = newAvailableCategories;
I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.
You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.
Also, I might use a linking entity. Possibly something like this:
public class UserCategory
{
public User User {get;set;}
public Category Category {get;set;}
}
And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.

How to prevent referenced object's version column updating when creating a new object in EF 4.1 code first?

I've been trying to map a one-to-many, one-way relationship using EF 4.1 code-first, e.g. User has Address but Address knows nothing about User. This is straightforward to implement either with a ForeignKey attribute or a fluent api (shown in included code).
The problem comes when adding a Version (byte[]) column with a Timestamp attribute on both mapped classes. If we now create an instance of a User that has a reference to an existing (in the db) Address and add it to the context, upon calling SaveChanges a database profiler will show two database queries, one that is the User insert and the other is an update to the Address table to change the Version. Not what I want. If I've modelled no relationship in my domain then I don't want any version change either. I only want a version change on Address if I change an Address instance.
I suspect that because the mapping is using HasMany(), internally the EF DbContext believes there is a collection that needs to be satisified and as the collection has changed (by adding a new User) it automagically updates the version of Address. All this despite the fact Address has no collection property of type ICollection<User>.
So to my question. What mapping do I need to put in place for the relationship to maintain the class structures as they are without having the Address version change when I add a new User?
EDIT:
I've found that the only way I can prevent the update of the version on Address is to reduce the mapping to an HasRequired(a => a.Address) only and no longer have the AddressId foreign key on the User class. It seems if the foreign key "property" is on User either explicit mapping or convention mapping will ensure the version of Address is updated.
I would prefer to apply some extension to HasRequired to tell the context how to treat the relationship rather than having to remove the foreign key property entirely.
Here's the code I use to demonstrate the problem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace DbTest
{
class Program
{
static void Main(string[] args)
{
Address address = null;
// Make sure we have one address to test with
using (var context = new DemoContext())
{
address = context.Addresses.FirstOrDefault();
if (address == null)
{
address = new Address { Street = "My Street" };
context.Addresses.Add(address);
context.SaveChanges();
}
}
byte[] version = address.Version;
using (var context = new DemoContext())
{
// Uncomment to test attaching
// context.Addresses.Attach(address);
address = context.Addresses.FirstOrDefault();
var user = new User { Name = "Mark", Address = address };
context.Users.Add(user);
context.SaveChanges(); // Results in new user inserted and a version update to the Address referenced object
}
using (var context = new DemoContext())
{
var address2 = context.Addresses.FirstOrDefault();
Console.WriteLine("Versions: {0}, {1}", BitConverter.ToString(version), BitConverter.ToString(address2.Version));
}
Console.ReadLine();
}
}
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
public int AddressId { get; set; }
// [ForeignKey("AddressId")]
public Address Address { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
public class Address
{
[Key]
public int AddressId { get; set; }
public string Street { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
public class DemoContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasRequired(a => a.Address)
.WithMany()
.HasForeignKey(u => u.AddressId);
}
}
}
A workaround:
Leverage your exposed foreign key and replace this code...
address = context.Addresses.FirstOrDefault();
var user = new User { Name = "Mark", Address = address };
...by:
var addressId = context.Addresses.Select(a => a.AddressId).FirstOrDefault();
var user = new User { Name = "Mark", AddressId = addressId };
This doesn't change the timestamp of the address entity.
I don't think that there is any mapping option to avoid the UPDATE statement of the address in your original code. I would follow your hypothesis that EF considers your code as a change of a relationship between users and address and therefore updates the address, no matter if the user collection is exposed in the Address model or not.
I've found that the only way I can prevent the update of the version on Address is to reduce the mapping to an HasRequired(a => a.Address) only and no longer have the AddressId foreign key on the User class. It seems if the foreign key "property" is on User either explicit mapping or convention will ensure the version of Address is updated.
I would prefer to apply some extensino to HasRequired to tell the context how to treat the relationship rather than having to remove the foreign key property entirely.

Resources