MVC3 EF4 duplicates foreign key object on save - c#

I'm using MVC3 with EF4 code-first. I have the following model:
public class Order {
public int ID { get; set; }
[Required]
public float Price { get; set; }
[Required]
public int PayMethodId { get; set; }
public PayMethod PayMethod { get; set; }
public int? SpecificEventId { get; set; }
public SpecificEvent SpecificEvent { get; set; }
public int? SeasonalTicketId { get; set; }
public SeasonalTicket SeasonalTicket { get; set; }
}
When I try to save an Order object with specificEventId = 2 and specificEvent = X, a new SpecificEvent object is created in the DB, even though there's already a specific event X with ID 2 in the DB. When i try with specificEventId = 2 and specificEvent = null I get a data validation error.
What am I doing wrong? I want SpecificEvent and SeasonalTicket to be nullable, and I don't want EF4 to create a new instance of these objects in the DB whenever I save 'Order'.
Update
This is my code for saving Order in the DB:
public void SaveOrder(Order order)
{
Order fromDb = null;
// If editing an existing object.
if ((fromDb = GetOrder(order.ID)) != null)
{
db = new TicketsDbContext();
db.Entry(order).State = System.Data.EntityState.Modified;
db.SaveChanges();
}
// If adding a new object.
else
{
db.orders.Add(order);
db.SaveChanges();
}
}
When I save, I do reach the else clause.

The real question is, where did you get the instance of X from? It appears as though EF has no knowledge of this instance. You either need to fetch the already existing SpecificEvent through EF and use the proxy it returns to set your navigation property, or else tell EF to "attach" X, so that it knows what your intent is. As far as EF knows, it appears, you are trying to send it a new instance with a conflicting Id, so it is properly issuing the error.

Related

How can I specify to add item to the database without also adding the foreign key?

