Hello everybody.

I'm confused a little :-/

DO is such complex system :)

Here is code. Comments below.

Sorry for huge code. It's simply :) But there is no another way to show all cases.


// public class Project : Entity
//{
//    [Field, Key]
//    public int Id { get; private set; }

//    [Field]
//    public string Name { get; set; }

//    [Field]
//    public EntitySet<task> Tasks { get; private set; }

//    [Field]
//    [Association(PairTo = "Projects")]
//    public EntitySet<user> Users { get; set; }
//}
// public class User : Entity
//{
//    [Field, Key]
//    public int Id { get; private set; }
//    [Field]
//    public string Name { get; set; }
//    [Field]
//    public EntitySet<project> Projects { get; private set; }
//}

//   public class Task : Entity
//{
//    [Field, Key]
//    public int Id { get; private set; }

//    [Field]
//    public string Name { get; set; }

//    [Field]
//    [Association(PairTo = "Tasks")]
//    public Project Project { get; set; }
//}

//DS - disconnected state, S - session

Session session = Session.Open(DomainManager.Domain);

var disconnectedState = new DisconnectedState();

using (var disconnectedScope = disconnectedState.Attach(session))
{
    using (disconnectedState.Connect())
    {
        Query.All<project>().Prefetch(z => z.Users).Run();
        Query.All<user>().Run();
        //all assosiations ok now, it's first time fetching
    }

    //ONE TO MANY assosiation (Project->Tasks)

    var project = new Project { Name = "z1" };
    using (disconnectedState.Connect())
    {
        var task = Query.Single<task>(7);
        project.Tasks.Add(task);

        var testTask = project.Tasks.FirstOrDefault(u => u.Id == 7); // 1) null
        foreach (var taskInner in project.Tasks)
        {
            var x = taskInner.Id;// 2) x == 7 is true
        }

        var taskProject = task.Project; // 3) not null
    }
    // I think, I understand 1) and 2). 1) is Query, so DS goes to S, but there is NO
    // new project entity in S, because we haven't applied changes yet.
    // 2) - it is only Read (enumerate collection) and 3) so. That's why we have such behavior.

    disconnectedState.ApplyChanges();

    //now everything ok
    using (disconnectedState.Connect())
    {
        var task = Query.Single<task>(7);

        var testTask = project.Tasks.FirstOrDefault(u => u.Id == 7); // 4) not null

        foreach (var taskInner in project.Tasks)
        {
            var x = taskInner.Id;// 5) x == 7 is true
        }

        var taskProject = task.Project; // 6) not null
    }

    //MANY TO MANY
    var project2 = new Project { Name = "z2" };
    using (disconnectedState.Connect())
    {
        var user = Query.Single<user>(4);
        project2.Users.Add(user);

        // accessing via project
        var uuu = project2.Users.FirstOrDefault(u => u.Id == 4); // 7) null
        foreach (var userInner in project2.Users)
        {
            var x = userInner.Id;// 8) never comes here, enumerates no result
        }

        // accessing via user
        var ppp = user.Projects.FirstOrDefault(u => u.Name == "z2"); // 9) null
        foreach (var projectInner in user.Projects)
        {
            var x = projectInner.Id;// 10) ok, we have Id = -2
        }
    }

    //I can understand it too, I think. Many-to-many - there are entites like User-Projects-Project.
    //So, only after calling ApplyChanges such entities will be passed to Session.
    //And when I try to enumerate project2.Users — I can't access already created entities User-Projects-Project.
    //But I can access user.Projects, because user have been stored in session earlier.

    disconnectedState.ApplyChanges();

    using (disconnectedState.Connect())
    {
        var user = Query.Single<user>(4);

        // accessing via project
        var uuu = project2.Users.FirstOrDefault(u => u.Id == 4); // 11) not null
        foreach (var userInner in project2.Users)
        {
            var x = userInner.Id;// 12) Oops! never comes here, enumerates no result
        }

        // accessing via user
        var ppp = user.Projects.FirstOrDefault(u => u.Name == "z2"); // 9) not null
        foreach (var projectInner in user.Projects)
        {
            var x = projectInner.Id;// 10) ok, we have Id = 19
        }
    }

    //So, 12) confused me :) 

}//disposing disconnectedscope of first disconnectedstate

