I'm trying O2O mapping feature of DO4 and found strange thing with int key generator.

My scenario:

have server model class User:

[Serializable]
[HierarchyRoot]
public class User : Entity
{
    [Key, Field]
    public int Id { get; private set; }

    [Field(Length = 200)]
    public string Name { get; set; }
}

As opposite, have Dto class UserDto for this:

[Serializable]
public class UserDto
{
    public int Id { get; set; }
    public string Key { get; set; }
    public string Name { get; set; }

    public UserDto() : this(Guid.NewGuid().ToString()) { }

    public UserDto(string key)
    {
        this.Key = key;
    }
}

Build mapping like this:

var mapping = new MappingBuilder()
    .MapType<User, UserDto, string>(user => user.Key.Format(), userDto => userDto.Key,
        userDto => new object[] {userDto.Id}).TrackChanges(userDto => userDto.Id, false)
    .Build();

and than i create Dto users and push changes back to DO4 to store these new users into underlying storage (sql database e.g.):

using (Session.Open(domain))
{
    using (var transactionScope = Transaction.Open())
    {
        List<object> newUsers = new List<object>();
        newUsers.AddRange(new[] 
            {
                new UserDto { Name = "Lara Croft" },
                new UserDto { Name = "Jurij Gagarin" }
            }
        );

        var mapper = new Mapper(mapping);
        using (var comparisonResult = mapper.Compare(null, newUsers))
        {
            comparisonResult.Operations.Replay();
        }

        transactionScope.Complete();
    }
}

When execute this code it throws me this exception:

An item with the same key has already been added.

   at Xtensive.Indexing.Index`2.InternalAdd(DataPage`2 page, TKey key, TItem item) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Indexing\Xtensive.Indexing\Index.Implementation.cs:line 158
   at Xtensive.Indexing.Index`2.Add(TItem item) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Indexing\Xtensive.Indexing\Index.Modification.cs:line 23
   at Xtensive.Storage.Providers.Index.Memory.MemoryIndexStorageView.Insert(Tuple key, Tuple value, String tableName) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage.Providers.Index\Memory\MemoryIndexStorageView.cs:line 121
   at Xtensive.Storage.Providers.Index.Memory.MemoryIndexStorageView.ExecuteUpdateCommand(UpdateCommand command) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage.Providers.Index\Memory\MemoryIndexStorageView.cs:line 85
   at Xtensive.Storage.Providers.Index.Memory.MemoryIndexStorageView.Execute(Command command) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage.Providers.Index\Memory\MemoryIndexStorageView.cs:line 36
   at Xtensive.Storage.Providers.Index.SessionHandler.Persist(IEnumerable`1 persistActions, Boolean allowPartialExecution) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage.Providers.Index\SessionHandler.cs:line 103
   at Xtensive.Storage.Providers.SessionHandler.Persist(EntityChangeRegistry registry, Boolean allowPartialExecution) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\Providers\SessionHandler.Persist.cs:line 29
   at Xtensive.Storage.Session.Persist(PersistReason reason) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\Session.Persist.cs:line 88
   at Xtensive.Storage.Session.CommitTransaction(Transaction transaction) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\Session.Transactions.cs:line 129
   at Xtensive.Storage.Transaction.Commit() in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\Transaction.cs:line 180
   at Xtensive.Storage.TransactionScope.Dispose() in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\TransactionScope.cs:line 54
   at Xtensive.Storage.OperationLog.Replay(Session session) in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\OperationLog.cs:line 70
   at Xtensive.Storage.OperationLog.Replay() in d:\Temp.Projects\DO4\DO4.Sources\hg\Xtensive.Storage\Xtensive.Storage\OperationLog.cs:line 50
   at DO4.O2O.Sample1.Program.Main(String[] args) in D:\Temp.Projects\DO4\TestSamples\DO4.O2O.Sample1\DO4.O2O.Sample1\Program.cs:line 69
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

