I am trying to use OrientDB-NET.binary, extending it to improve ease-of-use among my development team. I am trying to create syntax which is similar to dapper, as that's what we're using for our MSSQL and MySQL connections. This should make it easier on our development team swapping between the two technologies.
I'd also like my extensions to function with transactions.
Here's my Insert extension as it is now:
public static void Insert<T>(this ODatabase db, T model,
OTransaction transaction) where T : ABaseModel, new()
{
InsertHelper(db, model, transaction, new List<object>());
}
private static void InsertHelper<T>(ODatabase db, T model,
OTransaction transaction, ICollection<object> exclude, ORID parent = null)
where T : ABaseModel, new()
{
// Avoid following loops into a stack overflow
if (exclude.Contains(model)) return;
exclude.Add(model);
ODocument record = new ODocument();
record.OClassName = model.GetType().Name;
PropertyInfo[] properties = model.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance |
BindingFlags.SetProperty | BindingFlags.GetProperty);
ICollection<PropertyInfo> linkableProperties = new List<PropertyInfo>();
foreach (PropertyInfo prop in properties)
{
if (reservedProperties.Contains(prop.Name)) continue;
OProperty aliasProperty = prop.GetCustomAttributes(typeof(OProperty))
.Where(attr => ((OProperty)attr).Alias != null)
.FirstOrDefault() as OProperty;
string name = aliasProperty == null ? prop.Name : aliasProperty.Alias;
// Record properties of model, but store properties linking to other
// vertex classes for later
if (typeof(ABaseModel).IsAssignableFrom(prop.PropertyType))
{
linkableProperties.Add(prop);
}
else
{
record[name] = prop.GetValue(model);
}
}
transaction.Add(record);
model.ORID = record.ORID;
foreach (PropertyInfo prop in linkableProperties)
{
ORID outV, inV;
ABaseModel propValue = prop.GetValue(model) as ABaseModel;
if (!exclude.Select(ex => ex is ABaseModel ? ((ABaseModel)ex).ORID :
ORID_DEFAULT).Contains(propValue.ORID))
{
MethodInfo insertMethod = typeof(DatabaseExtensions)
.GetMethod("InsertHelper", BindingFlags.NonPublic |
BindingFlags.Static).MakeGenericMethod(propValue.GetType());
insertMethod.Invoke(null,
new object[] {
db, propValue, transaction, exclude, model.ORID
});
}
outV = model.ORID;
inV = propValue.ORID;
OEdgeAttribute edgeType =
prop.GetCustomAttributes(typeof(OEdgeAttribute))
.FirstOrDefault() as OEdgeAttribute;
OProperty propertyAlias = prop.GetCustomAttributes(typeof(OProperty))
.Where(p => ((OProperty)p).Alias != null)
.FirstOrDefault() as OProperty;
string alias = propertyAlias == null ? prop.Name : propertyAlias.Alias;
if (edgeType != null)
{
OEdge link = new OEdge();
link.OClassName = alias;
link["out"] = outV;
link["in"] = inV;
if(edgeType.IsInV)
{
ORID tmp = link.OutV;
link["out"] = link.InV;
link["in"] = tmp;
}
// Do not create an edge if there is an edge already
// connecting these vertices
IEnumerable<Tuple<ORID,ORID>> excludedLinks = exclude
.Select(ex => ex is OEdge ?
new Tuple<ORID,ORID>(((OEdge)ex).OutV,((OEdge)ex).InV) :
new Tuple<ORID,ORID>(ORID_DEFAULT, ORID_DEFAULT));
if (excludedLinks.Contains(
new Tuple<ORID, ORID>(link.OutV, link.InV))) continue;
exclude.Add(link);
transaction.Add(link);
}
}
}
ABaseModel is an abstract class intended to be extended by all model classes in my application. OEdgeAttribute is an attribute which lets me specify if a property is representing an 'in' edge or an 'out' edge on a vertex. ORID_DEFAULT is equal to new ORID()
The above extension method works just fine... mostly. Using the following model classes:
public class Person : ABaseModel
{
[OProperty(Alias = "ResidenceAddress")]
[OEdge(IsOutV = true)]
public Address Residence { get; set; }
[OProperty(Alias = "ShippingAddress")]
[OEdge(IsOutV = true)]
public Address Shipping { get; set; }
}
public class Dependent : Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
// etc...
}
public class Address : ABaseModel
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
// etc...
[OProperty(Alias = "PropertyAddress")]
public Person Resident { get; set; }
}
I then 'import' several hundred Dependents by mapping from another set of entries in the table:
OClient.CreateDatabasePool("127.0.0.1", 2424, "persephone", ODatabaseType.Graph,
"admin", "admin", 10, "Persephone");
// PersephoneConnection has access to ODatabase and OTransaction,
// and some methods which dispatch to them
IPersephoneConnection persephone = new PersephoneConnection();
persephone.BeginTransaction();
// Traverse is a functioning extension method which essentially calls
// `traverse * from {0}` and maps the result to model objects
IEnumerable<tmpED> eds = persephone.Connection.Traverse<tmpED>("tmpED");
foreach (tmpED model in eds)
{
Dependent dep = new Dependent
{
FirstName = model.firstname,
LastName = model.lastname,
// etc...
};
Address residence = new Address
{
AddressLine1 = model.addres_line1,
AddressLine2 = model.address_line2,
// etc...
Resident = dep
};
dep.Residence = residence;
Address shipping = new Address
{
AddressLine1 = model.shippingaddress_line1,
AddressLine2 = model.shippingaddres_line2,
// etc...
Resident = dep
};
dep.Shipping = shipping;
persephone.Connection.Insert(dep, persephone.Transaction);
}
persephone.Commit();
persephone.Dispose();
After running this code, my database contains 273 Dependent records (extends Person which extends V) with the correct values for their properties, 546 Address records (extends V) with the correct values for their properties, 273 ResidenceAddress records (extends PropertyAddress which extends E) with the correct out and in rids, and 273 ShippingAddress records (extends PropertyAddress) with the correct out and in rids.
However, none of the Dependents or Addresses can see the edges which connect them. traverse * #29:0 brings up the first Dependent, but doesn't bring up his residence or shipping address. select expand(out('ResidenceAddress')) from Dependent returns an empty result set.
After some tinkering, it appears that the cause of this is because OTransaction.Add is essentially performing an insert into {0} when the transaction is committed, rather than a create vertex {0} or create edge {0}.
I can certainly change my code to use ODatabase.Create.Vertex() and ODatabase.Create.Edge() instead of OTransaction.Add(), but instead of committing a transaction in that case, I have to call OSqlCreateEdge.Run() or OSqlCreateVertex.Run(), which processes the record creation immediately, and a rollback isn't an option.
Is there any way to get my edges created properly (as opposed to simply inserted as records) while still using transactions?