Create reference between Entity Framework domains - c#

I have a domain called Item and a domain called Category. I want to add the PK of the category to the Item but I am not sure how to do this in EF 6
public class Item
{
[Key]
public Guid Id { get; set; } = GuidCombGenerator.GenerateComb();
public string Name { get; set; }
public Category Category { get; set; }
}
public class Category
{
[Key]
public Guid Id { get; set; } = GuidCombGenerator.GenerateComb();
public string Name { get; set; }
public virtual ICollection<Item> Items { get; set; } = new List<Item>();
}
I thought I could do this
var item = new Item
{
CategoryId = dto.CategoryId ,
Name = dto.Name.Trim(),
};
dbContext.StorageItems.Add(item);
dbContext.SaveChanges();
I added another property called CategoryId and filled this in with the correct Id. However this goes and make a new Category entry instead of just linking the 2 up.
I thought I could do this but maybe I am mixing it up with NHibernate.
I then tried this (did not think this would work):
var item = new item
{
Category = new Category { Id = dto.CategoryId, },
Name = dto.Name.Trim(),
};
dbContext.StorageItems.Add(item);
dbContext.SaveChanges();
It does not like this at all.
The only option I can think of is going to the db and getting the Category object back and then adding it to Item but I would like to prevent trips to the db just to get back the id I already know.

Mark your Category virtual, create the CategoryId property, and then specify it's the foreign key for Category.
public class Item
{
[Key]
public Guid Id { get; set; };
public string Name { get; set; }
public Guid CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
}
With this, your first code (assign the Id directly) should work unless you "touch" the Category property or reference.
For this to work, make sure you haven't disabled proxies on the context
Also, make sure you don't set your Id properties to anything custom, let EF do its job

Related

EF Core nested objects

I have two entities with a many-to-many relationship, and I have a third entity to represent this relationship, these are classroom user and userclassroom respectively. I want to retrieve a specific classroom, users registered in this classroom, and messages from this classroom, I wrote the following query for this:
await _genericRepository.GetAsync(x => x.Id.ToString() == request.classroomId,
x => x.Messages, x => x.Tags, x => x.Users);
But the related entities in the returned data are constantly repeating themselves, you can check it from the picture below.
Is this normal or is it an error, if it is an error, what is the solution?
Entities:
public class AppUser: IdentityUser
{
public ICollection<UserClassroom> Classrooms { get; set; }
public List<Message> Messages { get; set; }
}
public class Classroom
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Tag> Tags { get; set; }
public List<Message> Messages { get; set; }
public virtual ICollection<UserClassroom> Users { get; set; }
}
public class UserClassroom
{
public string UserId { get; set; }
public Guid ClassroomId { get; set; }
public AppUser AppUser { get; set; }
public Classroom Classroom { get; set; }
public DateTime JoinDate { get; set; } = DateTime.Now;
}
It looks like you have a circular dependency between Classroom and UserClassroom.
Classroom has a collection of UserClassroom, and each UserClassroom has a Classroom which will point back to the Classroom which will point back to the UserClassroom which will... - you get the point.
I would suggest you remove the Classroom property from UserClassroom as you already have the ClassroomId that you can use to retrieve the Classroom if you need to.
This isn't an error. Even for 1:n relations you can have this behaviour. It simply says you have Navigation Properties in both ways.
Let's say you have the classes: Pet, PetOwner. When the navigation properties are set correctly you can access the Pet of an PetOwner. The Pet then holds the PetOwner reference, which again holds the Pet reference. And that way you can navigate indefinitly.

How to insert properly into a many-to-many relationship?

