My issue turned out to be having two context's. I reworked my code a bit to only have one context and my issue went away.
I have a User which has a list of UserContact's which itself has a ContactOption. Its a fairly simple 1 to many, many to 1 with the UserContact table in the middle.
If I pull the user out of the db and create a new UserContact, but set the ContactOption to an existing item (which I've pulled out of the db), when I SaveChanges, entity framework creates a new ContactOption in the database that is essentially a duplicate of the one I added to the UserContact (with the exception that it gets a new id).
I've battled with this for several hours and can't figure this out. Any ideas?
I am using a Repository pattern for my database queries, but I have ensured they are sharing the same context.
I pull the user out of the database with this:
var user = _context.Users.Include("UserContacts.ContactOption")
.Where(id => id == 1);
And contact options are pulled out with:
var co = _context.ContactOptions.FirstOrDefault(c => c.Id == id);
And I add the ContactOption to the UserContact like so:
var contactOption = _context.ContactOptions.FirstOrDefault(c => c.Id == someId);
var contact = new UserContact { ContactOption = contactOption };
contact.Data = "someData";
user.UserContacts.Add(contact);
My model looks like this:
public class User
{
[Key]
public int Id { get; set; }
public virtual ICollection<UserContact> UserContacts { get; set; }
}
public class UserContact
{
[Key]
public int Id { get; set; }
[Required]
public User User { get; set; }
[Required]
public ContactOption ContactOption { get; set; }
public string Data { get; set; }
}
public class ContactOption
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
I run your code and got the exact expected results: A new row in UserContacts table with the existing UserId and ContactOptionId, so I am not sure what's happing in there, but you can try to explicitly have FKs in UserContact object so that you'll have full control over how Code First inserts records for you. For that, you need to change UserContact as follows:
public class UserContact
{
public int Id { get; set; }
// By Convention these 2 properties will be picked up as the FKs:
public int UserId { get; set; }
public int ContactOptionId { get; set; }
[Required]
public User User { get; set; }
[Required]
public ContactOption ContactOption { get; set; }
public string Data { get; set; }
}
And then you can change your code like this:
var contactOption = _context.ContactOptions.Find(someId);
var user = _context.Users.Find(1);
var contact = new UserContact
{
ContactOptionId = contactOption.Id,
UserId = user.Id,
Data = "someData"
};
user.UserContacts.Add(contact);
context.SaveChanges();
My issue turned out to be having two context's. I reworked my code a bit to only have one context and my issue went away. Thanks to Morteza Manavi for pointing me in the right direction.
Related
I have a Database Schema like this.
I am working on gettingDetailByPostID API which needs to return 1 Object as follows.
Guid PostId;
Guid UserId;
String? Username;
String? MediaPath;
int Likes; // count the number of likes of the post
String? Caption;
bool IsLiked;
IEnumerable<Comment>? Comments; // List of comments in the post
I am having difficulty in joining the tables together to get more data from the User and Comment tables. How can I do this with ef core 6.
firsr have a DbSet property for in your db context inherited class which may look like this public DbSet<Post> Posts { get; set; }.
then inject the dbcontext to your required service as constructor injection.
the you can do var post = yourDbContenxt.Posts.FirstOrDefaultAsync(p=>p.PostId==yourPostId);
The best way to do this would be in a single transaction.
I believe you already have configured EF with navigation and you should have an entities like this
public class Post
{
public int post_id { get; set; }
public int user_id { get; set; }
public string caption { get; set; }
public string? media_path { get; set; }
virtual public List<Like> Likes { get; set; }
}
public class Like
{
public int post_id { get; set; }
public int user_id { get; set; }
virtual public Post Post { get; set; }
virtual public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public int post_id { get; set; }
public int user_id { get; set; }
public string username { get; set; }
virtual public Post Post { get; set; }
virtual public List<Like> Likes { get; set; }
}
If not, you should have a look on the microsoft documentation or on this great tutorial site
If it is configured with navigation (but without lazy loading to avoid infinite loop, and to have an ecological use of your server ressources :) )
Then you can build your object within the same context instanciation such as this short exemple
public class DetailedPost
{
Guid PostId;
Guid UserId;
String? Username;
String? MediaPath;
int Likes; // count the number of likes of the post
String? Caption;
bool IsLiked;
IEnumerable<Comment>? Comments; // List of comments in the post
public DetailedPost GettingDetailByPostID(int id)
{
DbContext context = new DbContext();
UserProfile userprofile = context.UserProfile
.Include(user => user.Posts.Where(p => p.post_id == id)) // Select post by id
.ThenInclude(post => post.Comments) // Include the Comment Db relative to post selected
.Include(user => user.Likes); // Include like db relative to userProfile db
// Return object construction
return new DetailedPost()
{
PostId = userprofile.post_id,
UserId = userprofile.user_id,
Username = userprofile.username,
Caption = userprofile.Posts.FirstOrDefault().Caption,
Likes = userprofiles.Likes.Count(),
};
}
}
I hope it helps !
I have two tables with one-to-one relationship.
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Category { get; set; }
public int? NameId { get; set; }
[ForeignKey("NameId ")]
public virtual Name Name { get; set; }
}
I already have data in those tables.
I know the database relations are not supported to be changed.
Is it possible to change one-to-one relationships to many-to-many relationships?
What is the most suitable approach to overcome this requirement?
Yes, you can still change that, using migrations.
Step 1 is to create a linking table, like NameCategories, which looks something like this:
public class NameCategories
{
public int Id { get; set; }
public int NameId { get; set; }
public Name Name { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Step 2 is to reference this table in the tables you already have. In Name it would look like this
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<NameCategory> Categories { get; set; }
}
Step 3 is to add a migration. You'll have some AddColumn() and some DropColumn() statements. In between them, when all the add stuff was executed but the drops not yet, you can add SQL code to carry over all the existing relations into the newly created table. After that, the old data will be deleted by the DropColumn() code. In your example, this would look something like this
INSERT INTO NameCategories (NameId, CategoryId)
SELECT (n.Id, c.Id) FROM Names n
JOIN Categories c on c.NameId = n.Id
WHERE ..
You can execute the SQL in the migration like this:
var sql = #"...";
Sql(sql);
I hope this helps you out!
I cant figure out how to relate a column with another table using the include method.
i have the following models
public class Fleet
{
[Key]
public Guid OwnerId { get; set; }
public String ownerName { get; set; }
public virtual ICollection<UserAccount> UserAccount { get; set; }
}
public class UserAccount
{
[Key]
[Display(Name="UserID")]
public Guid UserID { get; set; }
public String UserName { get; set; }
public Guid SelectedFleet { get; set; }
public Guid? PrimaryFleet { get; set; }
public String Password { get; set; }
public virtual Fleet Fleet { get; set; }
}
In the Model UserAccount i have 2 foreignKeys where selectedFleet and PrimaryKey are realated to my fleet table
With this code i do get my UserAccount row back so that i can complete my login, i state this so you are aware that there is actually info to get bakc from the table with the data provided
var v = dc.UserAccounts.Where(a => a.UserName == model.UserName).SingleOrDefault();
The problem is when i try to include the fleet my record returns null, i know i am doing something wrong since i don't even know how to tell the include what column is supposed to be related to which table i tried d => d.Fleet.OwnerId == d.SelectedFleet but i got an error telling me thats not the use of inlcude
var v = dc.UserAccounts.Where(a => a.UserName == model.UserName).Include(d => d.Fleet).SingleOrDefault();
I am learning Entity Framework in asp.net mvc application. I have 3 models -
AppModel, CategoryModel and App_CategoryModel (to specify many to many relationship between AppModel and CategoryModel). A snippet of this is:
public class CategoryModel
{
[Key]
public int id { get; set; }
public string Name {get; set; }
public virtual ICollection<App_CategoryModel> mapping { get; set; }
}
public class AppModel
{
[Key]
public int id { get; set; }
public string Name { get; set; }
public virtual ICollection<App_CategoryModel> mapping { get; set; }
}
public class App_CategoryModel
{
[Key]
public int id {get; set;}
public int AppId {get; set; }
public int CategoryId {get; set; }
public virtual AppModel App {get; set;}
public virtual CategoryModel Category {get; set;}
}
I was following 'Code-first' approach and the tables got created successfully. But, now I am stuck at how to populate and display this information.
I have the following as input data:
List<AppModel>
List<CategoryModel> and Dictionary<"appname", List<CategoryModel>>
How do I move on from here so that I can update the mapping table?
Also, wanted to understand whether this is the correct approach to represent data. Since an App can have multiple categories - I expect the output as a collection of unique Apps along with a list of categories for each app, something like:
Dictionary<AppModel, List<CategoryModel>>
Edit:
This is what I tried as per suggestion from smoksnes-
List<CategoryModel> cat_list = new List<CategoryModel>();
CategoryModel c1 = new CategoryModel();
c1.Name = "string1";
cat_list.Add(c1);
CategoryModel c2 = new CategoryModel();
c2.Name = "string2";
cat_list.Add(c2);
List<AppModel> app_list = new List<AppModel>();
AppModel a1 = new AppModel();
a1.Name = "app1";
app_list.Add(a1);
AppModel a2 = new AppModel();
a2.Name = "app2";
app_list.Add(a2);
a1.mapping.Add(c1);
a1.mapping.Add(c2);
a2.mapping.Add(c1);
a2.mapping.Add(c2);
db.categories.AddRange(cat_list);
db.apps.AddRange(app_list);
db.SaveChanges();
After this, EF worked as expeted - 2 categories , 2 apps and 4 entries in mapping table.
Although this worked, but not sure who is stopping EF to create 4 entries for categories?
Just as Barry O´Kane mentioned in your comment there's no reason to keep the App_CategoryModel model. EF will manage this for you. You should only keep it if it contains any extra information regarding the relation between the two tables. But according to your example, there's no reason to keep it.
public class CategoryModel
{
public CategoryModel()
{
AppModels = new List<AppModel>();
}
[Key]
public int id { get; set; }
public string Name {get; set; }
public virtual ICollection<AppModel> AppModels { get; set; }
}
public class AppModel
{
public AppModel()
{
// Not sure if this is actually needed anymore. But EF usually adds it.
CategoryModels = new List<CategoryModel>();
}
[Key]
public int id { get; set; }
public string Name { get; set; }
public virtual ICollection<CategoryModel> CategoryModels { get; set; }
}
And regarding your question about representation, I don't think it's necessary. Since the AppModel already has the connected CategoryModel on it's model there's no reason for a Dictionary. You can store it in a List<AppModel> instead.
IList<AppModel> myApps = context.AppModels.ToList();
foreach (var myApp in myApps)
{
Console.Writeline("App {0} has the following categories:", myApp.id);
foreach (var category in myApp.CategoryModels)
{
Console.Writeline(category.Name);
}
}
And when you want to add a category to an app:
// I don't know how you create your Context, so below it's just called context.
var newCategoryModel = new CategoryModel
{
Name = "SO is awesome!"
};
var appModel = context.AppModels.FirstOrDefault(x => x.id == 1);
appModel.CategoryModels.Add(newCategoryModel); // EF will automatically set foreign keys for you...
context.SaveChanges();
And if you want to make sure that no category is added twice:
public void AddCategory(int appId, string categoryName)
{
using(var context = new DbContext())
{
var category = context.CategoryModels.FirstOrDefault(x => x.Name == categoryName);
if(category == null)
{
// Only create new CategoryModel if it doesn't exist.
category = new CategoryModel
{
Name = categoryName
};
}
var appModel = new AppModel
{
id = appId
};
// Attach to save on db-trip
context.AppModels.Attach(appModel);
//TODO: Possibly check if this particular appModel already has this category?
appModel.CategoryModels.Add(category);
context.SaveChanges();
}
}
I am apparently having a real devil of a time understanding Entity Framework 6 which I am using with ASP.NET MVC 5.
The core of the matter is that I have a really quite simple data model that is typical of any real world situation where I have various business objects that have other business objects as properties (and of course they child objects may in turn have other child business objects) and also various types of lookup/type data (Country, State/Province, LanguageType, StatusType etc.) and I cannot figure out how to save/update it properly.
I keep going back and forth between two error states:
1) I either run into the situation where saving a parent business object results in unwanted duplicate values being inserted into my lookup/type tables (for example saving a business object that has been assigned an existing LanguageType of 'English' will result in another LanguageType for 'English' being inserted into the LanguageType table), or
2) I use some of the suggestions I've seen here and elsewhere on the net (e.g. Saving Entity causes duplicate insert into lookup data, Prevent Entity Framework to Insert Values for Navigational Properties ) to solve issue 1 and then find myself fighting against this same issue: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key .
I will now provide a few code snippets to help build the picture of what I am trying to do and what I am using to do it. First, an example of the entities involved:
public class Customer : BaseEntity
{
public string Name { get; set; }
[LocalizedDisplayName("Contacts")]
public virtual List Contacts { get; set; }
}
public class Contact : BaseEntity
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
[Required]
[ForeignKey("LanguageTypeID")]
public virtual LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
[LocalizedDisplayName("CultureName")]
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
In my controller, I have some code like the following:
foreach(Contact contact in lstContacts)
{
customer.Contacts.Add(contact);
}
if (ModelState.IsValid)
{
repository.Add(customer);
}
Let us suppose that each of the contacts has the same LanguageType of 'English' assigned (and in this example it is the fact that I am trying to save multiple contacts that have the same LanguageType that triggers the ObjectStateManager error). Initially, the repository.Add() code just did a context.SaveChanges() which did not work as expected, so now it looks something like this (Entity variable is a Customer):
try
{
if(Entity.Contacts != null)
{
foreach(Contact contact in Entity.Contacts)
{
var entry = this.context.Entry(contact.Language);
var key = contact.Language.ID;
if (entry.State == EntityState.Detached)
{
var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
if (currentEntry != null)
{
var attachedEntry = this.context.Entry(currentEntry);
//attachedEntry.CurrentValues.SetValues(entityToUpdate);
attachedEntry.State = EntityState.Unchanged;
}
else
{
this.context.LanguageTypes.Attach(contact.Language);
entry.State = EntityState.Unchanged;
}
}
}
}
context.Customers.Add(Entity);
context.SaveChanges();
}
catch(Exception ex)
{
throw;
}
Is it fundamentally wrong to expect this to have worked? How am I supposed to save and example like this? I have similar problems saving similar object graphs. When I look at tutorials and examples for EF, they are all simple and they all just call SaveChanges() after doing something very similar to what I am doing here.
I've just recently been using the ORM capabilities of ColdFusion (which is hibernate under the covers) and there are would simply load the LanguageType entity, assign it to the Contact entity, save the Contact entity, assign it to the Customer and then save the Customer.
In my mind, this is the most basic of situations and I cannot believe that it has caused me so much pain - I hate to say it, but using plain old ADO.NET (or heaven forbid, ColdFusion which I really don't enjoy) would have been MUCH simpler. So I am missing SOMETHING. I apparently have a key flaw in my understanding/approach to EF and If somebody could help me to make this work as expected and help me to figure out just where my misunderstanding lies, I would greatly appreciate it. I have spend too many hours and hours on this and it is a waste of time - I have/will have countless examples just like this one in the code I am building so I need to adjust my thinking with respect to EF right now so I can be productive and do approach things in the expected way.
Your help will mean so much and I thank you for it!
Let's consider the following object graph in which a teacher instance is the root object,
Teacher --[has many]--> courses
Teacher --[Has One]--> Department
In entity framework's DbContext, each instance of an object has a State indicating whether the object is Added, Modified, Removed or Unchanged. What happens apparently is the following :
Creating the root object for the first time
In this case, in addition to the newly created root object Teacher, ALL the child objects in the graph will have the State Added as well even if they're already created. The solution for this problem is to include the foreign key property for each child element and use it instead, i.e. Teacher.DepartmentId = 3 for example.
Updating the root object and one of its child elements' properties
Suppose you fetch a teacher object from the db, and you change the Teacher.Name property as well as the Teacher.Department.Name property; in this case, only the teacher root object will have the State marked as Modified, the department's State on the other hand remains Unchanged and the modification won't be persisted into DB; Silently without any warning.
EDIT 1
I used your classes as follows and I don't have a problem with persisting the objects :
public class Customer : BaseEntity
{
public string Name { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
public Customer Customer { get; set; }
[ForeignKey("LanguageTypeID")]
public LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
public class ApplicationUser
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And used the following Context :
public class Context : DbContext
{
public Context() : base("name=CS") { }
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<LanguageType> LanguageTypes { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//I'm generating the database using those entities you defined;
//Here we're demanding not add 's' to the end of table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Then I created a unit tests class with the following :
[TestMethod]
public void TestMethod1()
{
//our context
var ctx = new Infrastructure.EF.Context();
//our language types
var languageType1 = new LanguageType { ID = 1, Name = "French" };
var languageType2 = new LanguageType { ID = 2, Name = "English" };
ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
//persist our language types into db before we continue.
ctx.SaveChanges();
//now we're about to start a new unit of work
var customer = new Customer
{
ID = 1,
Name = "C1",
Contacts = new List<Contact>() //To avoid null exception
};
//notice that we're assigning the id of the language type and not
//an object.
var Contacts = new List<Contact>(new Contact[] {
new Contact{ID=1, Customer = customer, LanguageTypeID=1},
new Contact{ID=2, Customer = customer, LanguageTypeID=2}
});
customer.Contacts.AddRange(Contacts);
//adding the customer here will mark the whole object graph as 'Added'
ctx.Customers.Add(customer);
//The customer & contacts are persisted, and in the DB, the language
//types are not redundant.
ctx.SaveChanges();
}
It all worked smoothly without any problems.
As far as i know there is no build in support for reattaching modified graphs (like the SaveOrUpdate method of nHibernate). Perhaps this or this can help you.