/// <summary>
        /// Attempt to get a lock for updating a persiston. Fails if another user has it locked already.
        /// Succeeds efficiently if this user already had it locked.
        /// </summary>
        /// <returns>success flag and error reason code</returns>
        public (bool, string) RequestLock(DatonKey datonKey, string version, string sessionKey)
        {
            //check if already locked by this server
            if (Locks.TryGetValue(datonKey, out LockInfo linfo))
            {
                bool isLockedByMe = linfo.SessionKey == sessionKey;
                if (!isLockedByMe)
                {
                    return(false, Constants.ERRCODE_LOCK); //someone else on this server has it locked
                }
                return(true, null);                        //this session already has it locked
            }

            //attempt lock on database
            using (var lockdb = GetLockConnection())
            {
                if (RetroLock.Lock(lockdb, datonKey, version, sessionKey))
                {
                    //success
                    Locks[datonKey] = new LockInfo {
                        SessionKey = sessionKey, DatonKey = datonKey, OldVersion = version
                    };
                    return(true, null);
                }

                //failed, so determine why
                (string verifiedVersion, _) = RetroLock.GetVersion(lockdb, datonKey);
                if (verifiedVersion != version)
                {
                    return(false, Constants.ERRCODE_VERSION); //the most recent version is newer than the version known by the caller
                }
                return(false, Constants.ERRCODE_LOCK);        //someone else on another server has it locked
            }
        }
        /// <summary>
        /// Attempt to release a lock
        /// </summary>
        /// <returns>success flag and new version number (new version only provided if persiston was actually written)</returns>
        public (bool, string) ReleaseLock(DatonKey datonKey, string sessionKey)
        {
            //check if it is possible to unlock
            if (Locks.TryGetValue(datonKey, out LockInfo linfo))
            {
                bool isLockedByMe = linfo.SessionKey == sessionKey;
                if (!isLockedByMe)
                {
                    return(false, null);                 //can't unlock because someone else on this server has it locked
                }
            }
            else
            {
                return(false, null);   //can't unlock because it is not locked by anyone on this server
            }
            //attempt unlock on database
            using (var lockdb = GetLockConnection())
            {
                (bool unlockOK, string newVersion) = RetroLock.Unlock(lockdb, datonKey, sessionKey, linfo.WasWritten, ServerLifeNumber);
                Locks.TryRemove(datonKey, out _); //we forget in memory even if there was a database problem (should not happen)

                //propogate change (this is hooked up to code that pushes the change to subscribed sessions)
                if (linfo.WasWritten)
                {
                    ChangePropogator?.Invoke(datonKey, newVersion);
                }

                return(unlockOK, newVersion);
            }
        }
 /// <summary>
 /// Get the version of the persiston. This ALWAYS goes to the database so don't call this too much.
 /// The result is guaranteed and will assign a version if there was none recorded.
 /// </summary>
 public string GetVersion(DatonKey datonKey)
 {
     using (var lockdb = GetLockConnection())
     {
         (string version, _) = RetroLock.GetVersion(lockdb, datonKey);
         return(version);
     }
 }
 /// <summary>
 /// Get the state of a daton lock. This only checks the local server, so the daton could be locked even if it returns
 /// a value indicating not locked.
 /// </summary>
 /// <returns>3 values: is locked at all; is locked by the given session; version number when locked</returns>
 public (bool, bool, string) GetLockState(DatonKey datonKey, string sessionKey)
 {
     if (Locks.TryGetValue(datonKey, out LockInfo linfo))
     {
         bool isLockedByMe = linfo.SessionKey == sessionKey;
         return(true, isLockedByMe, linfo.OldVersion);
     }
     return(false, false, null);
 }
 /// <summary>
 /// Notify the lock system that a daton was written to the database. This does not unlock it, but it changes the behavior when unlocked.
 /// </summary>
 /// <returns>true if successful</returns>
 public bool NotifyDatonWritten(DatonKey datonKey)
 {
     if (Locks.TryGetValue(datonKey, out LockInfo linfo))
     {
         linfo.WasWritten = true;
         return(true);
     }
     return(false);
 }
