Entity Framework is trying to insert into wrong table? - c#

I've built a test application with Entity Framework to simulate a database that contains friends lists.
I want the database to store the user's ID's and when I retrieve them (the "AcceptedFriends") I want Entity Framework to also return the friends "usermodel".
But every time I try to add 2 users as friends to the "AcceptedFriends" table it
throws an exception:
" Violation of PRIMARY KEY constraint 'PK_Users'. Cannot insert duplicate key in object 'dbo.Users'. The duplicate key value is (GUID value of a user's ID) "
Some attempted solutions:
Solution 1
Attempting to create 2 lists of the same friend list (received, sent) but that defeats the purpose of what I am trying to achieve.
Solution 2
Here are the code files:
"Users Model"
public class Users
{
#region Private fields
#endregion
#region Public properties
public string Username { get; set; }
public string ID { get; set; }
public string PasswordHash { get; set; }
public virtual List<AcceptedFriends> AcceptedFriendsList { get; set; }
// public virtual List<PendingFriends> PendingFriendsList { get; set; }
// public virtual List<RemovedFriends> RemovedFriendsList { get; set; }
#endregion
}
"Accepted Friends model"
public class AcceptedFriends
{
#region Public properties
public string RelationKey { get; set; }
public string RequestSenderID { get; set; }
public string RequestReceiverID { get; set; }
public virtual List<Messages> ChatList { get; set; }
public Users RequestSender { get; set; }
public Users RequestReceiver { get; set; }
#endregion
}
"Database model creation"
#region Users table
// Create primary key in Users table
modelBuilder.Entity<Users>().HasKey(property => property.ID);
// Map Username to be unique
modelBuilder.Entity<Users>().HasIndex(property => property.Username).IsUnique();
// Create a one to many relation with AcceptedFriends table
modelBuilder.Entity<Users>()
.HasMany(property => property.AcceptedFriendsList)
.WithOne(property => property.RequestReceiver)
.HasForeignKey(property => property.RequestReceiverID);
#endregion
#region Accepted friends table
// Create key for AcceptedFriends
modelBuilder.Entity<AcceptedFriends>().HasKey(property => property.RelationKey);
#endregion
Edit
Here is how I am inserting the friends
public static void AddFriends(AcceptedFriends friends)
{
using(Context context = ConnectToDatabase())
{
context.AcceptedFriends.Add(friends);
context.SaveChanges();
};
}
Edit 2
Here is where I add the friends/users
Plus I've noticed another odd behaviour When I add new users to the friends table
without adding them to the users table first it adds them both to the friends table and users table.
Console.WriteLine("Connecting to database");
DB.ConnectToDatabase();
Console.WriteLine("Connected to database successfully");
List<Users> userList = new List<Users>(DB.GetUsersList());
List<AcceptedFriends> friendsCount = new List<AcceptedFriends>(DB.GetAcceptedFriends());
if(userList.Count != 2)
{
DB.AddUser(new Users()
{
Username = "User1",
PasswordHash = "PasswordHash",
});
DB.AddUser(new Users()
{
Username = "User2",
PasswordHash = "PasswordHash",
});
userList = new List<Users>(DB.GetUsersList());
};
if(friendsCount.Count < 1)
{
Users user1 = userList[0];
Users user2 = userList[1];
DB.AddFriends(new AcceptedFriends()
{
RequestReceiver = user2,
RequestSender = user1,
});
};
Console.WriteLine("Server is great success!");
Console.ReadLine();
Edit 3
I might have found a solution.
It does return the models both for the user and friends,
But I can't accept this as a solution yet because it feels to hackey(?) for me
(Thanks to #wertzui, You helped me to get to this solution)
Basically everytime a new context is created it sets up the the friends and users to return thier usermodels
/// <summary>
/// Gets the friends user models
/// </summary>
/// <param name="context"> The database context that was created </param>
private static void SetupFriends(Context context)
{
// For every "AcceptedFriend"
foreach(AcceptedFriends friend in context.AcceptedFriends)
{
// Get sender and receiver usermodels
// by matching ID's
Users sender = context.Users.FirstOrDefault(user => user.ID == friend.RequestSenderID);
Users receiver = context.Users.FirstOrDefault(user => user.ID == friend.RequestReceiverID);
sender.AcceptedFriendsList.Add(friend);
receiver.AcceptedFriendsList.Add(friend);
friend.RequestSender = sender;
friend.RequestReceiver = receiver;
};
}

When you create new User Instances in your new AcceptFriends {...} Code, you are not setting their Id, so they keep their default which is 0. Now Entity Framework thinks, that you want to create a new Friendship with 2 new Users. Instead you should populate them with the Users, you created earlier.
if(friendsCount.Count < 1)
{
Users user1 = userList[0];
Users user2 = userList[1];
DB.AddFriends(new AcceptedFriends()
{
RequestReceiver = user1,
RequestSender = user2,
});
}

Related

How to reference and then insert data into associative entity that is created automatically in db?

I am new to Entity Framework.
I have created tables using Code First approach and it worked i.e. created Users, Phones, UsersPhones tables.
Now I can add the data to Phones table and Users table since they are mentioned in my entity models but how would I insert data into UsersPhones associative entity for which I have not data entity or model becuase it was created automatically.
Code:
Phones:
public class Phones
{
public int ID { get; set; }
public string Model { get; set; }
public string Manufacturer { get; set; }
public List<Users> Users { get; set; }
}
Users:
public class Users
{
public int ID { get; set; }
public string Name { get; set; }
public List<Phones> Phones { get; set; }
}
UsersPhonesDBContext
public class UsersPhonesDBContext: DbContext
{
public DbSet<Users> Users { get; set; }
public DbSet<Phones> Phones { get; set; }
}
Controller and action to add phones:
[HttpPost]
public ActionResult Create(FormCollection fc)
{
Phones phone= new Phones();
phone.Model= fc["Model"];
phone.Manufacturer= fc["Manufacturer"];
UsersPhonesDBContext.Phones.Add(phone);
int r = UsersPhonesDBContext.SaveChanges();
if (r > 0)
return RedirectToAction("Index", "Test");
else
return Redirect("~Views/Shared/Error.cshtml");
}
and Users in a similar fashion.
But what about UsersPhones table?
You are adding phones without users or users without phones, then there are not data in the relational table UsersPhones.
If you want to have data in this table, then you have to relate a user with a phone: for example:
[HttpPost]
public ActionResult CreateUserWithPhone(FormCollection fc)
{
...
using (var context = new UsersPhonesDBContext())
{
var user= new Users { Name = "testName" };
context.Users.Add(user);
var phone = new Phones{ Model= "testModel" };
context.Phones.Add(phone);
user.Phones = new List<Phones>();
user.Phones.Add(phone);
context.SaveChanges();
}
...
}
This code will create a row in Users table, a row in Phones table and a row in UsersPhones table.
--EDIT--
I created a console project to test it, with your classes (Phones, Users and UsersPhonesDBContext) and this is the Program.cs:
class Program
{
static void Main(string[] args)
{
using (var context = new UsersPhonesDBContext())
{
var user = new Users { Name = "testName" };
context.Users.Add(user);
var phone = new Phones { Model = "testModel" };
context.Phones.Add(phone);
user.Phones = new List<Phones>();
user.Phones.Add(phone);
context.SaveChanges();
}
}
}
After execute the program, it created a row in Users table, Phones table and UsersPhones table:

Inserting A Record To Lookup Table

I refer to the answer here (https://stackoverflow.com/a/20846170/1753877) about how to insert a record into the Users_Friends lookup table in this type of relationship
The answer is :
var user = db.Users.Find(userID);
var friend = db.Friends.Find(friendID);
user.Friends.Add(friend);
db.SaveChanges();
However, to me it seems like a lot of overhead to have to retrieve two objects from the database just to insert a row comprising of just the two IDs that are already known to the application without the need for any queries.
Could we not just do a basic INSERT using userID and friendID as values (or pass them to a stored procedure to do the same).
As I am new to Entity Framework, I'm unsure if there are any advantages to using the code above that would be missed with a direct SQL INSERT, or if there is a valid reason to avoid using direct SQL with Entity framework for inserts
Thanks.
There are several ways to do this:
first is if User_Friends has its own entity you can just populate a new user_friends with the id's and save changes.
The second is to create a New friend and user and attach them (something similar to this, may take some tweaking):
var friend = new Friend{Id = friendID};
var user = new User{Id = userID};
using (var context = new Context())
{
context.Friends.Attach(friend);
user.Friends.Add(friend);
context.Entry(user).State = EntityState.Modified;
context.SaveChanges();
}
Entity Framework Add and Attach and Entity States
The third is to use Context.Database and either execute sql command or sql query (depending) to execute arbitrary SQL against the database.
What I have found the easiest is to include User_Friends as its own EF entity and create a new one. It makes adding to the union table very easy.
You could do a direct insert like:
db.Database.ExecuteSqlCommand(#"insert into Users_friends
(UserId, FriendId) values ({0}, {1})", userID, friendID);
I don't see an advantage first retrieving the values from db (except verifying both user and friend exists). However, in the above code there is no check if userID, friendID pair already exists which could be easily added to SQL.
You are correct that it is a lot needless overhead. In Entity Framework, it's enough to just pass a new instance of the class with the PK loaded:
var user = db.Users.Find(userID);
var friend = new Friend { Id = friendID };
user.Friends.Add(friend);
db.SaveChanges();
Create the bridge table as a model as well, like this
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public string PasswordSalt { get; set; }
public List<UserFriend> Friends { get; set; }
public User()
{
Friends = new List<UserFriend>();
}
}
public class Friend
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserFriend
{
public int Id { get; set; }
public int UserId { get; set; }
public int FriendId { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
[ForeignKey("FriendId")]
public Friend Friend { get; set; }
}
Then you can create or get user friends like this
// add user friend
var userFriend = new UserFriend()
{
UserId = userId,
FriendId = friendId
};
db.UserFriends.Add(userFriend);
db.SaveChanges();
// get user friends
db.Users.Include("Friends").ToList();

List of values as a user attribute

I'm using identity 2.1.0 in ASP.NET MVC 5 application, and I have pages for admin to work (create/edit/delete user) with some custom user properties I defined. However I need to store a list of values in one field. How can I achieve this by using identity, and how to show this on a web page ?
To save such values you can extend your ApplicationUser, in case of multiple values you can do this via an n:m relationship:
First create a table to store the country values in (also add to your DbContext as e.g. public DbSet<Country> Countries { get; set; }):
public class Country
{
[Key]
public int Id { get; set; } // or e.g. "string Code" to save e.g. "us"
public string Name { get; set; }
public List<ApplicationUsers> Users { get; set; }
}
then you can also add a list of Country to your ApplicationUser:
public class ApplicationUser : IdentityUser<KEY>
{
// ...
public List<Country> Countries { get; set; }
}
and finally to update the countries of a user something like the following:
var user = // get user
var countryToAdd = db.Countries.FirstOrDefault(c => c.Name == countryName) ??
new Country() { Name = countryName };
if (user.Countries == null)
user.Countries = new List<Country>() { countryToAdd };
else if (!user.Countries.Contains(countryToAdd))
user.Countries.Add(countryToAdd);
db.SaveChanges();
And to get all users from one country:
var country = db.Countries.Include(c => c.Users)
.FirstOrDefault(c => c.Name == countryName);
if (country != null)
{
var users = country.Users;
}

Navigation Property null when I do context.Users.Create(). Is that correct?

I'm developing my first class library that uses Entity Framework Code First as Data access layer.
I have this class:
public class User
{
public int UserId { get; set; }
public String Name { get; set; }
public int Age { get; set; }
public String City { get; set; }
public String Country { get; set; }
public String Email { get; set; }
public String InterestIn { get; set; }
public virtual ICollection<User> Friends { get; set; }
public virtual ICollection<User> FromWhomIsFriend { get; set; }
}
And now I testing my code with a Console application:
static void Main(string[] args)
{
Database.SetInitializer(
new DropCreateDatabaseAlways<AdnLineContext>());
insertUsersAndFriends();
}
private static void insertUsersAndFriends()
{
using (var context = new AdnLineContext())
{
var user1 = context.Users.Create();
user1.Name = "User1";
user1.Age = 25;
user1.City = "City1";
user1.Country = "Country1";
user1.Email = "email_1#email.com";
user1.InterestIn = "User1's interests";
var user2 = context.Users.Create();
user2.Name = "User2";
user2.Age = 26;
user2.City = "City2";
user2.Country = "Country2";
user2.Email = "email_2#email.com";
user2.InterestIn = "User2's interests";
var user3 = context.Users.Create();
user3.Name = "User3";
user3.Age = 27;
user3.City = "City3";
user3.Country = "Country3";
user3.Email = "email_3#email.com";
user3.InterestIn = "User3's interests";
context.Users.Add(user1);
context.Users.Add(user2);
context.Users.Add(user3);
user1.Friends.Add(user2);
user3.Friends.Add(user1);
context.SaveChanges();
}
}
I'm testing, so the database is empty.
This is my UserConfiguration class:
public UserConfiguration()
{
Property(d => d.Name).IsRequired();
Property(d => d.Age).IsRequired();
Property(d => d.City).IsRequired();
Property(d => d.Country).IsRequired();
Property(d => d.Email).IsRequired();
Property(d => d.InterestIn).IsRequired();
HasMany(d => d.MessagesSent).WithRequired(l => l.SentByUser).WillCascadeOnDelete(false);
HasMany(d => d.MessagesReceived).WithRequired(l => l.SentToUser).WillCascadeOnDelete(false);
HasMany(d => d.Friends).
WithMany(d => d.FromWhomIsFriend).
Map(c =>
{
c.ToTable("UserFriends");
c.MapLeftKey("UserId");
c.MapRightKey("FriendId");
});
HasMany(d => d.WantsToDo).
WithMany(a => a.Users).
Map(t =>
{
t.ToTable("UserActivities");
t.MapLeftKey("UserId");
t.MapRightKey("ActivityId");
});
}
But I get a null pointer exception here user1.Friends.Add(user2); because Friends is null.
What am I doing wrong? How can I fix this problem?
Entity Framework seems to be smart in this case. You are adding a new User to the context:
var user1 = context.Users.Create();
//...
context.Users.Add(user1);
//...
user1.Friends.Add(user2);
The entity is in Added state after that. Why should EF run a query with lazy loading to initialize the Friends collection? user1 is the principal in the relationships and because the state is Added it cannot exist yet in the database, hence there can't be any dependent in the database refering to it that could be loaded. So, EF does not try to load the collection at all (which is good in order to avoid unnecessary database roundtrips).
You could apply tricks to make it work - by attaching the new users before you add them to the context:
var user1 = context.Users.Create();
//...
var user2 = context.Users.Create();
//...
var user3 = context.Users.Create();
//...
user1.UserId = 1;
context.Users.Attach(user1);
user2.UserId = 2;
context.Users.Attach(user2);
user3.UserId = 3;
context.Users.Attach(user3);
// ...because we cannot attach three users with the same key
user1.Friends.Add(user2);
user3.Friends.Add(user1);
// Lazy loading will run twice here based on the `UserId` which is 1,2,3
// and returns nothing, but the Friends collection will be initialized
// as empty collection
// This MUST be AFTER accessing the Friends collection
context.Users.Add(user1);
context.Users.Add(user2);
context.Users.Add(user3);
// the three "dummy UserIds" are ignored because state is Added now
context.SaveChanges();
Now, just forget this solution again. It's nonsense to force lazy loading (= expensive database query) to create an empty collection. C# has the new operator for this:
var user1 = context.Users.Create();
//...
var user2 = context.Users.Create();
//...
var user3 = context.Users.Create();
//...
user1.Friends = new List<User>();
user1.Friends.Add(user2);
user3.Friends = new List<User>();
user3.Friends.Add(user1);
context.Users.Add(user1);
context.Users.Add(user2);
context.Users.Add(user3);
context.SaveChanges();
You can also just use var user1 = new User() in this scenario where you only add new entities to the context. Creating dynamic proxies has no benefit here (unless you would set any foreign key properties to other existing entities and want to access their corresponding navigation properties after calling SaveChanges - which doesn't seem to be the case in your example).
You must declare your "List<>" Properties like this:
public virtual ICollection<User> Friends { get; set; }
If you don't use virtual keyword EF will not initialize collection for you.
In your case, you are creating new object, use private properties to initialize it for new objects:
private ICollection<User> _friends;
public ICollection<User> Friends {
get { return _friends ?? (_friends = new List<User>()); }
set { _friends = value; }
}
You have to initialize the member Friends like this:
using (var context = new AdnLineContext())
{
context.Users.Add(user1);
context.Users.Add(user2);
context.Users.Add(user3);
user1.Friends = new List<User>();
user1.Friends.Add(user2);
user3.FromWhomIsFriend.Add(user1);
context.SaveChanges();
}
I think that you might want to consider how the database should model the relationship between users. It seems like you want to have a 1:N relationship between users and itself (IE, one user can have multiple other users associated with it). To be honest the only way I know how to achieve this is a lookup table that associates two UserIds together. You could do this in Entity Code First like this:
public class FriendDefinition
{
[ForeignKey("User")]
public int UserId { get; set; }
[ForeignKey("Friend")]
public int FriendUserId { get; set; }
public virtual User User { get; set; }
public virtual User Friend { get; set; }
}
Then you could update your User class like so:
public class User
{
public int UserId { get; set; }
public String Name { get; set; }
public int Age { get; set; }
public String City { get; set; }
public String Country { get; set; }
public String Email { get; set; }
public String InterestIn { get; set; }
public virtual ICollection<FriendDefinition> Friends { get; set; }
}
Finally, you would now use this as follows:
var user = db.context.Users.First();
var firstFriend = user.Friends.First().Friend;
It's a little clunky, but I think it would serve your purpose. Code first is an excellent tool but you still have to conceptualize how the data is actually being stored in the database to model what you need.
You should use the Create method of DbSet - it will give you a fully initialised proxy.
var user1 = context.Users.Create();
user1.Name = "User1";
user1.Age = 25;
user1.City = "City1";
user1.Country = "Country1";
user1.Email = "email_1#email.com";
user1.InterestIn = "User1's interests";
//....
context.Users.Add(user1);
Hard coding things like user1.Friends = new List<User>(); is hack not a solution.

Constructing linq query

I am struggling to figure out a linq statement that I would think is a very common scenario. Assuming the structure below, I have many users, each users has many diaries, each diary has many widgets.
I would like to say, get me the user where UserName = 'bob' and Password = 'password' and that users diaries where DisabledByAdmin == true and the list widgets where DisabledByAdmin == true only the diaries the previous statement returned.
Assuming the data is 1 user that is linked to 2 diaries (one has DisabledByAdmin == true), and those 2 diaries each have 2 widgets (1 has DisabledByAdmin == true), I would like to get my user object back that would have 1 diary object in my list and 1 widget in EACH diary.
So the return data would be user object, with one diary and one widget in that diary collection. For the life of me, I can't figure that out.
Anyone?
Thanks very much
Ralph
public class Widget
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Name { get; set; }
public bool DisabledByAdmin { get; set; }
public bool DisabledByUser { get; set; }
}
public class DigtalDiary
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool DisabledByAdmin { get; set; }
public bool DisabledByUser { get; set; }
public virtual List<Widget> Widgets { get; set; }
}
public class User
{
[Key]
public int Id { get; set; }
public string Password { get; set; }
public string UserName { get; set; }
public virtual List<DigtalDiary> Diaries { get; set; }
}
Something like
var users from u in UserList
where u.UserName = "Bob" and u.Password = "123";
var lookup = new Dictionary<int, List<DigitalDiary>>();
foreach(var u in users)
{
var digitalDiaries = from dd in u.Diaries
where dd.DisabledByAdmin != true; //Or whatever your criteria
lookup.add(u.Id, digitalDiaries.toList());
}
This will give you a dictionary of UserId's that match your criteria, and a list of Ditital Diaries for that user that aren't disabled and whatnot.
Then you can repeat that for Widgets
(Also this was just coded out in the window here, so there might by typos)
Edit:
Also you could use a
new Dictionary<User, List<DigitalDiary>>();
if you'll be wanting the full User Fields as soon as you're done. I'm just used to storing keys
If I understand you correctly, assuming that you have a List collection of User of name myUserList, you may iterate through this list only getting the values you are looking for. Then, if the values you are looking for were found, you may add the User responsible with these values to a new List collection of User.
Example
List<User> myUserList = new List<User>(); //Initialize a new List of Users
//Add users to the list
myUserList.Add(new User() { UserName = "bob", Password = "password" });
myUserList.Add(new User() { UserName = "Joe", Password = "password" });
myUserList.Add(new User() { UserName = "Miranda", Password = "password" });
myUserList.Add(new User() { UserName = "Kevin", Password = "password" });
//
List<User> myGatheredList = new List<User>(); //Initialize a new List of Users of name myGatheredList (not required)
foreach (User _user in myUserList.Where(x => x.UserName == "bob" && x.Password == "password")) //Only get values which match a UserName of value 'bob' and a password of value 'password' as _user for every User
{
Debug.WriteLine(_user.UserName); //Writes 'bob'.
foreach (DigtalDiary _diary in _user.Diaries) //Get every DigitalDiary in _user.Diaries as _diary for every DigitalDiary
{
if (_diary.DisabledByAdmin /*&& _diary.Widgets.Count == x */ /* More conditions can be inserted here */) //Continue if _diary.DisabledByAdmin is true
{
foreach (Widget _widget in _diary.Widgets) //Get every Widget in _diary.Widgets as _widget for every Widget
{
if (_widget.DisabledByAdmin) //Continue if _widget.DisabledByAdmin is true
{
if (!myGatheredList.Contains(_user)) //Continue if _user does not exist in myGatheredList
{
myGatheredList.Add(_user); //Add _user to myGatheredList (not required)
//Do something with _user
}
}
}
}
}
}
NOTICE: This will only get the Users which have got at least ONE DigitalDiary disabled by admin and ONE Widget disabled by admin.
Thanks,
I hope you find this helpful :)
If I understood the question AND you are using EntityFramework
You may need to play with the Select Projection to meet your requirements.
This example is an AND, use || for or condition as appropriate
var mylist = Context.Set<User>.Where(u=>u.Id==userId && u.Password==password
&& u.Diaries.DiabledByAdmin == false
&& u.Diaries.Widgets.DisabledByAdmin == false)
.Select(new {u.Id, u.Diaries.Id,u.Diaries.Widgets.Id}); //remove select if user Object desired

Categories