When inserting data into a many-to-many relationship, should you insert to the join-table or to both original tables?
My table models:
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<GroupMember> GroupMembers { get; set; }
The relationship between them is configured with Fluent API:
builder.Entity<GroupMembers>().HasKey(gm => new { gm.UserId, gm.GroupId });
builder.Entity<GroupMembers>().HasOne(gm => gm.Group).WithMany(group => group.GroupMembers).HasForeignKey(gm => gm.GroupId);
builder.Entity<GroupMembers>().HasOne(gm => gm.User).WithMany(user => user.GroupMembers).HasForeignKey(gm => gm.UserId);
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public List<GroupMember> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public List<GroupMembers> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class GroupMembers
{
[Key]
public Guid UserId { get; set; }
public User User { get; set; }
[Key]
public Guid GroupId { get; set; }
public Group Group { get; set; }
}
Now, the question is; in which tables/classes should I insert the data about the group members?
Is it like this:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
user.GroupMembers.Add(groupMember);
group.GroupMembers.Add(groupMember)
_databaseContext.Users.Update(user);
_databaseContext.SaveChanges();;
_databaseContext.Groups.Update(group);
_databaseContext.SaveChanges();
Or like this, leaving the User and Group untouched, with the information about their relationship ONLY in the join-table:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
As far as Entity Framework is concerned, this is not a many-to-many relationship
What you have here is three entity types with two one-to-many relationships defined between them. You might know that this is done to represent a many-to-many, but EF doesn't know that.
If I arbitrarily change the names of your entities while maintaining the structure, you wouldn't be able to tell if this was a many-to-many relationship or not.
Simple example:
public class Country {}
public class Company {}
public class Person
{
public int CountryOfBirthId { get; set; }
public virtual Country CountryOfBirth { get; set; }
public int EmployerId { get; set; }
public virtual Company Employer { get; set; }
}
You wouldn't initially think of Person as the represenation of a many-to-many relationship between Country and Company, would you? And yet, this is structurally the same as your example.
Essentially, your handling of your code shouldn't be any different from how you handle any of your one-to-many relationships. GroupMembers is a table (db set) like any else, and EF will expect you to treat it like a normal entity table.
The only thing that's different here is that because GroupMember has two one-to-many relationships in which it is the "many", you therefore have to supply two FKs (one to each related entity). But the handling is exactly the same as if you had only one one-to-many relationship here.
In other words, add your groupMember to the table itself:
GroupMembers groupMember = new GroupMembers
{
// You don't have to fill in the nav props if you don't need them
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
Note: The following only applies to non-Core Entity Framework, as EF Core does not yet support it.
An example of what would be a "real" many-to-many relationship in (non-Core) EF would be if the intermediary table was not managed by you, i.e.:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
}
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
In this scenario, EF will still generate the cross table in the database, but EF will hide this table from you. Here, you are expected to work via the nav props:
var user = myContext.Users.First();
var group = myContext.Groups.First();
user.Groups.Add(group);
myContext.SaveChanges();
Whether you use a "real" many-to-many relationship or manage the cross table yourself is up to you. I tend to only manage the cross table myself when I can't avoid it, e.g. when I want additional data on the cross table.
Make sure the data id is correct and Exists
GroupMembers groupMember = new GroupMembers
{
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
There is less line of code and you have to assume that the object is completely independent when inserted

Add an Entity object with its Related Entity based on primary keys

I have an entity like this:
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string FullText { get; set; }
public string Tags { get; set; }
public virtual Category Category { get; set; }
}
This entity has relations to Category:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<Post> Articles { get; set; }
}
Now, when inserting data into the Post table, I use this code:
var post = new Post
{
Category = _categoryService.FindById(model.CategoryId),
FullText = model.FullText,
Tags = model.Tags,
Title = model.Title,
};
_articsleService.Save(post);
This code works fine, but it has 1 fetching data form database. In entity objects that has more than 3 relations, I think this way not so good. Therefore, another way can be like this:
var post = new Post
{
Category = new Category { Id = model.CategoryId },
FullText = model.FullText,
Tags = model.Tags,
Title = model.Title,
};
_articsleService.Save(post);
But, when the code is run, I get this exception:
Cannot insert explicit value for identity column in table 'Categories' when IDENTITY_INSERT is set to OFF.
Is there any best solution for this problem?
Add to your post model the FK to CategoryId
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string FullText { get; set; }
public string Tags { get; set; }
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
}
Then just add a new post after updating your db model.
var post = new Post
{
CategoryId = model.CategoryId,
FullText = model.FullText,
Tags = model.Tags,
Title = model.Title,
};
_articsleService.Save(post);
That's it, EF will do the rest for you.
Your current approach leads to creating a new Category which does not work bc the same primary key already exists and inserting into an identity column is not enabled by default. This is how change-tracking from EF works. EF creates a proxy from each database row as an entity in your code and tracks this entity during application lifetime.
You can read more about it here.

Code first one-to-many with two of same entity

