I have an Entity Framework application using ASP.NET Identity 2.2 (i.e., my context inherits from IdentityDbContext<T> and I have a User class that inherits from IdentityUser). I am successfully seeding the AspNetUsers table using the following calls in my seed method:
var testUser = new User() {
UserName = "TestUser",
Email = "TestUser#Domain.tld"
};
manager.Create(testUser, "TestPassword");
I have extended the model to include a Post class which includes a reference to my User class:
public class Post {
public Post() {}
[Required]
public int Id { get; set; }
[Required]
public User User { get; set; }
}
And, of course, this corresponds to the following in my User class:
public class User : IdentityUser {
public User() : base() {
this.Posts = new HashSet<Post>();
}
public ICollection<Post> Posts { get; set; }
//Additional members...
}
I am then seeding the Posts collection with the following:
var testPost = new Post() { Id = 1, User = testUser };
context.Posts.AddOrUpdate(
post => post.Id,
testPost
);
Technically, this works fine; the Post instance is created, and the automatically generated User_Id field is correctly populated with the Id of the newly created User instance.
So what's the problem? Every time it runs, I get the following in my EntityValidationErrors: "The User field is required". It doesn't prevent my application from working, but it makes it difficult to detect legitimate errors.
Obviously, I could add custom code to my DbContext.SaveChanges() method in order to ignore this error, but I'd rather understand why it's happening in the first place, particularly if there's a problem with how I'm seeding my data.
It seems that when the UserManager.Create() method is called, it doesn't update the User instance with the information needed to create a reference. My assumption is that it's not populating the Id field, but I haven't confirmed.
Regardless, the solution is to reload the user after the call to UserManager.Create(). My final code looks something like:
var manager = new UserManager<User>(new UserStore<User>(context));
var testUser = new User() {
UserName = "TestUser",
Email = "TestUser#Domain.tld"
};
if (manager.Users.Count<User>() == 0) {
manager.Create(testUser, "TestPassword");
}
testUser = (User)manager
.Users
.Where<User>(u => u.UserName == "TestUser")
.FirstOrDefault<User>();
I was then able to seed the Post record the same way as before:
var testPost = new Post() { Id = 1, User = testUser };
context.Posts.AddOrUpdate(
post => post.Id,
testPost
);
Note: If using a [ForeignKey] attribute, it is apparently necessary to assign the User by Id (e.g., UserId = testUser.Id) instead of by object reference (e.g., User = testUser).
In my actual application this is all shuffled off to a CreateUser() helper method so it's easy to create multiple test users, but this covers the basics. This also addresses a flaw in my original code in that I wasn't previously checking to determine if the user had already been created or not.
Related
There are 2 classes
Event
public class Event
{
public Guid? UserID { get; set; }
[ForeignKey("UserID")]
public virtual User User { get; set; }
...
User
public class User
{
public Guid UserId { get; set; }
// Not used in this example, but just thought they might be related to problem
private List<Event> _attendedEvents;
public virtual ICollection<Event> AttendedEvents
{
get { return _attendedEvents ?? (_attendedEvents = new List<Event>()); }
set {
if (value == null)
_attendedEvents = new List<Event>();
else
_attendedEvents = new List<Event>(value);
}
}
public virtual ICollection<Event> HostedEvents { get; set; }
...
EventConfiguration
HasOptional<User>(s => s.User)
.WithMany(s => s.HostedEvents)
.HasForeignKey(s => s.UserID);
The thing I'm trying to do is
Add User to Repository
Add Event(which has same User inside it) to Repository
Save Changes
Retrieve Event back from repository
Everything kind of works, except when I retrieve Event back it has null User, however UserId is valid and points to User i created earlier.
Here's how I'm doing it
// Creates just the User object with specified UserName
var user = ObjectHelpers.CreateUser("ServiceTestUser");
// Adds to Repository + Saves Changes
_client.AddUser(user);
// Find it again to have generated Id and kind of test if it was added
user = _client.FindUserByEmail(user.Email);
// Create Event object and assign specified user object to it
// At this point #event has User set to above one and UserID null
var #event = ObjectHelpers.CreateEvent(user);
// Attach User from Event + Add Event to repository + Save Changes
_client.AddEvent(#event);
// Get it back to Check if everything went fine
// At this point #event has User set to null and valid UserID
#event = _client.GetEventByTitle(#event.EventTitle);
By default EF will not read related entities. And this behavior is really useful. If not, whenever you tried to read an entity from the DB, you'd read that entity, and all the probably very big, tree of related entities.
You must read realted entities:
explicitly, by using .Include()
or implicitly, by using lazy loading, which means accessing the navigation property provided the DbContext hasnot been disposed
Example of Include():
DbCtx.Events.First(ev => ev.Title == "title").Include(ev => ev.User);
For more information on including related entities see this: Loading Related Entities
EDIT-2: After hours of research and almost every odata related link on google turning purple, I found out that the concept of 'deep-inserts' (link) exists in the OData specification. So after all, what I'm doing should work, even without the links. Does anyone know how to enable this on the Microsoft OData client? Are there any other OData clients out there that support that concept?
EDIT: Maybe this is the wrong approach, so please tell me if I'm doing it totally wrong. Not being able to save is really blocking our progress!
I have an issue with OData v3. I have a class Associate that has a required Address. When I try to POST a new Associate, it fails due to the Address property being null (EF6 throws DbUpdateException with foreign key violation). My Associate class looks like this:
public class Associate
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(50)]
public string Role { get; set; }
public bool IsMailReceiver { get; set; }
public bool IsLegalRepresentative { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
public int AddressId { get; set; }
}
I use the Microsoft OData client, and try to add the associate in the following way:
var associate = new Associate { /* ... */ };
context.AddObject("Associates", associate);
context.AddObject("Addresses", associate.Address);
/* UI fills associate data */
context.SetLink(associate, "Address", associate.Address);
context.UpdateObject(associate);
context.UpdateObject(associate.Address);
/* at this point the associate has the address set! */
context.SaveChanges(); // << Exception
On the server, in the controller, the Associate arrives without the foreign key, however. When I inspect the POST request with Fiddler, I see why:
{
"odata.type" : "xxx.Data.Entities.Associate",
"AddressId" : 0,
"Id" : 0,
"IsLegalRepresentative" : false,
"IsMailReceiver" : false,
"Name" : "John Doe",
"Role" : "Father"
}
The address is not transmitted, even though the generated class on the client has an Address property.
How can i solve this problem?
I too could not find any information about this - it really feels like an issue in OData. Here is how I managed to get it to work.
Define the foreign key explicitly
class Student {
public int TeacherId { get; set; }
[Required, ForeignKey("TeacherId")]
public virtual Teacher Teacher { get; set; }
}
When performing the insert, fetch the related record and fix the model state:
public IHttpActionResult Post(Student student)
{
student.Teacher = this.db.Teacher.FirstOrDefault(i => i.TeacherId == student.TeacherId);
if (student.Teacher != null)
{
this.ModelState.Remove("student.Teacher");
}
if (!this.ModelState.IsValid)
{
return this.BadRequest(this.ModelState);
}
}
So from then on to post a Student, you ignore the Teacher field and just post with TeacherId.
I haven't tested this with the OData client, but I can't think of why this wouldn't work. You will just have to use the Id field rather than the object.
Basically when you create the object
var associate = new Associate { /* ... */ };
It is not inserted into the database. It is created in the memory. When you call
context.SaveChanges();
It will be saved in the database. At this point database validation happens and key's are generated. Assuming your Id is unique identifier, that is generated in the datebase, note that in order for it to get updated value back from the database you need to have StoreGeneratedPattern set to Identity from Entity model view.
If this is not done your local context and database context no longer match. If you where to use that object with reference to something else it would fail.
I assume something like this would work:
Address address = new Address{ City = "Tallinn" /*etc*/};
context.SaveChanges();
//At this point Address will be in database context and has Id
associate = new Associate {
name = "Margus",
role = "Admin",
receiver = true,
representative = true,
AddressId = address.id
};
context.SaveChanges();
There is no solution to this. I will roll my own context with a notion of change sets that works with web-api. I will put it on github, when I'm done.
The only way addlink and setlink work is if the foreign key is nullable and you ahvbe to create a postput function call create link see here
I came across this and I can confirm that it is indeed a problem with the OData client (proxy), although I haven't found any references about it.
I managed to fix it using a workaround, which is not 100% perfect but works for me. Here is the code and I will explain about its shortcomings.
public static class DataServiceContextExtensions
{
public static int PostChanges(this DataServiceContext context)
{
using (var client = new WebClient())
{
client.Credentials = context.Credentials;
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var entities = context.Entities.Where(x => x.State == EntityStates.Added);
foreach (var descriptor in entities)
{
var url = $"{context.BaseUri}{descriptor.Entity.GetType().Name}";
var data = JsonSerializer.Serialize(descriptor.Entity);
var response = client.UploadString(url, data);
context.ChangeState(descriptor.Entity, EntityStates.Detached);
}
return entities.Count();
}
}
}
As you can see, I am using an extension method over DataServiceContext where I iterate through all the entities stored in the change tracker which are marked as added, and then I use a WebClient to POST a JSON serialized version of them to the OData endpoint, using any credentials that the proxy might have.
Problems:
First, it only deals with added entities, I'm OK with that, but others may need a more comprehensive solution. The only way I see is to replace WebClient with some other client that can do arbitrary HTTP verbs.
Second, it doesn't hydrate the entities with the generated primary key. Again, I don't need it in my case, but this one will be difficult to solve, as OData does not seem to return it on the result of UploadString.
Third, all URLs are always, by convention, assumed to be composed as BaseUri + entity type (e.g., https://foo.bar/MyEntity). This may or may not always be the case, not 100% sure, again, works in my case.
this may be a simple problem for some of you, but I am have a difficult time trying to resolve it.
I have a Message.cs class:
public class Message : Audit
{
public int MessageId { get; set; }
public string TextBody { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
}
I am using entity framework to have secure log ins and etc.
When I post a message, I have set break points to see that the message object that I am sending does contain the UserId, but when db.SaveChanges(); is called, there is an error:
"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_dbo.Messages_dbo.AspNetUsers_ApplicationUser_Id\". The conflict occurred in database \"aspnet-ChatApp-20140708035313\", table \"dbo.AspNetUsers\", column 'Id'.\r\nThe statement has been terminated."
Also, I am using data adapters and interfaces to work with my APIs.
my MessageDataAdapter for POST looks like this: (EDITTED LOOK BELOW)
public Models.Message PostMessage(Models.Message newMessage)
{
ApplicationUser user = new ApplicationUser();
Message message = new Message();
newMessage.TextBody = message.TextBody;
newMessage.DateSent = DateTime.Now;
newMessage.Hidden = message.Hidden;
newMessage.UserId = user.Id;
db.Messages.Add(newMessage);
db.SaveChanges();
return newMessage;
}
When I set a breakpoint, newMessage contains the necessary data plus the UserId when I am logged in, for I have it authroized so only "Users" can POST to the DATABASE. However, when it reaches db.SaveChanges that error occurs.
Any ideas on how to resolve this issue?
Thank you!
EDIT
this is what I have(I guess what I really want to be able to do is to be able to POST Message containing the UserId of whoever is logged in):
public Models.Message PostMessage(Models.Message newMessage)
{
ApplicationUser user = new ApplicationUser();
Message message = new Message();
message.TextBody = newMessage.TextBody;
message.DateSent = DateTime.Now;
message.Hidden = newMessage.Hidden;
message.UserId = newMessage.UserId;
db.Messages.Add(message);
db.SaveChanges();
return message;
}
My front end HTML has a input type hidden with the value being "User.Identity.GetUserId()" which I want to be POSTed with the Message.
Why are you creating new user when posting message?
Retrive the old-one from db.AspNetUsers(or db.Users ?). Or if creating new user is really needed, then you must also add user to db.AspNetUsers repository - id will be generated only when you add user to repository. If not, then user.Id property will be default - i.e. 0 for integer type. But there is no user with id=0 in AspNetUsers table. This error says about that.
This question is a continuation of:
EntityFramework adding new object to a collection
Now I understand that when using DbSet EF won't load the entire collection into memory
But what if I have something like the following code:
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleID { get; set; }
public string RoleName { get; set; }
public User User { get; set; }
}
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
public class SomeClass
{
public void AssignRoleToUser(int userID, Role role)
{
var ctx = new MyContext();
var user = ctx.Users.First(x => x.UserID.Equals(userID));
user.Roles.Add(role);
ctx.SaveChanges();
}
}
In this case, I'm not using the DbSet object to add a new object to the Role collection, instead, I'm adding a new role to a specific user using an ICollection collection
So what happens in this case?
Does EntityFramewrk have to load into memory all the user's roles in order to perform the insert?
In provide code above you are not adding new role as your ctx.Users is just used to retrieve data. Somewhat similar issue is addressed in this SE post - Linq To Entities - how to filter on child entities.
I would advice to look at this short and useful article - Entity Framework 4.0 FAQ – Getting Started Guide.
No. EF does not need to know what 'Roles' the User has.
You need to learn what happens in regards to change tracking:
Once the query is run the change tracker receives an object entry for 'user'. The state of this user is 'Unchanged'
You add a new role to the user's Roles collection. This simply adds a change tracker entry for this new role and marks it as 'Added'
On SaveChanges() EF will look at your change tracker and see that the user object has not changed so nothing needs to be done there. There is also an entry for the new role which states that it needs to be added. So an SQL query will be written to insert this role.
Simple as that. You can always debug and add a watch for the state of the change tracker. The entries can be found by calling DbContext.ChangeTracker.Entries().
EF will blindly send off the 'add' to the DB.
I have an Entity in EF called Advertiser and another called Client. Advertisers have a association field called Client, which is selected from a dropdownlist. I want to know how to save this association to the database. The approach I've used is to find the Client object (by using the Id) and then assign this Client object to the Advertiser.Client navigation property. I hoped that by then Adding this Advertiser property, I'd have added an Advertiser that is associated with an existing Entity. However this was not the result. Instead, a new Client record also got added to the table. How do I fix this?
Full explanation and code bits are below...
public class Advertiser
{
public int Id { get; set; }
public string Name { get; set; }
//Navigation Properties
public virtual Client Client { get; set; }
}
And another called Client
public class Client
{
public Client()
{
Advertisers = new List<Advertiser>();
}
public int Id { get; set; }
public string Name { get; set; }
// Navigation Properties
public virtual ICollection<Advertiser> Advertisers { get; set; }
}
A bunch of clients are added to the database in a separate view. When the user lands on the Advertiser views, they have the create option. What I want the create to do is allow the user to pick a client from a drop down list containing all clients. I want this advertiser to then be associated with that client.
This is the code for the controller:
//
// POST: /Advertiser/Create
[HttpPost]
public ActionResult Create(Advertiser advertiser,int Id)
{
if (ModelState.IsValid)
{
advertiser.Client = clientRepo.Retrieve(Id); // Finds and returns a Client object
System.Diagnostics.Debug.WriteLine("Saving Advertiser, with int Id = {0} and Client.Id = {1}", Id, advertiser.Client.Id);
repo.Create(advertiser);
repo.SaveChanges();
return RedirectToAction("Index");
}
return View(advertiser);
}
The Advertiser view populates a dropdownlist with all the Clients and then returns the Id for the currently selected Client.
<div class="editor-field">
#{
var clients = new Repository<Client>().FetchAll();
var clientLists = new SelectList(clients, "Id", "Name");
}
#Html.DropDownList("Id", clientLists)
</div>
Now, this view correctly returns the correct Id. The Debug.Writeline also confirms that the correct Id is being passed back. The problem lies in what happens after that...
Instead of inserting a new Advertiser that is associated with the existing Client entity, what it does is, it first inserts an Advertiser, and then inserts a copy of the Client entity to the database. This results in duplicate Clients that differ only in primary key (Id),
I know this can be solved by exposing the foreign key and passing the foreign key instead of finding and referencing the appropriate Client to the the Advertiser.Client property. But if possible I'd prefer to do this without exposing foreign keys. Is there some way this can be done? ... i.e. What am I doing wrong?
If what goes on in the Repository class could be useful to answer this question, I've added it below:
public OperationStatus Create(TEntity item)
{
OperationStatus status = new OperationStatus {Status = true};
var value = DataContext.Set<TEntity>().Add(item);
if(value == null)
{
status = null;
}
return status;
}
public TEntity Retrieve(int id)
{
return DataContext.Set<TEntity>().Find(id);
}
Add a [Key] Attribute to the ID property on both Client and Advertiser