//Creating new disconnected state
var disconnectedState2 = new DisconnectedState();
using (var disconnectedScope = disconnectedState2.Attach(session))
{

    using (disconnectedState2.Connect())
    {
        var project2 = Query.Single<project>(19);//is Id of project with name z2

        var user = Query.Single<user>(4);

        // accessing via project
        var uuu = project2.Users.FirstOrDefault(u => u.Id == 4); // 13) not null
        foreach (var userInner in project2.Users)
        {
            var x = userInner.Id;// 14) ok, Id = 4
        }

        // accessing via user
        var ppp = user.Projects.FirstOrDefault(u => u.Name == "z2"); // 15) not null
        foreach (var projectInner in user.Projects)
        {
            var x = projectInner.Id;// 16) ok, Id = 19
        }

        //Modifying project
        project2.Name = "z2_";
    }

    disconnectedState2.ApplyChanges();

    using (disconnectedState2.Connect())
    {
        var project2 = Query.Single<project>(19);

        var x = project2.Name;//z2_, i.e. saved successfully
    }
}//disposing disconnectedscope of second disconnectedstate

//Trying to merge, and read from first disconnectedState

disconnectedState.Merge(disconnectedState2, MergeMode.PreferNew);

using (var disconnectedScope = disconnectedState.Attach(session))
{

var project2 = Query.Single<project>(19);//ok, that ensures, that we use DS cache
var name = project2.Name;//z2_, i.e. merged successfully

    using (disconnectedState.Connect())
    {

        var user = Query.Single<user>(4);

        // accessing via project
        var uuu = project2.Users.FirstOrDefault(u => u.Id == 4); // 17) not null
        foreach (var userInner in project2.Users)
        {
            var x = userInner.Id;// 18) Oops! never comes here, enumerates no result
        }

        // accessing via user
        var ppp = user.Projects.FirstOrDefault(u => u.Name == "z2_"); // 19) not null
        foreach (var projectInner in user.Projects)
        {
            var x = projectInner.Id;// 20) ok, we have Id = 19
        }
    }

    // 18) confused me again :) 

}

Please confirm that I have understood correctly.

And questions: Why I can't use 12) and 18) ? If I dispose session and attach DS again - everything is ok. But it that case there is no need in DS, I think. And it seems Merge() merges only fields, not EntitySets.


upd. We've got magic :)


// public class Project : Entity
//{
//    [Field, Key]
//    public int Id { get; private set; }

// ....

//    [Field]
//    public EntitySet<user> Users { get; set; }
//}
// public class User : Entity
//{
//    [Field, Key]
//    public int Id { get; private set; }
// ...
//    [Field]
//    [Association(PairTo = "Users")]
//    public EntitySet<project> Projects { get; private set; }
//}

If I switch Association PairTo from Project to Users, everything works fine.

12) and 18) enumerates OK.

We'll continue researching.


upd2.

Another case. Changing way of adding association in One-To-Many. Now we create new task, and associate it with existing project.


// public class Project : Entity
    //{
    //    [Field, Key]
    //    public int Id { get; private set; }

    //    [Field]
    //    public string Name { get; set; }

    //    [Field]
    //    public EntitySet<task> Tasks { get; private set; }

    //  ...
    //}

//   public class Task : Entity
    //{
    //    [Field, Key]
    //    public int Id { get; private set; }

    //    [Field]
    //    public string Name { get; set; }

    //    [Field]
    //    [Association(PairTo = "Tasks")]
    //    public Project Project { get; set; }
    //}

 var taskx = new Task { Name = "t1" };
                using (disconnectedState.Connect())
                {
                    var projectx = Query.Single<project>(5);
                    taskx.Project = projectx;

                    var testTask = projectx.Tasks.FirstOrDefault(u => u.Name == "t1"); // 1) null
                    foreach (var taskInner in projectx.Tasks)
                    {
                        var x = taskInner.Id;// 2) NO results
                    }

                    var taskProject = taskx.Project; // 3) not null
                }

2) - confusing.

I think that the Association Pair-To strangely works in the DS or I don't understand something.

And note: if we don't use DS, and repeat this cases in one transaction, everything is ok.

Alex, help me, please :)


upd4.

There are some cases to test.

Context: attached disconnected state. All cases in opened session and transaction without disconnected state works fine (at least we did not find any problems).


class User
{
[Field]
[Association(PairTo="Users")]
public EntitySet<project> Projects;
}

class Project { [Field] public EntitySet<users> Users; }

1) create new Project and add existing user to it via project.Users 2) create new Project and it to existing user via user.Projects

3) create new User and add existing project to it via user.Projects 4) create new User and it to existing project via project.Users

5) create new User and new Project and try to add them to each other via project.Users, user.Projects

6) get existing User and existing Project and try to add them to each other via project.Users, user.Projects

After every case should check querying and fetching: user.Projects.FirstOrDefault, project.Users.FirstOrDefault, enumerate user.Projects and project.Users.

