0

I want to update a record type graph, which modify the parent object and child objects also having internally. The problem usually occurs when EF UPDATE registry for children puts them to NULL in where children helps identify the records you are updating.

My domain classes are:

This class helps me to change the status, Add - Customized - delete, which is the graph from the parent as all children. The function that makes it work.

public interface IObjectWthState
{
    State State { get; set; }
}
public enum State 
{
    Added,
    Unchanged,
    Modified,
    Deleted
}

This is a User Class:

 public abstract class User : IObjectWthState 
 {
    public int Id { get; set; }
    public String Name { get; set; }
    public String LastName { get; set; }        
    [Timestamp]
    public byte[] RowVersion { get; set; }
    [NotMapped]
    public State State { get; set; }
}

And here are the two classes that inherit from user:

public class AdminUser:User
{
    public ICollection<BasicUser> UsersList { get; set; }
    public String Email { get; set; }
}

public class BasicUser: User
{
    public String Position { get; set; }
    public String Department { get; set; }
}

As seen AdminUser BasicUser has a list.

The model was generated as wanted, needed to detect a foreign key and add. Here a picture of the DB:

Model generated with migration

This is the function that adds or updates the infromación:

public virtual void AddUpdateGraph(T entity)
{

    if (((IObjectWthState)entity).State == State.Added)
    {
        DbEntityEntry dbEntityEntry = dbContext.Entry(entity);
        dbEntityEntry.State = EntityState.Added;
    }
    else
    {
        dbSet.Add(entity);
        dbContext.ApplyStateChanges();
    }
} 

The function that handles go and adjust the status of internal nodes:

public static void ApplyStateChanges(this DbContext context)
{
    foreach (var entry in context.ChangeTracker.Entries<IObjectWthState>())
    {
        IObjectWthState stateInfo = entry.Entity;
        entry.State = StateHelpers.ConvertState(stateInfo.State);
    }
}  

Function that returns the state to EF:

public static EntityState ConvertState(State state)
{
    switch (state)
    {
        case State.Added:
            return EntityState.Added;
        case State.Modified:
            return EntityState.Modified;
        case State.Deleted:
            return EntityState.Deleted;
        default:
            return EntityState.Unchanged;
    }
}

When you want to add a new AdminUser with his list BasicUser everything works fine with no problem, the problem comes when you want to modify the EF BasicUser AdminUser and generates updates for the BasicUser but added a condition that the foreign key is null.

Here you can see the two updates that are generated

AdminUser:

 exec sp_executesql N'update [dbo].[User]
set [Name] = @0, [LastName] = @1, [Email] = @2
where (([Id] = @3) and ([RowVersion] = @4))
select [RowVersion]
from [dbo].[User]
where @@ROWCOUNT > 0 and [Id] = @3',N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 nvarchar(max) ,@3 int,@4 binary(8)',@0=N'Beto',@1=N'Guerere',@2=N'beto@gmail.com',@3=3,@4=0x0000000000000801

BasicUser:

exec sp_executesql N'update [dbo].[User]
set [Name] = @0, [LastName] = @1, [Position] = @2, [Department] = @3, [AdminUser_Id] = @4
where ((([Id] = @5) and ([RowVersion] = @6)) and [AdminUser_Id] is null)
select [RowVersion]
from [dbo].[User]
where @@ROWCOUNT > 0 and [Id] = @5',N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 nvarchar(max) ,@3 nvarchar(max) ,@4 int,@5 int,@6 binary(8)',@0=N'Viomar',@1=N'Guerere',@2=N'Supervisora',@3=N'Ventas',@4=3,@5=4,@6=0x0000000000000802

As you see in the EF generated SQL command to do the update of BasicUser is added the condition of the record has the Null value to a AdminUser_Id. I do not understand the reason for this. The field can not be null because that user is assigned to a supervisor.

I hope I explained.

Thank you very much for any help you can give me.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
Ozplc
  • 1,091
  • 2
  • 10
  • 15
  • You should load `BasicUser.AdminUser` before setting it to another admin user. Or set the `AdminUser_Id` property. – Gert Arnold Nov 11 '12 at 22:58
  • Thanks for your answer, but as you can see above in the definition of basicUser not provided any foreign key, in any part of the code referenced or defined it, the only connection between basicUser and AdminUser is the list that has the AdminUser it belongs in the basicUser classes is defined. From what I understood every object has an internal state that EF uses to determine when you are performing an upgrade - insertion - delete registry therefore should put that internal state BasicUser by modified, it knows it has to update information in the database. – Ozplc Nov 12 '12 at 12:40
  • Then remove the user from one admin user's `UsersList` collection and add it to an other's `UsersList`. – Gert Arnold Nov 12 '12 at 13:25

2 Answers2

0

Are you retrieving a basicuser that already has an adminuser assigned? It looks to me like the adminuserid is not being retained from the time you retrieve it to the time you are doing the update. I see this frequently, for example in MVC applications where you don't want to display that foreign key and therefore you don't include it in the view. But when you update and send the object to the controller, it creates a new user instance that has the null FK. And as you are using that FK as a concurrency checking field, it is doubly important that you retain the value as you move from controller to view to controller. IF this is the case, luckily I already have a blog post on this problem so I don't ahve to reexplain in full here: http://thedatafarm.com/blog/data-access/round-tripping-a-timestamp-field-with-ef4-1-code-first-and-mvc-3/