Beispiel #6
0
 /// <summary>
 /// Update the touched column for the daton, only if locked by the given session.
 /// </summary>
 public static void Touch(DbConnection db, DatonKey key, string sessionKey)
 {
     using (var cmd = db.CreateCommand())
     {
         cmd.CommandText = "update RetroLock set Touched=@t where DatonKey=@k and LockedBy=@s";
         Utils.AddParameterWithValue(cmd, "t", DateTime.UtcNow);
         Utils.AddParameterWithValue(cmd, "k", key.ToString());
         Utils.AddParameterWithValue(cmd, "s", sessionKey);
         cmd.ExecuteNonQuery();
     }
 }
Beispiel #7
0
 /// <summary>
 /// Get where clause for main table, or null if none
 /// </summary>
 protected SqlSelectBuilder.Where MainTableWhereClause(TableDef tabledef, DatonKey key)
 {
     if (key is PersistonKey pkey)
     {
         return(MainTableWhereClause(tabledef, pkey));
     }
     if (key is ViewonKey vkey)
     {
         return(MainTableWhereClause(tabledef, vkey));
     }
     return(null);
 }
Beispiel #8
0
 public Daton Get(DatonKey key)
 {
     if (Cache.TryGetValue(key, out Item i))
     {
         if (key is PersistonKey) //don't keep viewons in cache very long
         {
             i.LastAccessedUtc = DateTime.UtcNow;
         }
         return(i.Daton);
     }
     return(null);
 }
Beispiel #9
0
        /// <summary>
        /// Attempt to obtain a lock
        /// </summary>
        /// <param name="version">the version which was known</param>
        /// <param name="sessionKey"></param>
        /// <returns>true if successful</returns>
        public static bool Lock(DbConnection db, DatonKey key, string version, string sessionKey)
        {
            //assuming first there is a record, attempt to get the lock by updating it
            using (var cmd = db.CreateCommand())
            {
                cmd.CommandText = "update RetroLock set Touched=@t, LockedBy=@s where DatonKey=@k and DatonVersion=@v and (LockedBy is null or Touched<@old)";
                Utils.AddParameterWithValue(cmd, "t", DateTime.UtcNow);
                Utils.AddParameterWithValue(cmd, "s", sessionKey);
                Utils.AddParameterWithValue(cmd, "k", key.ToString());
                Utils.AddParameterWithValue(cmd, "v", version);
                Utils.AddParameterWithValue(cmd, "old", DateTime.UtcNow.AddSeconds(-120));
                int nrows = cmd.ExecuteNonQuery();
                if (nrows == 1)
                {
                    return(true);
                }
            }

            //update touched date: this tells us if the record exists, and ensures it won't be cleaned up during the lock process;
            //also unlock it if the lock is too old
            //bool recordExists;
            //using (var cmd = db.CreateCommand())
            //{
            //    cmd.CommandText = "update RetroLock set LockedBy=(case when Touched<@old then null else LockedBy end), Touched=@t where DatonKey=@k";
            //    Utils.AddParameterWithValue(cmd, "old", DateTime.UtcNow.AddSeconds(-120));
            //    Utils.AddParameterWithValue(cmd, "t", DateTime.UtcNow);
            //    Utils.AddParameterWithValue(cmd, "k", key.ToString());
            //    int nrows = cmd.ExecuteNonQuery();
            //    recordExists = nrows == 1;
            //}

            //reached here, so there was no record; create one with lock
            //(this won't occur if everything is working, since reading the version should happen before attempting to lock)
            using (var cmd = db.CreateCommand())
            {
                cmd.CommandText = "insert into RetroLock (DatonKey,DatonVersion,Touched,LockedBy) values(@k,@v,@t,@s)";
                Utils.AddParameterWithValue(cmd, "k", key.ToString());
                Utils.AddParameterWithValue(cmd, "v", version);
                Utils.AddParameterWithValue(cmd, "t", DateTime.UtcNow);
                Utils.AddParameterWithValue(cmd, "s", sessionKey);
                try
                {
                    cmd.ExecuteNonQuery();
                    return(true);
                }
                catch
                {
                    return(false); //another user created the lock record since we queried it above
                }
            }
        }