I made my reasearch of yours unit test for O2O mapping - MapperTest.cs and found that my problem is that objects of UserDto does not have initialized its property Id and both 2 new users have same value 0, which i get it is wrong. I used pattern to set the value of Id by getting hascode value from generated Guid value (yours unit test gets value from random object which i dont like). I change constructor of my UserDto class to this:

public UserDto(string key)
{
    this.Key = key;
    this.Id = key.GetHashCode();
}

Important here is line this.Id = key.GetHashCode(); than everything works as excepted and new 2 rows are added to database.

Now whats my point of this issue is that, these 2 new rows in db have id values like -1725842688 and -150163054 (generated has hashcode from guid value). I know that i set this Id values myself, but when i look on server entity User than i dont set explicitly any key generator type on field Id and thus default KeyGenerator<int32> will to be used. And here is where i am confused, i expect that O2O mapping feature will detect that server entity User and its key field Id has this KeyGenerator<int32> and use it to generate next value from this generator and than push back this value into source field Id of object UserDto.

Because imagine that on server side, i create "real" entity objects from User class which will generate values for Id like 1, 2, 3 and so. Than i create new UserDto class and i dont know which value i can use for Id to NOT be in conflict in already existing Id values on server side (db) generated by int32 generator.

Is there something that i miss to setup when building mapping? Or this is not implemented/supported by DO4, and if not is it possible to implement such feature?

See attached sample to reproduce this problem.


Updated at 17.03.2010 7:37:21

another error, when i have Dto class with Nullable types it fails with such error:

A first chance exception of type 'System.InvalidOperationException' occurred in Xtensive.Core.dll

Additional information: The type System.Nullable`1[[System.DateTime, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] hasn't been registered.

sample dto class:

public class UserDto
{
   public DateTime? LastLogin {get;set;}
}

And when i remove ? sign from LastLogin property from dto class UserDto but not from db model class User then it fails:

Additional information: The primitive property System.DateTime From in [b]DtoModel.UserDto[/b] is bound to the property System.Nullable`1[System.DateTime] From in [b]Model.User[/b] that isn't primitive.

But my database support null values in column LastLogin thus my entity class User have DateTime? , and i suppose that dto class must have DateTime? too, but it seems that o2o mapping dost not support nullable types. Is this a bug?

Are nullable types supported by O2O mapping?


Updated at 17.03.2010 14:42:15

You are right, it was a bug. I have just fixed it. In addition, I have implemented the validation routine checking that both of bound properties (in source and target type) have nullable types. If one of properties is nullable and the other is not, it will throw the InvalidOperationException.

Thanks for fix, is this fix already pushed on google code ?

This thread was imported from our support forum. The original discussion may contain more detailed answer.

asked Mar 16 '10 at 11:47

Peter%20%C5%A0ulek's gravatar image

Peter Šulek
492313236


One Answer:

Alex (Xtensive) wrote:

Replace: .MapType<user, userdto,="" string="">(user => user.Key.Format(), userDto => userDto.Key, userDto => new object[] {userDto.Id})

To: .MapType<user, userdto,="" string="">(user => user.Key.Format(), userDto => userDto.Key)

Your original code defines arguments passed to Key.Create(..., params object[] args) explicitly. That's why you're getting the same key value after applying the operations.


Alexander Nikolaev (Xtensive) wrote:

You are right, it was a bug. I have just fixed it. In addition, I have implemented the validation routine checking that both of bound properties (in source and target type) have nullable types. If one of properties is nullable and the other is not, it will throw the InvalidOperationException.

answered Mar 16 '10 at 13:48

Editor's gravatar image

Editor
46145156157

Thanks, this will help me, i do some other tests if there is not any other "hidden" issues for me with O2O mapping. If were some documentation in manual, other than reading unit-tests than it will be more clear to me, but thanks a lot with help!

(Mar 16 '10 at 13:48) Peter Šulek Peter%20%C5%A0ulek's gravatar image

It will be pushed in a few minutes.

(Mar 16 '10 at 13:48) Alexander Nikolaev Alexander%20Nikolaev's gravatar image

v4.2.0 installers were updated to the latest stable revision.

(Mar 16 '10 at 13:48) Alex Yakunin Alex%20Yakunin'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