Let me know if that is, indeed, the case. If so, hopefully it solves your problem. If not, I'll probably have more questions! :)

(p.s. the rest of your code looks pretty familiar! :) does that come from my & Rowan's book on DbContext? :) )

Julie Lerman
  • 4,602
  • 2
  • 22
  • 21
  • Thank you for your reply! Sure you are familiar! I really congratulate you again for everything you have posted and the material I used to learn about EF. Well I understand your explanation, but it does not apply in this case. In Class basicUser there is no variable that references a foreign key to the id of AdminUser. – Ozplc Nov 13 '12 at 22:12
  • The FK was defined by declaring a collection called UsersList which is a list of BasicUser found within AdminUser (AdminUser has a list of BasicUser. A BasicUser should always be assigned an AdminUser. A basicUser cannot be an orphan) assuming that EF is responsible for handling all matters relating to the foreign key. I can only modify the BasicUser if and only if I have the AdminUser it belongs to, therefore this BasicUser is in that AdminUsers's UsersList. When I add an AdminUser with its UsersList (List of BasicUser) it works perfectly and is stored the correct foreign key. – Ozplc Nov 13 '12 at 22:13
  • The problem happens when I do an Update of any of its child BasicUser in the UsersList. I hope I explained myself, in case you have questions please do not hesitate to ask me. – Ozplc Nov 13 '12 at 22:13
  • The flag here is that EF thinks that the original value of the adminid fk is empty. Without debugging your app myself I can't see how this is happening. I understand the graph is disconnected. Before its disconnected, does it have the fk value? It looks like when it is reattached the value is null. But since its used for concurrency it requires the original value. Another question: did you intend for that nproperty to be used for concurrency anyway? – Julie Lerman Nov 14 '12 at 13:30
  • Answering your question I would like able to use the property of concurrence, As you make clear in effect the application if it work that way. I have a question, probably all this it is a mistake of my design, is required to declare and work with FK in my domain class?? , not enough to indicate as seen in AdminUser that has the list and EF generate de FK automatic.For all that we have discussed and trying to analyze the situation objectively I think that's my mistake. I need to define the FK Manually in the Domanin Class? I am right in my conclusion? – Ozplc Nov 14 '12 at 20:01
  • Oh I"m so sorry. Focused on other things, I didn't catch that you don't have a foreign key property. It does create trouble with disconnected applicatations when there is no FK property in the dependent class. The *database* knows the FK value but the BasicUser has absolutely no idea that it's there. I still can't figure out why code first is using the field as a concurrency field but since there is no original value available it is seen as null. If you look at this article: http://msdn.microsoft.com/en-us/magazine/hh708747.aspx in section Problems with Updates When There’s No Foreign Key .. – Julie Lerman Nov 15 '12 at 00:45
  • ...youll see an explanation of the problem. Things are *always* easier with a foreign key property. Hopefully adding it will align with your model. – Julie Lerman Nov 15 '12 at 00:46
  • Adding the variable in the entity BasicUser and refer in the creation of the model who is the FK,I could verify that already the problem does not exist that prensentaba to realize the update, I think to publish a solution to my problem. The only thing that I do not still have totally clear is the fact that EF knows BasicUser has a FK and in the code it is possible to see that he is in the list of AdminUser, it is possible to sail when the byline returns from the client to update, but when it generates the SQL it does not bear it in mind. – Ozplc Nov 16 '12 at 16:37
  • I understand EF he is not a magician but maybe that leap come sometime manage to achieve a higher level of abstraction when do Dominian Class using Code Fist. Julie there is something that I do not understand on what you say to me of the field of concurrence, for which it does not seem to you??? It wanted to be able to deal to what you refer to understand little more. Again I am grateful for your help and having patience to myself in this case, The development in EF is for One page Applications. – Ozplc Nov 16 '12 at 16:38
0

Answering my question to me: EF believes the FK in the database anyhow you have to work with her inside the model as variable of this form if you work disconnected you be to be sure that this your information being stored.

The first thing that has to be done is to add in the class BasicUser a variable of type int the one that we will call AdminUser_Id:

public class BasicUser: User
    {       
        public String Position { get; set; }
        public String Department { get; set; }
        public int AdminUser_Id { get; set; }
    }

Then we have to tell EF that the variable added is a FK that references a AdminUser AdminUser and can have many BasicUser.

To do it, we go where we are declaring the contex, DataLayer (In My case), and in the cuncion where this being created the model we add the following annotation:

modelBuilder.Entity<AdminUser>()HasMany(A => A.UsersList ).WithRequired().HasForeignKey(B => B.AdminUser_Id );

We update the model and with these adjustments the mistake is eliminated completely at the moment of realizing the update of the record.

For more information about FK and EF recommend reading this article:

  1. Making Do with Absent Foreign Keys
  2. Code First Relationships Fluent API

Thank you very much to all especially to Julie Lerman for your help, without it I would have cost a little more to get to that solution.

I hope I have explained.

Community
  • 1
  • 1
Ozplc
  • 1,091
  • 2
  • 10
  • 15