Beispiel #10
0
 /// <summary>
 /// Determine if any subscription from any client is out of date; that is, if there is a client
 /// whose latest version is not the new version provided for the daton key provided.
 /// </summary>
 public bool IsAnyOutOfDate(DatonKey key, string newVersion)
 {
     foreach (var cli in Sessions.Values)
     {
         if (!cli.Subscriptions.TryGetValue(key, out string clientsVersion))
         {
             continue;
         }
         if (clientsVersion != newVersion)
         {
             return(true);
         }
     }
     return(false);
 }
Beispiel #11
0
        /// <summary>
        /// Given parsed untyped JSON in full compatible format, construct it into a typed daton
        /// </summary>
        public static Daton FromCompatibleWireFull(DataDictionary dbdef, JObject jroot)
        {
            var datonKey = DatonKey.Parse(jroot.Value <string>("key"));
            var datondef = dbdef.FindDef(datonKey);
            var daton    = Utils.ConstructDaton(datondef.Type, datondef);

            daton.Key     = datonKey;
            daton.Version = jroot.Value <string>("version");
            if (daton is Viewon viewon && jroot.Value <bool>("isComplete") == false)
            {
                viewon.IsCompleteLoad = false;
            }

            var mainRowsNode = jroot[CamelCasify(datondef.MainTableDef.Name)];

            if (mainRowsNode == null)
            {
                return(daton);
            }
            if (!(mainRowsNode is JArray mainRowsArray))
            {
                throw new Exception($"{datondef.MainTableDef.Name} node must be an array");
            }
            if (datondef.MultipleMainRows)
            {
                var targetListInfo = datondef.Type.GetField(datondef.MainTableDef.Name);
                if (targetListInfo == null)
                {
                    throw new Exception($"Expected {datondef.MainTableDef.Name} to be a member of {datondef.Type.Name}");
                }
                var targetList = Utils.CreateOrGetFieldValue <IList>(daton, targetListInfo) as IList;
                ReadCompatibleJsonRowArray(mainRowsArray, datondef.MainTableDef, (IList)targetList);
            }
            else
            {
                if (mainRowsArray.Count != 1)
                {
                    throw new Exception($"{datondef.MainTableDef.Name} node must have one element for this daton type");
                }
                ReadCompatibleJsonRow(mainRowsArray[0] as JObject, datondef.MainTableDef, daton);
            }

            daton.Recompute(datondef);
            return(daton);
        }
Beispiel #12
0
        /// <summary>
        /// Get the daton version code and session key holding the lock if any; if the lock row was missing, create it and assign
        /// the version
        /// </summary>
        /// <returns>(version,lockedBy) where lockedBy might be null</returns>
        public static (string, string) GetVersion(DbConnection db, DatonKey key)
        {
retry:

            //read existing row
            using (var cmd = db.CreateCommand())
            {
                cmd.CommandText = "select DatonVersion,LockedBy from RetroLock where DatonKey=@k";
                var p = cmd.CreateParameter();
                p.ParameterName = "k";
                p.Value         = key.ToString();
                cmd.Parameters.Add(p);
                using (var rdr = cmd.ExecuteReader())
                {
                    if (rdr.Read())
                    {
                        return(Utils.Read <string>(rdr, 0), Utils.Read <string>(rdr, 1));
                    }
                }
            }

            //not found, so create it
            string version = Guid.NewGuid().ToString();

            using (var cmd = db.CreateCommand())
            {
                try
                {
                    cmd.CommandText = "insert into RetroLock (DatonKey,DatonVersion,Touched) values(@k,@v,@t)";
                    Utils.AddParameterWithValue(cmd, "k", key.ToString());
                    Utils.AddParameterWithValue(cmd, "v", version);
                    Utils.AddParameterWithValue(cmd, "t", DateTime.UtcNow);
                    cmd.ExecuteNonQuery();
                    return(version, null);
                }
                catch
                {
                    //rare failure: another user created the row between when we queried and attempted the insert
                    Thread.Sleep(1);
                    goto retry;
                }
            }
        }