Excuse my confusing title, but its not easy to say it in short.
I'm having problems defining a many-to-many relationship in my code first model.
This is my first project using the code first framework, and I need some help.
I got two models, Item and Trade. And the story behind it all is that im building a item trading website.
A trade involve one or many items beeing sent from person A to person B, and one or many items are sent back from person B to person A, completing the trade.
One item belongs to one trade only, but one trade can have many items.
This is how my models look so far.
Item
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public int TradeId { get; set; }
[ForeignKey("TradeId")]
public virtual Trade Trade { get; set; }
}
Trade
public class Trade
{
public int TradeId { get; set; }
public virtual ICollection<Item> ItemsToSend { get; set; }
public virtual ICollection<Item> ItemsToReturn { get; set; }
}
But when trying to run update-database I get the following error.
System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Items_dbo.Trades_TradeId". The conflict occurred in database "MyDB", table "dbo.Trades", column 'TradeId'.
Any help is much appreciated, thanks!
I solved it for now by just adding a flat structure of items and trades. Like a many to many on items and trades.
Since I know the owner of each item, I can also sort out sender and returner by adding senderId and returnerId on the trade.
This is how my model looks like now.
Item
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public virtual ICollection<Trade> Trades { get; set; }
}
Trade
public class Trade
{
public int TradeId { get; set; }
public virtual UserProfile SendUser { get; set; }
public virtual UserProfile ReturnUser { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
This works for now..
This: "One item belongs to one trade only, but one trade can have many items." sounds like one-to-many?
If you can do without the back-reference from Item to Trade then the mapping will be a lot simpler.
This model will map without any configuration:
public class Trade
{
public int Id { get; set; }
public virtual ICollection<Item> ItemsToSend { get; set; }
public virtual ICollection<Item> ItemsToReturn { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
Inserting data using this code:
var trade = new Trade
{
ItemsToReturn = new [] { new Item{ Name = "Item to return 1"}, new Item{ Name = "Item to return 2"}},
ItemsToSend = new [] { new Item{ Name = "Item to send 1"}, new Item{ Name = "Item to send 2"} }
};
context.Trades.Add(trade);
context.SaveChanges();
Will produce an Item table looking like this:
And a Trades table containing a single row with a single Id column.
If you need to navigate from an Item to the Trade it belongs to you could do that doing a query.
E.g to get the Trade that Item with id = 3 belongs to you can do this:
using (var db = new TradingContext())
{
var tradeOfItem3 = db.Trades.
FirstOrDefault(t => t.ItemsToReturn.Any(i => i.Id == 3) || t.ItemsToSend.Any(i => i.Id == 3));
}

.Net Entity Many to Many SaveChanges

I have been playing with Entity Core classes such as DbContext and had the following error trying to save an object:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
I basically have my erd with a many to many such as
comment comment_category category
id comment_id id
text category_id name
the comment_category table is a combo primary key to map comments to categories
retrieving data is fine but when I try to save it complains about the relationship
the models I am most interested in look like
public class Comment
{
[Key]
public int Comment_Id {get;set;}
public string Text {get;set;}
public virtual List<Category> Categories { get; set; }
}
public class Comment_Category
{
[Key, Column(Order = 0)]
public int Comment_Id {get;set;}
[Key, Column(Order = 2)]
public int Factor_Id {get;set;}
}
And its used such as
#Comments have Categories with Category Id filled and Comment Id null
List<Comment> comments = getComments();
using(dbContext db = new dbContext())
{
foreach( Comment c in comments)
db.Comments.add(c);
db.SaveChanges();
}
I am not entirely sure why it can find it easily enough but has a hard time saving. The only difference I can think of is that the comments I am saving are new so they only have Comment Categories with no Comment id just category ids. I assumed that it would save the comment and assign the comment_id to the comment_category table so I am not sure how to accomplish this
I realize that perhaps my approach is wrong in that I am using the mapping table as opposed to the actual entity for categories so if anyone knows a better way please share.
Thanks!
The easiest way to do this without a lot of ceremony is to also have a collection of Comments on Category and let Entity Framework infer the M:M relationship. It will automatically create a CategoryComments table with primary and foreign keys.
So for the model we simply have:
public class Comment
{
[Key]
public int Comment_Id { get; set; }
public string Text { get; set; }
public virtual List<Category> Categories { get; set; }
}
public class Category
{
[Key]
public int Category_Id { get; set; }
public string Name { get; set; }
public virtual List<Comment> Comments { get; set; }
}
Usage would be something like:
public class MyDbContext : DbContext
{
public DbSet<Comment> Comments { get; set; }
public DbSet<Category> Categories { get; set; }
}
using (var db = new MyDbContext())
{
var blue = new Category { Name = "Blue" };
var red = new Category { Name = "Red" };
db.Categories.Add(blue);
db.Categories.Add(red);
db.Comments.Add(new Comment
{
Text = "Hi",
Categories = new List<Category> { blue }
});
db.SaveChanges();
}

Categories

Resources