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.
Related
I need to migrate an existing production .NET Core Web API from using EF Core with the PostgreSQL provider to the MariaDB provider.
The proccess of changing the actual provider simple: install the Pomelo.EntityFrameworkCore.MySql nuget package, when initializing the DbContext in dependency injection, replace the UseNpgsql(...) with UseMysql(...) and boom! done.
The problem is how to migrate the existing data?
The biggest issue I've ran into is that the schema isn't a 1:1 match between PostgreSQL and MariaDB. For example, while PostgreSQL supports string arrays, MariaDB doesn't. So I need to use a ValueConverter in EF Core. That's all good, but it makes the two databases event harder to migrate using regular SQL scripts. Due to these issues, I've decided to try migrating the data using EF Core. However, that proved to be a beast of its own.
How to copy the entire database (including circular relationships) with EF Core?
Here's an example of what my entities look like:
public class User
{
[Key]
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> Claims { get; set; } = new List<string>();
public User Employer { get; set; }
public List<User> Employees { get; set; } = new List<User>();
}
public class Order
{
[Key]
public Guid OrderId { get; set; }
public DateTime Created { get; set; }
public User Creator { get; set; }
public User AssignedHandler { get; set; }
public List<OrderState> States { get; set; } = new List<OrderState>();
}
public class OrderState
{
[Key]
public Guid OrderStateId { get; set; }
public User CreatedBy { get; set; }
public DateTime Timestamp { get; set; }
public OrderStatus Status { get; set; }
public string Message { get; set; }
}
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum OrderStatus
{
Created = 1,
Processing = 2,
Canceled = 3,
Completed = 4
}
Original idea
Here's how I'm trying to migrate the data
PostgreSqlDbConext _sourceDb; //old context, connects to existing db
MySqlSqlDbConext _targetDb; //new context, connects to an empty new db
var data = _sourceDb.Orders.AsNoTracking()
.Include(a => a.Creator)
.Include(a => a.AssignedHandler)
.Include(a => a.States)
.ToList();
_targetDb.Orders.AddRange(data);
_targetDb.SaveChanges();
But I get this exception. From what I understand, since the User with ID 07744349-7a0e-4128-a878-9a30e126e5f8 is a creator of multiple orders and I'm selecting it from the source with AsNoTracking(), I'm basically trying to create the same user twice and that causes the problem.
System.InvalidOperationException: The instance of entity type 'User' cannot be tracked because another instance with the key value '{UserId: 07744349-7a0e-4128-a878-9a30e126e5f8}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Also tried this
I've also tried detaching all the entities using information from this guide: Cloning the Entity object and all related children using the Entity Framework
var data = _sourceDb.Orders.AsNoTracking()
.Include(a => a.Creator)
.Include(a => a.AssignedHandler)
.Include(a => a.States)
.ToList();
foreach (var item in data)
{
var cloned = item.Clone();
cloned.ClearEntityReference(false);
_targetDb.Entry(cloned).State = EntityState.Detached;
_targetDb.Orders.Add(cloned);
}
_targetDb.SaveChanges();
Which still ends with the following error:
The instance of entity type 'User' cannot be tracked because another instance with the key value '{UserId: b824fe09-e80d-4f16-a620-e72592f1a1ad}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Removing the include statements makes the error go away, but I lose all the relationships in the proccess. Basically, all orders will be imported, but with no relation to their Creators, AssignedHandlers or States.
What to do?
Any helpful ideas are appreciated deeply!
I found two solutions for this issue.
1. Use Entity Framework Extensions
Using the Entity Framework Extensions' BulkInsert method copies all the data, including the related entities and is very easy to use. I've ended up using these parameters with it:
InsertKeepIdentity = true - to keep my existing IDs
IncludeGraph = true - to include related entities
Here's what the copy code looks like:
using Z.EntityFramework.Extensions;
var items = _sourceDb.Orders
.Include(a => a.Creator)
.Include(a => a.AssignedHandler)
.Include(a => a.States);
_targetDb.BulkInsert(items, o =>
{
o.InsertKeepIdentity = true;
o.IncludeGraph = true;
});
2. Use linq2db.EntityFrameworkCore
as suggested by #SvyatoslavDanyliv in the comments.
For this to work, I needed to add the foreign key IDs to my models. Navigation properties weren't enough, as mentioned in this GitHub issue.
Edited entities:
public class User
{
[Key]
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> Claims { get; set; } = new List<string>();
public Guid EmployerId { get; set; }
public User Employer { get; set; }
public List<User> Employees { get; set; } = new List<User>();
}
public class Order
{
[Key]
public Guid OrderId { get; set; }
public DateTime Created { get; set; }
public Guid CreatorId { get; set; }
public User Creator { get; set; }
public Guid AssignedHandlerId { get; set; }
public User AssignedHandler { get; set; }
public List<OrderState> States { get; set; } = new List<OrderState>();
}
public class OrderState
{
[Key]
public Guid OrderStateId { get; set; }
public Guid CreatedById { get; set; }
public User CreatedBy { get; set; }
public DateTime Timestamp { get; set; }
public OrderStatus Status { get; set; }
public string Message { get; set; }
}
Copy
using LinqToDB.EntityFrameworkCore;
using LinqToDB.Data;
var options = new BulkCopyOptions { KeepIdentity = true };
_targetDb.BulkCopy(options, _sourceDb.Users.AsEnumerable());
_targetDb.BulkCopy(options, _sourceDb.Orders.AsEnumerable());
_targetDb.BulkCopy(options, _sourceDb.OrderStates.AsEnumerable());
I am using Entity Framework Core 2.0.1 and I have the following models
public class Article
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Slug { get; set; }
public int Approved { get; set; }
public DateTime ArticleDate { get; set; }
// ... some other fields
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategory
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
//... soem other fields
[ForeignKey("ArticleCategoryParent")]
public int? ArticleCategoryParentID { get; set; }
public virtual ArticleCategory ArticleCategoryParent { get; set; }
public virtual ICollection<ArticleCategory> SubCategories { get; set; }
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategoryRelation
{
[Column(Order = 0)]
public int ArticleId { get; set; }
public Article Article { get; set; }
[Column(Order = 1)]
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory {get; set;}
}
Every article belongs to one or more categories. Categories might have parent category.
I want to get from database last two articles (where Approved = 1) with related category details, for each category that belongs to a parent category which id is given as input.
I have tried but with no success. I can't filter results of an .Include() entity. Is it possible... or I don't know how to do it?
All my data are accessed through entity framework with appContext (the context used to get entities from database). Can I achieve what I want through entity framework core (lambda expression is preferred over Linq if possible), or should I use ADO.NET library (which I know how to execute custom queries).
P.S. I want to get data only to show in the view... no edit is needed.
You don't actually need to include here at all, as far as I can tell. Whenever you use data from a nav property, EF will go get the data from that table, as best it can filter it.
var CategoriesUnderParent = AppContext.ArticleCategories
.Where(c => c.ArticleCategoryParent == {parent});
foreach(var category in CategoriesUnderParent)
{
var ArticlesAllowed = category.ArticleCategoryRelations
.Where(acr => acr.Article.Approved == 1).Select(a => a.Article);
var ArticlesPicked = ArticlesAllowed
.OrderByDescending(ar => ar.ArticleDate)
.Take(2);
// Do something with your data
}
I'm using entity framework code first approach
I have a class
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
and a class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
When the database is created I get one table Movies with Id, Title, Director_Id and a table Person with Id and Name.
I expect to have a table Movies_Persons with columns Movie_Id and Actor_Id
How can I achieve this?
Your Problem is, that you don`t tell the Person Class, that there can be multiple Movies per person.
So by adding the following line in your person class:
public virtual ICollection<Movie> Movies { get; set; }
Your entity knows that both your classes can have multiple references to the other class.
To fulfill this requirement Entity Framework will create a third table with Movie_ID and Person_ID.
If you want more informations just look for:
Entity Framework - Many to many relationship
or follow this link:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
You can check out the other articels on that page too, if you are new to entity framework.
UPDATE:
Sorry i missed, that you are already have another reference to your person table.
Here you have to tell your entity framework, which way you want to reference the two tables by fluent api.
Check out this stackoverflow answer. That should do the trick.
You have to insert this code into your OnModelCreating Function of your DbContext Class.
So your final code should look like this:
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public virtual Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Movie> Movies_Actors { get; set; }
public virtual ICollection<Movie> Movies_Directors { get; set; }
}
And in your OnModelCreating add following code:
modelBuilder.Entity<Movie>()
.HasMany(a => a.Actors)
.WithMany(a => a.Movies_Actors)
.Map(x =>
{
x.MapLeftKey("Movie_ID");
x.MapRightKey("Person_ID");
x.ToTable("Movie_Actor");
});
modelBuilder.Entity<Movie>()
.HasRequired<Person>(s => s.Director)
.WithMany(s => s.Movies_Directors);
I don't have the possibility to test the code, but that should do the trick.
If you have to do some adjustments to make it work, plz add them in the comments, so other ppl can benefit from it.
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 walked through a couple of tutorials and it seems they all leave out how you can utilize the logged in user to store information to a database. To help me illustrate my point, here is a model I've been using.
public class Note
{
public int ID { get; set; }
public int UserId { get; set; }
public string Text { get; set; }
}
This each user can write a note to the database. When I created the CRUD controller for this model I then updated the UserId property to the WebSecurity.CurrentUserId when doing Update/Create. Then when retrieving data back I filter the notes using Where in the linq expression. For some reason this just feels wrong.
Trolling through even more examples I came across someone doing it like so.
public class Note
{
public int ID { get; set; }
public virtual UserProfile User { get; set; }
public string Text { get; set; }
}
public class NoteDbContext : DbContext
{
public DbSet<Note> Notes { get; set; }
}
This looks a lot cleaner since the models are properly linked in C#. And wow, it actually builds! So now in my controller I will first get the user object from the database, then using a Where list their notes.
//First get the logged in user
var user = dbUser.UserProfiles.Where(x => x.UserId == WebMatrix.WebData.WebSecurity.CurrentUserId).First();
//Now get all their notes
var notes = db.Notes.Where(x => x.User == user);
However, this unexpectedly fails. So could someone please provide a sample of a good way to store the UserProfile object against other objects in the database? Basically, I just need a good example that shows now the UserProfile object can be linked to a Note object, and how you should properly query for Notes of a specific UserId.
The way you've defined your relationship, is that you are creating a one-to-one relationship between a Note and a User. I would expect that a user can have multiple notes, based on the query that you're having trouble with. Thus, in order to create a one-to-many between a user and their notes, you should create a collection on your UserProfile object. For instance,
public class UserProfile
{
...
public List<Note> Notes {get; set;}
}
...and to query, loading your Notes associated with that user,
var user = myUsers.Include(n=>n.Notes)
.Single(x => x.UserId == WebMatrix.WebData.WebSecurity.CurrentUserId);
Each user can have many notes, right? If so, change your class like this:
public class Note
{
public int ID { get; set; }
public int UserId { get; set; }
public string Text { get; set; }
public virtual UserProfile User { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
public UserProfile()
{
this.Notes = new HashSet<Note>();
}
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Note> Notes{ get; set; }
}
Now, have users and notes connecting correctly. So, you can easily achive your goal like the following. You also don't need to struggle with WebMatrix.WebData.WebSecurity to get the current user! just use User.Identity.Name :
// ...
var notes = db.Notes.Where(x => x.User.UserName == User.Identity.Name).AsQueryable();