Beispiel #13
0
 /// <summary>
 /// Change the subscription state of a daton for a client
 /// </summary>
 public void ManageSubscribe(string sessionKey, DatonKey datonKey, string version, bool subscribe)
 {
     if (datonKey.IsNew)
     {
         throw new Exception("Cannot subscribe to an unsaved persiston");
     }
     if (!Sessions.TryGetValue(sessionKey, out var ses))
     {
         return;
     }
     if (subscribe)
     {
         ses.Subscriptions[datonKey] = version;
     }
     else
     {
         ses.Subscriptions.TryRemove(datonKey, out _);
     }
 }
Beispiel #14
0
        /// <summary>
        /// Get the list of daton keys with version numbers that were updated by other servers since the given time
        /// </summary>
        /// <param name="serverLifeNumber">the number for this server; updates from this server are excluded from the query</param>
        public static List <(DatonKey, string)> GetRecentUpdatesByOtherServers(DbConnection db, DateTime sinceUtc, int serverLifeNumber)
        {
            var ret = new List <(DatonKey, string)>();

            using (var cmd = db.CreateCommand())
            {
                cmd.CommandText = $"select DatonKey,DatonVersion from RetroLock where Touched>@t and UpdatedByServer<>@u";
                Utils.AddParameterWithValue(cmd, "t", sinceUtc);
                Utils.AddParameterWithValue(cmd, "u", serverLifeNumber);
                using (var rdr = cmd.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        var    key     = DatonKey.Parse(Utils.Read <string>(rdr, 0));
                        string version = Utils.Read <string>(rdr, 1);
                        ret.Add((key, version));
                    }
                }
            }
            return(ret);
        }
Beispiel #15
0
        /// <summary>
        /// Unlock a daton and optionally assign new version; only has an effect if it was locked by the given session
        /// </summary>
        /// <param name="datonWasWritten">pass true if this unlock is following a write, or false if it is an abandoned lock</param>
        /// <returns>success flag and the new version (version only returned if datonWasWritten)</returns>
        public static (bool, string) Unlock(DbConnection db, DatonKey key, string sessionKey, bool datonWasWritten, int serverLifeNumber)
        {
            string version = datonWasWritten ? Guid.NewGuid().ToString() : null;

            using (var cmd = db.CreateCommand())
            {
                string versionsql = datonWasWritten ? ",DatonVersion=@v,UpdatedByServer=@u" : "";
                cmd.CommandText = $"update RetroLock set LockedBy=null,Touched=@t{versionsql} where DatonKey=@k and LockedBy=@s";
                Utils.AddParameterWithValue(cmd, "t", DateTime.UtcNow);
                Utils.AddParameterWithValue(cmd, "k", key.ToString());
                Utils.AddParameterWithValue(cmd, "s", sessionKey);
                if (datonWasWritten)
                {
                    Utils.AddParameterWithValue(cmd, "v", version);
                    Utils.AddParameterWithValue(cmd, "u", serverLifeNumber);
                }
                int  nrows   = cmd.ExecuteNonQuery();
                bool success = nrows == 1;
                return(success, version);
            }
        }
Beispiel #16
0
        /// <summary>
        /// Given parsed untyped JSON in diff format, construct a PersistonDiff
        /// </summary>
        public static PersistonDiff FromDiff(DataDictionary dbdef, JObject jroot)
        {
            var datonKey = DatonKey.Parse(jroot.Value <string>("key"));
            var datondef = dbdef.FindDef(datonKey);
            var diff     = new PersistonDiff(datondef, datonKey, jroot.Value <string>("version"));

            ReadJsonDiffRowArray(jroot, datondef.MainTableDef, diff.MainTable);

            //existing single-main-row diffs might not include the primary key in the main row, so add it here
            if (!datondef.MultipleMainRows)
            {
                var mainDiffRow = diff.MainTable.First();
                if (!mainDiffRow.Columns.ContainsKey(datondef.MainTableDef.PrimaryKeyColName))
                {
                    var pkColdef = datondef.MainTableDef.FindCol(datondef.MainTableDef.PrimaryKeyColName);
                    var pk       = Utils.ChangeType(((PersistonKey)datonKey).PrimaryKey, pkColdef.CSType);
                    mainDiffRow.Columns[datondef.MainTableDef.PrimaryKeyColName] = pk;
                }
            }

            return(diff);
        }
Beispiel #17
0
        /// <summary>
        /// Get a daton, from cache or load from database. The reutrn value is a shared instance so the caller may not modify it.
        /// For new unsaved persistons with -1 as the key, this will create the instance with default values.
        /// </summary>
        /// <param name="user">if null, the return value is a shared guaranteed complete daton; if user is provided,
        /// the return value may be a clone with some rows removed or columns set to null</param>
        /// <param name="forceCheckLatest">if true then checks database to ensure latest version even if it was cached</param>
        /// <returns>object with daton, or readable errors</returns>
        public async Task <RetroSql.LoadResult> GetDaton(DatonKey key, IUser user, bool forceCheckLatest = false)
        {
            //new persiston: return now
            if (key.IsNew)
            {
                var   datondef2 = DataDictionary.FindDef(key);
                Daton newDaton  = Utils.Construct(datondef2.Type) as Daton;
                Utils.FixTopLevelDefaultsInNewPersiston(datondef2, newDaton);
                newDaton.Key = key;
                if (datondef2.Initializer != null)
                {
                    await datondef2.Initializer(newDaton);
                }
                return(new RetroSql.LoadResult {
                    Daton = newDaton
                });
            }

            //get from cache if possible, and optionally ignore cached version if it is not the latest
            string verifiedVersion = null;
            Daton  daton           = DatonCache.Get(key);

            if (forceCheckLatest && daton != null)
            {
                //viewons: always ignore cache; persistons: use cached only if known to be latest
                if (daton is Persiston)
                {
                    verifiedVersion = LockManager.GetVersion(key);
                    if (verifiedVersion != daton.Version)
                    {
                        daton = null;
                    }
                }
                else
                {
                    daton = null;
                }
            }

            //get from database if needed (and cache it), or abort
            var datondef = DataDictionary.FindDef(key);

            if (typeof(Persiston).IsAssignableFrom(datondef.Type) && (key is ViewonKey))
            {
                throw new Exception("Persiston requested but key format is for viewon");
            }
            if (typeof(Viewon).IsAssignableFrom(datondef.Type) && (key is PersistonKey))
            {
                throw new Exception("Viewon requested but key format is for persiston");
            }
            if (daton == null)
            {
                var sql = GetSqlInstance(key);
                RetroSql.LoadResult loadResult;
                using (var db = GetDbConnection(datondef.DatabaseNumber))
                    loadResult = await sql.Load(db, DataDictionary, user, key, ViewonPageSize);
                if (loadResult.Daton == null)
                {
                    return(loadResult);
                }
                daton = loadResult.Daton;
                if (verifiedVersion == null && daton is Persiston)
                {
                    verifiedVersion = LockManager.GetVersion(key);
                }
                daton.Version = verifiedVersion;
                DatonCache.Put(daton);
                Diagnostics.IncrementLoadCount();
            }

            //enforce permissions on the user
            if (user != null)
            {
                daton = daton.Clone(datondef);
                var guard = new SecurityGuard(DataDictionary, user);
                guard.HidePrivateParts(daton);
            }

            return(new RetroSql.LoadResult {
                Daton = daton
            });
        }
Beispiel #18
0
        private async Task HandleHttpMain(MainRequest req, IUser user, MainResponse resp)
        {
            //initialize
            if (req.Initialze != null)
            {
                resp.DataDictionary = Retrovert.DataDictionaryToWire(DataDictionary, user, LanguageMessages);
            }

            //load datons
            if (req.GetDatons != null)
            {
                var getResponses = new List <GetDatonResponse>();
                foreach (var drequest in req.GetDatons)
                {
                    var loadResult = await GetDaton(DatonKey.Parse(drequest.Key), user, forceCheckLatest : drequest.ForceLoad);

                    var getResponse = new GetDatonResponse
                    {
                        Errors = loadResult.Errors
                    };
                    if (loadResult.Daton != null)                                                                                      //null means it was not found by key, usually
                    {
                        bool doReturnToCaller = loadResult.Daton.Version == null || drequest.KnownVersion != loadResult.Daton.Version; //omit if client already has the current version
                        if (doReturnToCaller)
                        {
                            getResponse.CondensedDaton = new CondensedDatonResponse
                            {
                                CondensedDatonJson = Retrovert.ToWire(DataDictionary, loadResult.Daton, false)
                            };
                        }
                        if (drequest.DoSubscribe && loadResult.Daton is Persiston)
                        {
                            ClientPlex.ManageSubscribe(req.SessionKey, loadResult.Daton.Key, loadResult.Daton.Version, true);
                        }
                    }
                    else
                    {
                        getResponse.Key = drequest.Key; //only needed if daton is not returned to client
                    }
                    getResponses.Add(getResponse);
                }
                resp.GetDatons = getResponses.ToArray();
            }

            //save datons
            if (req.SaveDatons != null)
            {
                var diffs = new List <PersistonDiff>();
                foreach (var saveRequest in req.SaveDatons)
                {
                    var diff = Retrovert.FromDiff(DataDictionary, saveRequest);
                    diffs.Add(diff);
                }
                (bool success, var results) = await SaveDatons(req.SessionKey, user, diffs.ToArray());

                var saveResponses = new List <SavePersistonResponse>();
                foreach (var result in results)
                {
                    saveResponses.Add(new SavePersistonResponse
                    {
                        IsDeleted = result.IsDeleted,
                        IsSuccess = result.IsSuccess,
                        OldKey    = result.OldKey.ToString(),
                        NewKey    = result.NewKey?.ToString(),
                        Errors    = result.Errors
                    });
                }
                resp.SavedPersistons       = saveResponses.ToArray();
                resp.SavePersistonsSuccess = success;
            }

            //change datons state
            if (req.ManageDatons != null)
            {
                var manageResponses = new List <ManageDatonResponse>(req.ManageDatons.Length);
                foreach (var mrequest in req.ManageDatons)
                {
                    //what does the caller wants to change?
                    var  datonKey       = DatonKey.Parse(mrequest.Key);
                    bool wantsLock      = mrequest.SubscribeState == 2;
                    bool wantsSubscribe = mrequest.SubscribeState >= 1;

                    //handle change in subscription
                    //(Performance note: unsubscribe should happen before unlock so that the unlock-propogation can short circuit reloading. Ultimately
                    //if only one client is dealing with a daton and that client releases the lock and subscription, this server can forget about it
                    //immediately.)
                    bool isSubscribed = false;
                    if (datonKey is PersistonKey)
                    {
                        ClientPlex.ManageSubscribe(req.SessionKey, datonKey, mrequest.Version, wantsSubscribe);
                        isSubscribed = wantsSubscribe;
                    }

                    //handle change in lock
                    string lockErrorCode = "";
                    bool   hasLock       = false;
                    if (wantsLock)
                    {
                        if (string.IsNullOrEmpty(mrequest.Version))
                        {
                            throw new Exception("Version required to lock daton");
                        }
                        (hasLock, lockErrorCode) = LockManager.RequestLock(datonKey, mrequest.Version, req.SessionKey);
                    }
                    else
                    {
                        LockManager.ReleaseLock(datonKey, req.SessionKey);
                    }

                    manageResponses.Add(new ManageDatonResponse
                    {
                        ErrorCode      = lockErrorCode,
                        Key            = mrequest.Key,
                        SubscribeState = hasLock ? 2 : (isSubscribed ? 1 : 0)
                    });
                }
                resp.ManageDatons = manageResponses.ToArray();
            }

            //quit - free up locks and memory
            if (req.DoQuit)
            {
                ClientPlex.DeleteSession(req.SessionKey);
                LockManager.ReleaseLocksForSession(req.SessionKey);
            }
        }
Beispiel #19
0
        /// <summary>
        /// Load daton from database. Caller is responsible for setting the version (this does not deal with locks or versions)
        /// </summary>
        /// <param name="pageSize">only inspected for viewons main table</param>
        /// <returns>null if not found</returns>
        public virtual async Task <LoadResult> Load(IDbConnection db, DataDictionary dbdef, IUser user, DatonKey key, int pageSize)
        {
            var datondef = dbdef.FindDef(key);

            //viewon validation
            if (key is ViewonKey vkey2)
            {
                var validator = new Validator(user);
                await validator.ValidateCriteria(datondef, vkey2);

                if (validator.Errors.Any())
                {
                    return new LoadResult {
                               Errors = validator.Errors.ToArray()
                    }
                }
                ;
            }

            //handle viewon paging and ordering
            string sortColName = datondef.MainTableDef.DefaulSortColName;
            int    pageNo      = 0;

            if (key is ViewonKey vkey)
            {
                pageNo = vkey.PageNumber;
                if (vkey.SortColumnName != null)
                {
                    sortColName = vkey.SortColumnName;
                }
            }
            else
            {
                pageSize = 0;  //prohibit paging in persistons
            }
            //load main table
            var whereClause = MainTableWhereClause(datondef.MainTableDef, key);
            var loadResult  = await LoadTable(db, dbdef, datondef.MainTableDef, whereClause, sortColName, pageSize, pageNo);

            loadResult.RowsByParentKey.TryGetValue("", out var rowsForParent);

            //single-main-row datons cannot have zero main rows
            if (!datondef.MultipleMainRows && (rowsForParent == null || rowsForParent.Count == 0))
            {
                return(null);                                                                                   //was throw new Exception("Single-main-row not found using: " + whereClause.ToString());
            }
            Daton daton = Utils.Construct(datondef.Type) as Daton;

            if (datondef.MultipleMainRows)
            {
                if (daton is Viewon viewon)
                {
                    viewon.IsCompleteLoad = loadResult.IsComplete;
                }

                //copy rowsDict into daton's main table IList
                var listField = datondef.Type.GetField(datondef.MainTableDef.Name);
                if (rowsForParent != null)
                {
                    var list = Utils.CreateOrGetFieldValue <IList>(daton, listField);
                    foreach (var row in rowsForParent)
                    {
                        list.Add(row);
                    }
                }
            }
            else //single main row
            {
                if (rowsForParent != null)
                {
                    daton = rowsForParent?[0] as Daton;
                }
            }

            //child tables
            var rowsByPK = RestructureByPrimaryKey(datondef.MainTableDef, loadResult.RowsByParentKey);

            await LoadChildTablesRecursive(rowsByPK, db, dbdef, datondef.MainTableDef);

            daton.Key = key;
            daton.Recompute(datondef);
            return(new LoadResult {
                Daton = daton
            });
        }
Beispiel #20
0
 public PersistonDiff(DatonDef datondef, DatonKey key, string version)
 {
     DatonDef       = datondef;
     Key            = key;
     BasedOnVersion = version;
 }