Then we switch PairTo like this:


class User
{
[Field]
public EntitySet<project> Projects;
}

class Project { [Field] [Association(PairTo="Projects")] public EntitySet<users> Users; }

And repeat all cases again.

All cases also can be repeated with OneToMany.

asked Nov 16 '10 at 14:37

Ness's gravatar image

Ness
155232328

edited Nov 17 '10 at 11:29

1

Studying this right now, the answer will appear quite soon :)

(Nov 17 '10 at 08:21) Alex Yakunin Alex%20Yakunin's gravatar image

One Answer:

Case 1: ONE TO MANY assosiation (Project->Tasks), labels 1-6

Your understanding is correct here.


Case 2: MANY TO MANY, labels 7-20

8) never comes here, enumerates no result - strange, we'll check this. The item must be returned on enumeration. I suspect it tries to load the EntitySet for new item, because it is paired, although it shouldn't.

In general, new items in entity sets (incl. paired) must be fully visible, if:

  • EntitySet was loaded into DS,
  • or EntitySet belongs to a newly created Entity.

Otherwise (i.e. if EntitySet wasn't loaded) you really have a chance of getting the content of EntitySet refreshed, and thus getting new item lost. We'll think how to deal with this as well - i.e. probably, we'll handle even this case.

12) Oops! never comes here, enumerates no result - that's because EntitySet was already loaded (as empty), so DO doesn't see the reason to refresh it.

18) Oops! never comes here, enumerates no result - that's strange again. Merge really should ensure the newer state overwrites the old one. So we'll check this as well.


Everything else

If I switch Association PairTo from Project to Users, everything works fine.

...

2) - confusing.

Most likely, this means there is a bug related to loading of state of paired entity set bound to newly created entity. It was "fine" because you check the operations from one side only. Reversing them lead to exposure of the same issue. Most likely we have something similar in our tests, i.e. not all the cases are covered there.


Conclusion

Likely, there are two bugs:

  • Unnecessary attempt to load the state of paired EntitySet bound to newly created entity
  • EntitySet content merge.

I'll create issues for them, and we'll try to fix this ASAP.

answered Nov 17 '10 at 08:49

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

edited Nov 17 '10 at 08:52

Ok, thanks.

As I understand, after 8) will be fixed, 12) will be fixed automatically. Other cases looks like bugs.

I thought that maybe I didn't understand something, so create this post. We tried to check many cases. Only important I've posted.

I've added update to my question, seems to be it describes all cases (without results), maybe it helps you.

(Nov 17 '10 at 11:28) Ness Ness's gravatar image

As I understand, after 8) will be fixed, 12) will be fixed automatically.

Yes.

Other cases looks like bugs.

I see just 8+12 and merge bug here. Did I miss something?

Ah, it seems this is related to the latest update. If so, yes, we'll check everything you mentioned in our test.

user.Projects.FirstOrDefault, project.Users.FirstOrDefault

This should work as it works now - i.e. this is a LINQ query, thus new items won't appear there before ApplyChanges, and that's expected.

(Nov 17 '10 at 16:58) Alex Yakunin Alex%20Yakunin's gravatar image

Issue 848 and Issue 849 are created.

(Nov 17 '10 at 17:05) Alex Yakunin Alex%20Yakunin's gravatar image

Btw, thanks for so comprehensive description. That's really helpful!

(Nov 17 '10 at 17:07) Alex Yakunin Alex%20Yakunin's gravatar image

You are welcome.

DO is very useful for me and my team, so we consider that our duty is to help you develop the product, even we can only search bugs :)

(Nov 18 '10 at 13:23) Ness Ness's gravatar image

I totally agree with Ness. DO is the best thing that happened to our project. My colleques cheer almost every time they have to make a change to the model. DO just does a fabulous job upgrading the DB. Together with X-Tensive we're going to try and make DO the best ORM there is.

(Nov 21 '10 at 05:58) Paul Sinnema Paul%20Sinnema's gravatar image

Thanks a lot for your positive attitude, we really appreciate your feedback! =)

(Nov 22 '10 at 10:30) Dmitri Maximov Dmitri%20Maximov's gravatar image

Is there progress on these issues?

(Jan 09 '11 at 16:25) Sebastian Hauer Sebastian%20Hauer's gravatar image

/push (see above)

(Jan 11 '11 at 10:52) Sebastian Hauer Sebastian%20Hauer's gravatar image

Unfortunately, not yet. The issues are in queue.

(Jan 29 '11 at 04:27) Dmitri Maximov Dmitri%20Maximov's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA