Coder Social home page Coder Social logo

gravity's Introduction

Gravity

Open Source Community: Gravity is an ORM framework for Relativity custom development. Using Gravity will greatly decrease the amount of time it takes to pick up Relativity development and allow you to write code that interacts with Relativity with commonly used C# syntax.

While this project is hosted on the RelativityDev account, support is only available through the Relativity developer community. You are welcome to use the code and solution as you see fit within the confines of the license it is released under. However, if you are looking for support or modifications to the solution, we suggest reaching out to a Relativity Development Partner.

Gravity was originally created by TSD Services. Through their generosity and leadership, they have released the project as open source. It is an active project and has contributions from other Relativity Development Partners. Anyone who has a need is invited to use and contribute to the project. If you are interested in contributing, check out the open issues and Wiki pages.

We would like to recognize the following Relativity Development Partners who have made significant contributions to the Gravity project:

TSD Services            

This is also available as a nuget package.

Target Frameworks

  • .NET 4.5.1, .NET 4.6.2

Dependencies

This project requires references to Relativity's Relativity® SDK dlls, which are referenced via Nuget packages. As such, DLL versions 9.4.224.2 and up are supported.

Sample / Test Suite

Information about the demo application and accompanying integration tests is available on a separate page.

Usage Guide

Before using the CRUD/Q methods in Gravity you will have to create a model and decorate it with the appropriate attributes:

  • RelativityObject - Specifies the type Guid of the RDO you are targeting.
  • RelativityObjectField - Specifies the type Guid and the "RdoFieldType" of the RDO field you are targeting.
  • RelativityObjectChildrenList - Used to decorate a List of child RDOs as a object List.
  • RelativityObjectFieldParentArtifactId - If the RDO is a child object, used to specify that the RDO field contains the ParentArtifactID

The following example demonstrates a RDO represented as a Model:

[Serializable]
[RelativityObject("0B5C62E0-2AFA-4408-B7FF-789351C9BEDC")]
public class DemoPurchaseOrder : BaseDto
{
	[RelativityObjectField("E1FA93B9-C2DB-442A-9978-84EEB6B61A3F", RdoFieldType.FixedLengthText, 255)]
	public override string Name { get; set; }

	[RelativityObjectField("37159592-B5B6-4405-AF74-10B5728890B4", RdoFieldType.WholeNumber)]
	public int OrderNumber { get; set; }

	[RelativityObjectField("37159592-B5B6-4405-AF74-10B5728890B4", RdoFieldType.FixedLengthText, 100)]
	public string CustomerName { get; set; }

	[RelativityObjectField("3BDC0971-A87C-414E-9A37-FC477279BBAD", RdoFieldType.FixedLengthText, 100)]
	public string CustomerEmail { get; set; }

	[RelativityObjectField("D0770889-8A4D-436A-9647-33419B96E37E"), RdoFieldType.MultipleObject)]
	public IList<Items> Items { get; set; }

	[RelativityObjectField("D0770889-8A4D-436A-9647-33419B96E37E"), RdoFieldType.SingleObject)]
	public Address Address { get; set; }

	[RelativityObjectField("4501A308-5E68-4314-AEDC-4DEB527F12A8", RdoFieldType.Decimal)]
	public decimal Total { get; set; }

	[RelativityObjectField("CEDB347B-679D-44ED-93D3-0B3027C7E6F5", RdoFieldType.SingleChoice)]
	public OrderType OrderType { get; set; }

	[RelativityObjectChildrenList]
	public IList<RelatedPurchase> RelatedPurchases { get; set; }
}
  • Note:
    • For property of type User use kCura.Relativity.Client.DTOs.User [limited support]
    • For property of type FileField use Gravity.Base.FileDto
      • If the file is only going to be read from the server, or you can keep the files entirely in memory, use the Gravity.Base.ByteArrayFileDto subclass. This is the class returned by any Get requests.
      • If you want to use files stored on disk AND don't need to read the files from the server, you can use the Gravity.Base.DiskFileDto subclass.
      • Otherwise, use the base class and cast as necessary.

For Choice field you must create a enum and decorate it with the appropriate attributes:

  • RelativityObject - Specifies the choice Guid.

The following example demonstrates a choice field represented as an Enum:

public enum OrderType
{
	[RelativityObject("4F04381D-F3E3-4DEE-8EF9-11F27047D9B4")]
	TypeOne = 1,

	[RelativityObject("8453BF3E-D95B-4BC5-BD68-3CF4277DD731")]
	TypeTwo = 2
}

To use Gravity for RSAPI operations, you must instantiate an RsapiDao object using the RsapiDao constructor, with IHelper and workspaceId as parameters.

Supported IGravityDao methods:

  • Get<T>(int artifactId, ObjectFieldsDepthLevel depthLevel) - Get DTO by Artifact ID.
  • Get<T>(IList<int> artifactIDs, ObjectFieldsDepthLevel depthLevel) - Get DTOs by Artifact IDs.
  • Delete<T>(int objectToDeleteId)- Delete object recursively (includes child objects) by Artifact ID.
  • Insert<T>(T theObjectToInsert, ObjectFieldsDepthLevel depthLevel) - Insert Relativity object from RDO, updating the RDO with its new ArtifactID.
  • Insert<T>(IList<T> theObjectsToInsert, ObjectFieldsDepthLevel depthLevel) - Insert Relativity objects from RDOs, updating the RDOs with their new ArtifactIDs.
  • Update<T>(BaseDto theObjectToUpdate, ObjectFieldsDepthLevel depthLevel) - Update Relativity object from RDO, inserting any non-existing children.
  • Update<T>(IList<T> theObjectsToUpdate, ObjectFieldsDepthLevel depthLevel) - Update Relativity objects from RDOs, inserting any non-existing children.
  • UpdateField<T>(int rdoID, Guid fieldGuid, object value) - Update field value by GUID and RDO Artifact ID [limited support]

Additional methods supported by RsapiDao:

  • List<T> Query<T>(Condition queryCondition = null, ObjectFieldsDepthLevel depthLevel = ObjectFieldsDepthLevel.FirstLevelOnly) - Get all DTOs of type matching an optional RSAPI query condition

Where available, the depthLevel parameter controls whether (and how deeply) to recurse into object fields and child object lists. The recursion options are OnlyParentObject, FirstLevelOnly, and FullyRecursive.

Example

The following example demonstrates a object "Get" used in Event handler. First we instantiate RsapiDao and then we use the Gravity RSAPI Dao Get method to get the object (ObjectFieldsDepthLevel.OnlyParentObject means that we want just the object - no child object fields, multiple object fields or single object fields are populated recursively):

public override Response Execute()
{
	Response returnResponse = new Response() { Message = string.Empty, Success = true };

	RsapiDao gravityRsapiDao = new RsapiDao(this.Helper, this.Helper.GetActiveCaseID());

		DemoPurchaseOrder demoOrder =  gravityRsapiDao.Get<DemoPurchaseOrder>(1047088,
	  ObjectFieldsDepthLevel.OnlyParentObject);

		return returnResponse;
}

gravity's People

Contributors

aarongilbert123 avatar arithmomaniac avatar divanova-tsd avatar ggachevski avatar hhristov-tsd avatar jacobmalliet avatar mrobustelli avatar nikolaykepov avatar ppande1020 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gravity's Issues

Add paging to queries

Currently, RsapiDao.GetRDOs(Condition) returns a List<T> and pulls all items down at once.

In order to enable handling of large sets, it would be better to use an IEnumerable<T> for queries, and pull results by paging. The general idea can be seen in the following code snippet we have used before at Milyli:

var initialResultSet = repo.Query(query, pageLength);

foreach (var result in initialResultSet.Results)
    yield return result;

string queryToken = initialResultSet.QueryToken;

// Iterate though all remaining pages
var totalCount = initialResultSet.TotalCount;
int currentPosition = pageLength + 1;

while (currentPosition <= totalCount)
{
    var subsetResult = repo.QuerySubset(queryToken, currentPosition, pageLength);
    foreach (var result in subsetResult.Results)
        yield return result;

    currentPosition += pageLength;
}

The paging could be set as a global configuration and/or on the specific call, and fallback to "no paging" if not set.

Remove (int) Cast in Attributes

This is minor, but it's a pain to cast enums in the attributes. Could we simply change the attribute definition to accept the enum instead of int?

Attribute Definition Before:
public RelativityObjectFieldAttribute(string fieldGuid, int fieldType)

Attribute Definition After:
public RelativityObjectFieldAttribute(string fieldGuid, Gravity.Base.RdoFieldType fieldType)

Attribute Implementation Before:
[RelativityObjectField(guidString, (int)RdoFieldType.FixedLengthText, length)]

Attribute Implementation After:
[RelativityObjectField(guidString, RdoFieldType.FixedLengthText, length)]

Switch Relativity dependencies to use Nuget packages

This is a prerequisite for Continuous Integration, in addition to just making development easier in general. The packages are still a bit of a WIP, but the ones currently needed are:

  • Relativity.Rsapi (kCura.Relativity.Client.dll)
  • Relativity.API (Relativity.API.dll)
  • Relativity.RowDataGateway (kCura.Data.RowDataGateway.dll)

SQL Dao/Provider

We have been needing this on our side and we are currently in testing phase. You will be able to get your RDO via auto-generated SQL calls which provide an increase in efficiency and eliminate the Audit log burden (that may be generated by a frequent agent that does something in the background).

Initial tests show a ten-fold increase in efficiency and speed. :) We will be able to merge very soon.

ImportAPI Dao/Provider

ImportAPI would be another nice target for BaseDTO mapping.
On a simple level, the code could convert a set of DTOs into a DataTable ready to import via the ImportAPI.
On a more complicated level, it could also wrap the whole ImportAPI API to make the methods task-based instead of event-based, support paging, etc.

Create RelativityChoice attribute

Instead of code like

[RelativityObjectField("C3B2943D-C9C2-4C92-A88D-115B3F9ED64D", (int)RdoFieldType.MultipleChoice, typeof(MultipleChoiceFieldChoices))]
public IList<MultipleChoiceFieldChoices> MultipleChoiceFieldChoices { get; set; }
[RelativityObjectField("CEDB347B-679D-44ED-93D3-0B3027C7E6F5", (int)RdoFieldType.SingleChoice, typeof(SingleChoiceFieldChoices))]
public SingleChoiceFieldChoices SingleChoice { get; set; }

We should have a RelativityChoiceField attribute that looks like this:

[RelativityChoiceField("C3B2943D-C9C2-4C92-A88D-115B3F9ED64D")]
public IList<MultipleChoiceFieldChoices> MultipleChoiceFieldChoices { get; set; }
[RelativityChoiceField("CEDB347B-679D-44ED-93D3-0B3027C7E6F5")]
public SingleChoiceFieldChoices SingleChoice { get; set; }

Whether it it single or multiple choice can be determined by checking whether the property type is an IEnumerable<>, and the choice type can be read from the property like we are doing in #41.

At a minimum, we could make it possible to do without the type parameter in RelativityObjectField.

LINQ Provider

In keeping with the nature of POCO abstractions, it would be nice (and, if we want to abstract the interfaces from underlying API implementations, necessary) to wrap queries as a partially-implemented IQueryable.

  • OrderBy should be able to just grab the name/guid of the selected property and pass it into the Query Sort property.
  • Take/Skip can use the start and pageSize parameters on the query object.
  • Count could just pull 1 item, but read the TotalCount property on the QueryResult object.
  • Select would be unsupported - you need to return the whole object.
  • The Where part would be the hardest, but even that wouldn't be so bad. There are two packages (LinqToAnything and ReLinq) that simplify the tokenization of the Where expression tree, and most of these map nicely to the tokens used by the Condition class. Even just supporting simple equality like (x => x.CustomerEmail == "[email protected]") would solve a lot of use cases.

Interface-based scoping of DTO fields

Right now, the only method for only returning or updating specific fields is on the class level. This requires either many partially duplicate classes, or manually constructing inheritance chains. Neither of these options support multiple inheritance, and they don't play nice with code generation (see #11 ).

I think at least having the option of using interfaces would be simpler:

  • A BaseDto subclass can implement one or more interfaces
  • In addition to the current single-parameter CRUD methods that return a whole object, the Dao CRUD members can take two generic parameters; the second is the interface to input/return, and the first remains the underlying class for the operation.
  • Only gettable members of the interface are used in query conditions (once we implement LINQ, see #12 ) and in reads / query results. Only settable ones are used for creates/updates.

If you generate partial classes, then consumers can scope the object to all of their needs by slapping on as many interfaces as they want. The interfaces would not need the attributes, so these would be easy to write too.

To take an example: suppose I want just want to read just the Name and OrderNumber properties on DemoPurchaseOrder, and update the FooBar property on the Items (leaving the attributes off of the classes for simplicity; the interfaces would not need them):

public class DemoPurchaseOrder : BaseDto, IDemoPurchaseOrder_UpdateFooBar
{
    public override string Name { get; set; }
    public int OrderNumber { get; set; }
    public string CustomerName { get; set; }
    public string CustomerEmail { get; set; }
    public IList<Item> Items { get; set; }
}

public class Item : BaseDto, IItem_UpdateFooBar
{
    public string Baz { get; set; }
    public string FooBar { get; set; }
}
 
public interface IDemoPurchaseOrder_UpdateFooBar
{
    string Name { get; }
    int OrderNumber { get; }
    IList<IItem_UpdateFooBar> Items { get; set; }
}

public class Item : BaseDto, IItem_UpdateFooBar
{
    string FooBar { set; }
}

//pseudocode

var demoOrder =  
    gravityRsapiDao.GetRelativityObject<DemoPurchaseOrder, IDemoPurchaseOrder_UpdateFooBar>
        (1047088, ObjectFieldsDepthLevel.FirstLevelOnly);
foreach (var item in demoOrder.Items)
{
    return item.FooBar = $"{demoOrder.Name}_{demoOrder.ItemNumber}";
    //item.Baz is not available to set
    //demoOrder.CustomerEmail is not available to get
}
gravityRsapiDao.Update(IDemoPurchaseOrder_UpdateFooBar>
    (demoOrder, ObjectFieldsDepthLevel.FirstLevelOnly);

Test no longer run. Due to error in GetEnumerableInnerType in TypeExtesions.cs

Looks like commit 5246bf9 in the Dev Branch broke the Read Tests.
Get "Sequence contains no matching element" @ line 44. when trying to execute GetChildObjectRecursively @ line 111 for the objectPropertyInfo {System.Collections.Generic.IList`1[Gravity.Test.TestClasses.GravityLevel2] GravityLevel2MultipleObjs} System.Reflection.PropertyInfo {System.Reflection.RuntimePropertyInfo}.

@Arithmomaniac were you able to execute the test when you checked this in?

Changes to Gravity.Test project

  1. The Readme file says to install an application for setting up the Gravity.test project which is fine but to make this agnostic let's use 'ImportApplication' keyword located here - https://github.com/relativitydev/relativity-test-helpers/blob/master/Source/Relativity.Test.Helpers/Relativity.Test.Helpers/Application/ApplicationHelpers.cs
    This will import the application for you everytime the test runs.

  2. I also had to hard code my workspace id in the app.config file. We recommend you create a workspace every time these tests are run so the test doesn't depend on a user doing manual work to set up the data. The create workspace helper is located here - https://github.com/relativitydev/relativity-test-helpers/blob/master/Source/Relativity.Test.Helpers/Relativity.Test.Helpers/WorkspaceHelpers/CreateWorkspace.cs

3 If you are setting up your workspace everytime you run a test, you want to delete your workspace so you clean up everytime a test successfully passes. Some delete workspace helpers we recommend are located here - https://github.com/relativitydev/relativity-test-helpers/blob/master/Source/Relativity.Test.Helpers/Relativity.Test.Helpers/WorkspaceHelpers/DeleteWorkspace.cs

  1. It also looks like we also are hard-coding an artifact id of the RDO we are trying to query. Once the RDO's are uploaded into an instance of Relativity through ADS, we can make a call to retrieve the artifact ID of the RDO.

  2. Although the visual studio testing framework works out well, since we use Nunit here at Relativity, we highly recommend Nunit.

Long-term architecture for supporting different providers

As mentioned in #70 , it may be time to start thinking about how we want to support multiple providers in the long run. I was thinking something as follows:

  1. There would be a base Gravity.Base project/package that would contain BaseDto, relevant helper methods, and an IGravity(Async)Repository interface containing a basic CRUD contract for all implementations. This would correspond to, e.g. RsapiDao now.
  2. Each implementation would get its own Nuget project/package, to prevent needing tons of dependencies (e.g. Gravity.RSAPI).
    • Each package would have the IGravity(Async)Repository implementation, which in turn would in turn take a thin provider to abstract away calls to and from the server (e.g. RsapiDao would be renamed RsapiRepository would have an RsapiProvider similar to the one in #70).
    • The repository itself would handle issues like field mapping, recursive calls, etc.
  3. To implement IGravityRepository.Query, each provider would need to include a LINQ Provider, as per #12. We could have missing implementations throw NotSupportedException, create a separate IQueryableGravityRepository implementation, or just leave Query out of the base interface.

If this sounds right, we can decide how we'd like to create a project around it.

Add Model Generation tool

Currently, models need to be written by hand, but there is no inherent reason the relevant information could not be pulled from an application schema file.

I would suggest using Heretik's parser (and contributing to it where needed so that it can be used by Gravity), but the XML could also be used directly.

One question that would need to be asked is how the generation would be done. Do we want to use MRT4 to generate a class file, or generate a whole DLL via a command-line application that a user could reference?

Remove redundant methods

I posited that in #38 , Gravity should be a library with a well-defined purpose: to manage data access for relativity objects. The following methods neither are used anywhere within the solution nor suit that purpose, being instead utilities more generally:

  • BaseDto.DeepClone
  • CollectionsExtensions.IsSameAs
  • StringExtensions.Encrypt
  • StringExtensions.Decrypt
  • StringExtensions.RemoveHtmlFromMessageContent
  • StringExtensions.RemoveHtml
  • StringExtensions.ClipInMiddle

If/when we do #38, a couple more of these will appear (such as IntExtensions.ToSeparatedString.)

In addition the whole AppConfigConnectionHelper class fits this bill.

Use HashSet or flags for choices

Right now, a choice list is a List<T>. This doesn't ensure that the selected choices are unique, and could lead to strange behavior and/or failed API calls if a choice is added to the list multiple times.

We should use either flag enums instead (and then the choice list can just be of type T), or the choice list should be a HashSet<T> instead.

I favor using HashSet.

Re-Architect for Testability

The architecture of the RSAPI repositories does not allow them to be mocked. As the Gravity framework matures, a comprehensive set of unit tests will be a must for developers to contribute without breaking existing functionality. Therefore, an architecture that doesn't abstract the repositories discourages community contribution. If these repositories are separated from the framework implementation, however, unit tests are simple to implement and contributions more likely.

My architectural solution is as follows:

Inside the RsapiDao object constructor, instantiate the RsapiClient proxy and assign the supported respositories to a public property called repos. A RepoGroup object that takes an IRSAPIClient implementation and assigns the individual repos in the constructor may be best. For example:

class RepoGroup
{
  public RDORepository RDO;
  RepoGroup(IRSAPIClient client)
{
  this.RDO = client.repositories.RDO;
  // further supported repositories here
}
}

class RsapiDao
{
    public RepoGroup repos;
    public IRSSAPIClient client;

   RsapiDao(IHelper helper)
{
  this.client = GetProxy(helper);
  this.repo = new RepoGroup();
}
}

It's necessary for the repos property on the RsapiDao to be public because, when unit testing, this property must be replaceable with a mock. It may be worthwhile to indicate this property isn't intended for direct use with a combination of code comments and naming. A property with one or two underscores indicates private in Python __repos.

When this architecture exists, the repos property may be used in place of the full proxy signature. For further abstraction, logic-intensive operations may have the necessary repo(s) passed via their arguments. For example:

public GetRelativityObject(int artifactID)
{
var rdo = this.GetRDO(artifactID, this.repos.RDO);
}

Thankfully, this architecture is only necessary for the RSAPI. If or when we decide to include additional services (such as a RestDao or DBContextDao) only this Dao will require this extra step.

Performance Note: It may be worthwhile to lazy load the assignment of the rsapi repositories to the RepoGroup object. I don't know what the performance hit may be, but most apps won't need all repos and this should keep uncalled repos from eating up memory.

Tech Note: The RepositoryGroup object that houses all the repositories on the RSAPIClient object instantiates them inside an internal constructor. This is the reason it cannot be mocked.

Update client object for unit tests

We cant mock the client object when the client is created through the Helper. We found out internally through the platform team the reason we were not able to do that was because we are not able to stub out some of the properties when the client gets set through a Helper.

We recommend you use the IRsapiRepositoryGroup to create a client for different object types. This is an example of how we have created the client object - https://github.com/relativitydev/admin-migration-utility/blob/master/Source/Code/AdminMigrationUtility/AdminMigrationUtility/AdminMigrationUtility.Helpers/Rsapi/RSAPIiRepositoryGroup.cs

Remove RelativityObjectField and ObjectFieldDTOType

Per pull #87, based on recent changes we no longer think these are needed.

As for single/multiple, the idea is that you don't need to declare the type because it can be inferred. Perhaps we should remove the type argument on RelativityObjectField altogether, now that we don't have the ID-list versions.

IHelper Implementation Mismatch

The Gravity.Base.AppConfigConnectionHelper implements Relativity.API.IHelper, however, it's missing four methods on the interface of the 9.4.398.62 assembly:

  • public string ResourceDBPrepend()
  • public string ResourceDBPrepend(IDBContext context)
  • public string GetSchemalessResourceDataBasePrepend(IDBContext context)
  • public Guid GetGuid(int workspaceID, int artifactID)

RelativityObjectChildrenList has redundant parameter

Note the following from the front page example:

[RelativityObjectChildrenList(typeof(RelatedPurchase))]
public IList<RelatedPurchase> RelatedPurchases { get; set; }

The type parameter typeof(RelatedPurchase) is redundant. It should just be inferred from whatever property the attribute is on.

Simplify File field model

Currently, file fields expose RSAPI's FileMetadata and FileValue classes directly. These contain far more information than we need.

We should map to/from a custom object with the following three fields:

  • byte[] data
  • string FileName (can be inferred if FilePath is supplied)
  • string FilePath (left blank on server reads)

Remove SharedConstant.FieldTypes

I was thinking about #2 , and think the custom field types (SharedConstants.FieldTypeCustomListInt amd SharedConstants.FieldTypeByteArray) should be removed from Gravity altogether. Gravity, as a public DAL library, should be focused on mapping to/from Relativity types only. This won't remove the ability for anyone to do anything they already were; they would just have to move the conversion logic into property setters. For example, instead of

[RelativityObjectField(guidString, SharedConstants.FieldTypeCustomListInt)]
List<int> IntegerList {get; set;}

one could use standard Relativity types as follows:

[RelativityObjectField(guidString, (int)RdoFieldType.LongText)]
string IntegerListBacking
{
   get => IntegerList.Select(x => x.ToString()).ToSeparatedString(",");
   set => IntegerList = value.Split(',').Select(int.Parse);
}

List<int> IntegerList {get; set;}

Remove or change signature of UpdateField

https://github.com/tsdservices/Gravity/blob/075de8e177e33c903227f00bf58863484e59649e/Gravity/Gravity/DAL/RSAPI/RsapiDao.Update.cs#L104-L105

The code in this method is hard to understand, and the rules for mapping values to RDO values do not follow the same ones as in ToHydratedDto.

More broadly, what is this code trying to solve? We have mapping code elsewhere. If it's because sometimes you want to just set a single field, then either we should implement #25, or just add a params at the end of UpdateRelativityObject and related methods that lets you specify which fields you want to update, like seen here.

At a bare minimum, we should use the same basic mapping logic we use in ToHydratedDto, even if that requires the end user to specify the field type along with the GUID.

Map EventHandler artifacts to/from BaseDTO objects

It would make event handlers easier to work with if we could manipulate the Artifact object as a BaseDto type. This would be especially handy for Post-Save Event Handlers, where one may want to use a service/repository that assumes, like elsewhere in an application using Gravity that the object inherits BaseDto.

What should we do with choices that do not match an enum?

What should we do when reading/querying a choice field, but the choice does not correspond to any of our choice Guid/name values?

Some options:

  1. Ignore. We could just not add those choices to the model.
    • This is simple to implement, but would not work with updates, because we would then be overwriting the value by omitting it.
  2. Store Unmapped Values. We could keep unmapped values in a private base property of BaseDto.
    • When updating, these choices could be added to those mapped from the enum.
    • Downside is that this is hacky/brittle.
  3. Throw. Simply throw an exception if the value cannot be mapped.
    • This is simple and does not corrupt any data, but prevents using dynamic choices.

I personally favor 3.

Inconsistent/ill-defined recursion behavior

I was refactoring the RsapiDao in preparation for working on #8, and notices something strange.
You can see the refactor here.

Most of the recursion can be refactored out to the following method:

private T GetHydratedDTO<T>(RDO objectRdo, ObjectFieldsDepthLevel depthLevel) 
    where T : BaseDto, new()
{
    T dto = objectRdo.ToHydratedDto<T>();

    switch (depthLevel)
    {
        case ObjectFieldsDepthLevel.FullyRecursive:
            PopulateChildrenRecursively<T>(dto, objectRdo, depthLevel);
            return dto;
        default:
            return dto;
    }
}

But one method seems to use different logic:

public T GetRelativityObject<T>(int artifactId, ObjectFieldsDepthLevel depthLevel)
    where T : BaseDto, new()
{
    var objectRdo = GetRdo(artifactId);
    var dto = objectRdo.ToHydratedDto<T>();

    switch (depthLevel)
    {
        case ObjectFieldsDepthLevel.OnlyParentObject:
            return dto;
        default:
            PopulateChildrenRecursively<T>(dto, objectRdo, depthLevel);
            return dto;
    }
}

In addition to being inconsistent, it seems at first glance that neither rule covers what one would expect for ObjectDepthsFieldLevel.FirstLevelOnly - mainly, that there would be one level of recursion, but that level would hydrate the DTOs with ObjectFieldsDepthLevel.OnlyParentObject.

We should clarify what, exactly, we expect each member of the enum to do, and fix/refactor the recursion calls accordingly.

Add editor.config file

Since we're working across companies with different code styles, it may be nice to use VS2017's editor.config support to enforce uniform code convention (at least for simple things like tabs vs spaces).

Need to update version of TestHelpers used.

There are some changes to the DBContext in the APIs that may cause some problems with tests run against later 9.6 versions. The update should not require Gravity coding changes.

Insert Single Object field does not work.

The following code does not create the GravityLevel2 object. The GravityLevelOne is created but the GravityLevel2 object should be created and then GravityLevelOne field for that object should be created.

GravityLevelOne testObject = new GravityLevelOne();
testObject.Name = $"TestObject";

GravityLevel2 g2 = new GravityLevel2();
g2.Name = $"TestLevel2Object";

testObject.GravityLevel2Obj = g2;

 _testObjectHelper.CreateTestObjectWithGravity(testObject);

Defining recursion behavior for insert/update

We have well-defined recursion behavior for Get (can specify) and Delete (delete recursively). But what about Insert and Update?

Here is a partial proposal> "?"s mean we need to make a choice.

  • Insert
    • File: Upload
    • Single/MultiObject
      • New: insert recursively
      • Update: ?
        • Use only ArtifactIDs (Depth: None)
        • Control Depth via Insert parameter (None, LevelOne, infinite)
        • Control Depth via DTO property
    • Child Object: new; insert recursively
  • Update
    • File: ?
      • New: Insert
      • Delete: Clear
      • Update: ?
        • Would we upload new version if Depth is None?
        • Do we even know how to detect changes?
    • Single/Multi/Child Object:
      • New: Create
      • Updated:
        • Use only ArtifactIDs (Depth: None)
        • Control Depth via Update parameter (None, LevelOne, infinite)
        • Control Depth via DTO property
    • Child Object:
      • Missing from list: ?
        • Delete object entirely?
        • Control by recursion level if missing from list?

Support for Execution Identity in RsapiDao

One major feature we need with Gravity is the ability to set the current ExecutionIdentity. Adding this in seems easy enough I just wanted to check which route the fix should be made.

This is the current signature for creating an RsapiDao:

public RsapiDao(IHelper helper, int workspaceId, InvokeWithRetrySettings invokeWithRetrySettings = null)

In my eyes the ideal signature would be
public RsapiDao(IHelper helper, int workspaceId, ExecutionIdentity executionIdentity, InvokeWithRetrySettings invokeWithRetrySettings = null)

My only concern here would be that this is a breaking change and would cause some issues for people upgrading (Currently this would only affect TSD). One potential solution would be adding the above signature and keeping the old one there as well. Possibly even deprecating the original constructor and removing it at a future time.

Thoughts?

Create Roadmap

Detail which features are solid and have test coverage and where we want to head next.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.