I have the following Models:
public interface Item
{
public int Id { get; set; }
}
public class ComponentOwner : Item
{
public int Id { get; set; }
public Component Component { get; set; }
public AppUser? User { get; set; }
public DateTime ModifiedDate { get; set; }
public AppUser? UpdatedBy { get; set; }
}
public class AppUser : IdentityUser
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
}
and the following async Task that saves the item to the database:
private async Task<Item> SaveItem(Item item)
{
Item updatedItem = null;
using var context = _dbContextFactory.CreateDbContext();
try
{
if (item.Id == 0)
{
context.Add(item);
await context.SaveChangesAsync();
}
When I Save a new ComponentOwner, context.Add(Item) adds the item, but also tries to add a new 'AppUser' at the same time. This causes issues because the AppUser is already created.
Is there a way that I can specify to add the ComponentOwner but not the AppUser?
as soon as 'Context.Add(item)' is hit, it wants to add an AppUser as well as the Component. I only want it to add the ComponentOwner however..
EF Core relies on tracking to determine what to do with entities. In this case it seems that item.User is not tracked, so EF tries to add it. There are multiple possible solution to this. To name a few:
If you are sure that user exists, you can just attach the entity:
if(item.User is not null)
context.Users.Attach(item.User); // or just context.Attach(item.User);
Fetch user from database and assign it to the root entity:
if (item.User is not null)
{
var user = context.Users.FirstOrDefault(u => u.Id == item.User.Id); // TODO: handle null
item.User = user;
}
Use Find:
Finds an entity with the given primary key values. If an entity with the given primary key values is being tracked by the context, then it is returned immediately without making a request to the database. Otherwise, a query is made to the database for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found, then null is returned.
if (item.User is not null)
{
var user = context.Users.Find(item.User.Id); // TODO: handle null
item.User = user;
}

Can't insert entity with foreign key in Entity Framework

I'm building Backend for Mobile Application with ASP.NET MVC Framework.
I have two Objects:
public class CarLogItem : EntityData
{
public CarLogItem(): base()
{
Time = DateTime.Now;
}
public DateTime Time { get; set; }
public int RPM { get; set; }
public int Speed { get; set; }
public int RunTime { get; set; }
public int Distance { get; set; }
public int Throttle { get; set; }
[ForeignKey("Trip")]
public String Trip_id { get; set; }
// Navigation property
public TripItem Trip { get; set; }
}
and
public class TripItem : EntityData
{
public TripItem() : base()
{
UserId = User.GetUserSid();
StartTime = DateTime.Now;
logItems = new List<CarLogItem>();
}
public string UserId { get; set; }
public List<CarLogItem> logItems {get;set;}
public DateTime StartTime { get; set; }
}
and I have controller, which add new CarLogItem to database.
public class CarLogItemController : TableController<CarLogItem>
{
// POST tables/CarLogItem
public async Task<IHttpActionResult> PostCarLogItem(CarLogItem item)
{
var lastItem = db.CarLogItems.OrderByDescending(x => x.Time).FirstOrDefault();
//lastItem = (Query().Where(logitem => true).OrderBy(logitem => logitem.Time)).Last();
//checking if lastItem.Trip isn't null because
// I have entities with Trip field is null, but all of them should have it.
if (lastItem != null && lastItem.Trip != null && item.RunTime > lastItem.RunTime)
{
item.Trip = lastItem.Trip;
}
//In order to test adding of new TripItem entity to database
// I compare item.RunTime with 120, so it always true
else if (lastItem == null || item.RunTime < 120) // < lastItem.RunTime)
{
var newTrip = new TripItem();
item.Trip = newTrip;
}
else
{
throw new ArgumentException();
}
CarLogItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
}
When I'm trying to add new CarLogItem with Trip = null it's ok, but when Trip is particular object it fails with following Exception:
The entity submitted was invalid: Validation error on property 'Id': The Id field is required
How properly to add new CarLogItem with nested TripItem?
I think that you need to populate the Id property on your TripItem, e.g.
var newTrip = new TripItem(){ Id = Guid.NewGuid() }
You need a primary key field in every entity class, like Id or CarLogItemId (ClassName + "Id"). Or just have a property with [Key] attribute:
[Key]
public string/int/Guid/any-db-supported-type MyProp { get; set; }
Entity Framework relies on every entity having a key value that it
uses for tracking entities. One of the conventions that code first
depends on is how it implies which property is the key in each of the
code first classes. That convention is to look for a property named
“Id” or one that combines the class name and “Id”, such as “BlogId”.
The property will map to a primary key column in the database.
Please see this for more details.
I also suspect this to be a problem:
public Lazy<CarLogItem> logItems { get; set; }
You don't have to mark navigation property as Lazy<>. It is already lazy (unless you have configuration that disables lazy loading). Please try to remove Lazy<> and see if it works this way.

ViewModel Object Convert to Entity Framework Object

Goal: to save ViewModel object by Entity Framework. I have UserViewModel object which has list of UnitViewModel. Then, I have a UserAdapter class which converts UserViewModel into Entity Framework User object (see Convert()below how).
Now, my question is how do I convert this list of UnitViewModel to its corresponding Entity Framework Unit list? - Do I have to get each object from DB Context by calling something like context.Units.Where(u=>myListofUnitIDs.Contains(u.UnitID))?
public class UserViewModel
{
public Guid? UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public DateTime? CreateTime { get; set; }
public List<UnitViewModel> UserUnits { get; set; }
}
public class UnitViewModel
{
public Guid UnitID { get; set; }
public string Name { get; set; }
public int? SortIndex { get; set; }
public DateTime CreateTime { get; set; }
public bool Assigned { get; set; }
}
public class UserAdapter
{
public static User Convert(UserViewModel userView)
{
User user;
if (userView.UserID.HasValue)
{
using (var provider = new CoinsDB.UsersProvider())
{
user = provider.GetUser(userView.UserID.Value);
}
}
else
{
user = new User();
}
user.FirstName = userView.FirstName;
user.LastName = user.LastName;
user.Password = StringHelper.GetSHA1(userView.Password);
user.UserName = user.UserName;
user.CreateTime = DateTime.Now;
// Problem here :)
// user.Units = userView.UserUnits;
return user;
}
}
UPDATE: The main concern here is that I have to retrieve each Unit from database to match (or map) it with ViewModel.Unit objects, right? Can I avoid it?
For your information, this operation is called as Mapping mainly. So, you want to map your view model object to the entity object.
For this, you can either use already existed 3rd party library as AutoMapper. It will map properties by reflection which have same name. Also you can add your custom logic with After method. But, this approach has some advantages and disadvantages. Being aware of these disadvantages could help you to decide whether you must use this API or not. So, I suggest you to read some articles about advantages and disadvantages of AutoMapper especially for converting entities to other models. One of such disadvantages is that it can be problem to change the name of one property in the view model in the future, and AutoMapper will not handle this anymore and you won't get any warning about this.
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit = Mapper.Map<UnitViewModel, UserUnit>(item);
user.Units.Add(userUnit);
}
So, I recommend to write your custom mappers.
For example, I have created a custom library for this and it maps objects lik this:
user.Units = userView.UserUnits
.Select(userUnitViewModel => userUnitViewModel.MapTo<UserUnit>())
.ToList();
And I am implementing these mapping functions as:
public class UserUnitMapper:
IMapToNew<UnitViewModel, UserUnit>
{
public UnitViewModel Map(UserUnit source)
{
return new UnitViewModel
{
Name = source.Name,
...
};
}
}
And then in runtime, I am detecting the types of the objects which will be used during mapping, and then call the Map method. In this way, your mappers will be seperated from your action methods. But, if you want it urgently, of course you can use this:
foreach(var item in userView.UserUnits)
{
// get the mapped instance of UnitViewModel as Unit
var userUnit= new UserUnit()
{
Name = item.Name,
...
};
user.Units.Add(userUnit);
}

Converting a string into a List and then using Entity Framework to insert into Database

I am converting a string into a list and then trying to use entity framework to insert it into a DB. The issue that I am having is that I don't know how to save the changes to the DB.
This is the code that I am trying to use and is where the string is converted to a list:
if (intCounter == 0)
{
return JsonConvert.DeserializeObject<List<foo>>(jsonString).Cast<T>().ToList();
}
Then in a seperate class below.
ConvertJson.Convert<CouncilEvent>(strResponseJSONContent, intCounter);
The Entity Framework Model that I am trying to use for the list.
namespace foo.Models
{
public partial class foo
{
public foo()
{
this.EventDates = new List<EventDate>();
}
public System.Guid foo_PK { get; set; }
public string EntityID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public bool Adult { get; set; }
}
}
The class foo contains properties that match those in the string.
It is this foo that I am then trying to insert into the DB. foo is also part of my Entity Framework model.
I have never used a list in this situation before and I thought it would just be a matter of using db.SaveChanges() but that doesn't seem to work. Where would I place the necessary lines of code such as using (db = new contextFoo) and db.SaveChanges(). Also do I need to add the items? I haven't because I thought I was already adding them to the class and therefore didn't need to do this manually?
db.SaveChanges() will only 'update' your database to what was changed. So, you need to add something to the database, and then call SaveChanges() for it to work.
You can loop the list to add the objects to the context, then call SaveChanges()...
var councilEvents = ConvertJson.Convert<CouncilEvent>(strResponseJSONContent, intCounter);
using (var db = new contextFoo())
{
foreach (var councilEvent in councilEvents)
{
db.CouncilEvents.Add(councilEvent);
}
db.SaveChanges();
}

Database items getting deleted when referencing them multiple times

I am creating a simple blogging application to get .NET MVC 4 down and I am having a problem. Everything works except for when I try to tag a blog using an array of strings for each blog like so:
public class BlogEntry
{
public List<Comment> BlogComments { get; set; }
public virtual List<String> RawTags { get; set; }
public virtual List<Tag> BlogTags { get; set; }
public virtual User Author { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime DatePosted { get; set; }
[Key]
public int Id { get; set; }
public bool IsAcceptingComments { get; set; }
public bool IsVisible { get; set; }
public DateTime LastEdited { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int RefCount { get; set; }
}
Upon creating a blog and tagging it, I save tags into the BlogEntry model using this:
[HttpPost]
public int Create(string data)
{
if (data != null)
{
BlogEntry newBlog = JsonConvert.DeserializeObject<BlogEntry>(data);
newBlog.Author = Session["user"] as User;
newBlog.AuthorId = newBlog.Author.Id;
newBlog.IsVisible = true;
newBlog.IsAcceptingComments = true;
newBlog.LastEdited = DateTime.Now;
newBlog.DatePosted = DateTime.Now;
newBlog.BlogTags = new List<Tag>();
foreach (String s in newBlog.RawTags)
{
// First check to see if the tag already exists
Tag check = Db.Tags.Where(m => m.Name == s).FirstOrDefault();
if (check != null)
{
check.RefCount++;
newBlog.BlogTags.Add(check);
Db.Tags.Attach(check);
Db.Entry(check).State = System.Data.Entity.EntityState.Modified;
Db.SaveChanges();
}
else
{
// Create a new tag
Tag newTag = new Tag();
newTag.Name = s;
newTag.RefCount = 1;
newBlog.BlogTags.Add(newTag);
Db.Tags.Add(newTag);
}
}
Db.BlogEntries.Add(newBlog);
Db.SaveChanges();
return newBlog.Id;
}
return -1;
}
First I do a check to see if a tag already exists.. If it does, I try to add the same tag, check to the newBlog object. I would have thought that this would just save a reference to this Tag object in the DbSet, however, if I create multiple blogs posts with the tag "html" and then run a query to see what blogs have the html tag, only the most recently tagged blog retains this value.... What can I do so that I can have multiple BlogEntry objects with the same Tag object in the database?
I don't have my dev machine in front of me right now, so this is just a guess, but I figure it's better than making you wait until tomorrow...
I don't think you need the last 3 lines in your if(check!=null) and in fact, I wonder if they aren't messing you up:
Db.Tags.Attach(check);
Db.Entry(check).State = System.Data.Entity.EntityState.Modified;
Db.SaveChanges();
You shouldn't need to attach because you got it from the Db object already, so it should already be being tracked. This means you don't need to change the state and as for the SaveChanges, you are going to do that below.
And now another disclaimer: I've done some work with Entity Framework (version 6, if you want to know), but not with MVC, so it may be different, but my understanding is that it is better to create a new DbContext for each set of instructions, rather than having a class variable that just tracks running changes. I'm not sure if that is what you are doing or not, but it sort of looks that way from this code sample. Assuming that is relevant in MVC, you may consider creating a new DbContext (Db) at the top of your create method.
Let me know how it goes--if this doesn't help, I'll delete this answer.
First you would have to update the Tag class so that it can track its registered blog entries itself. Here the BlogEntry and Tag classes have a many-to-many relationship. So the Tag class would look like below:
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int RefCount { get; set; }
public virtual List<BlogEntry> BlogEntries { get; set; } // MODIFICATION
}
Now you have to add the blog entry to all of its tags for back referencing to meet your query in an easy way. Look for the modifications I have made in the for-loop below:
foreach (String s in newBlog.RawTags)
{
// First check to see if the tag already exists
Tag check = Db.Tags.Where(m => m.Name == s).FirstOrDefault();
if (check != null)
{
check.RefCount++;
check.BlogEntries.Add(newBlog); // MODIFICATION
newBlog.BlogTags.Add(check);
Db.Tags.Attach(check);
Db.Entry(check).State = System.Data.Entity.EntityState.Modified;
Db.SaveChanges();
}
else
{
// Create a new tag
Tag newTag = new Tag();
newTag.Name = s;
newTag.RefCount = 1;
newTag.BlogEntries = new List<BlogEntry>(); // MODIFICATION
newTag.BlogEntries.Add(newBlog); // MODIFICATION
newBlog.BlogTags.Add(newTag);
Db.Tags.Add(newTag);
}
}
To see what blogs have the html tag, you just have to query on the Tag class and search through the BlogEntries to get the desired blogs. Good luck!

Categories