How to use "one to many" relations in SubSonic - subsonic

What are the ways to tell SubSonic about the relationship (only foreign keys? Or other methods too)?
If I have (for example) a team object with related team members
** how do I access and update the team members from the team
** how do I update the team members? Does saving the team object saves the team members changes
** How do I add members to the team? Do I just create a new member, assign the team ID to the foreign key and save? Or is there a more object oriented way (e.g. team.Add(teamMember))

Subsonic code generation will read the foreign key relationships in the tables and create the required helper methods in the table classes. The Northwind Product class has a PrimaryKey relationship to the OrderDetail class. Subsonic generates the method
public Northwind.OrderDetailCollection OrderDetails()
to get the OrderDetail rows as an OrderDetailCollection. This is a BindingList that you can change as needed, and call SaveAll() to save the list. There is no deep saving, so saving the Product won't save related OrderDetail rows.
[Test]
public void Demo_Product_OrderDetails()
{
Product product = new Product(3); // Read an existing row.
OrderDetailCollection orderDetails = product.OrderDetails();
Assert.IsTrue(orderDetails.Count == 12);
foreach(OrderDetail orderDetail in orderDetails)
{
orderDetail.Discount -= 0; // Do something meaningful.
}
OrderDetail newDetail = new OrderDetail();
newDetail.ProductID = 3;
newDetail.OrderID = 10248;
newDetail.UnitPrice = 7.00m;
newDetail.Discount = 0.10f;
newDetail.Quantity = 12;
orderDetails.Add(newDetail);
orderDetails.SaveAll();
orderDetails = product.OrderDetails();
Assert.IsTrue(orderDetails.Count == 13);
OrderDetail.Destroy(newDetail.OrderID);
orderDetails = product.OrderDetails();
Assert.IsTrue(orderDetails.Count == 12);
}

Related

illegal document key in ArangoDB graph

I am starting in ArangoDB, through an access in a Java application. In the project, I need to create a collection of employees and a dynamic number of vertices of stockings, with connections between employees and these stockings through edges, as shown in the figure.
In the codes below I am creating only one stocking (just use a loop to create the others). The problem is how to create the edge.
I can already create the Capacity and Employee vertices. These collections must be connected through the edge Vacation, but I am not able to create the edges.
Initially, I used Solution1, based on this code. With this code, the three collections are created as a vertex, but the edges are not created interconnecting the Employees and Stockings.
Adapting the code for Solution 2. based on the following script, “Error: 1221 - illegal document key” was generated when executing createGraph() (see marked line).
I ask the experts for help in identifying how to create the graph correctly.
Solution1
ArangoDB arangoUtil = new ArangoDB.Builder()
.serializer(new ArangoJack())
.host(ARANGODB_HOST, ARANGODB_PORT)
.user(ARANGODB_USER)
.password(ARANGODB_PASSWORD)
.useProtocol(Protocol.HTTP_JSON)
.build();
registerStockings();
//each employee has more than one vacation record per year
for(Vacation v : listVacations){
registerEmployees(v);
registerVacations(v);
}
private registerStockings() throws ArangoDBException{
BaseDocument bDoc;
for(Stocking s : list){
bDoc = new BaseDocument();
bDoc.setKey(s.getUorg());
bDoc.addAttribute("nomeLotacao", s.getNome());
bDoc.addAttribute("descricao", s.getDescricao());
arangoUtil.db(ARANGODB_DATABASE)
.collection(“Stock”)
.insertDocument(bDoc);
}
}
private void registerEmployees(Vacation v) throws ArangoDBException{
BaseDocument bDoc = new BaseDocument();
bDoc = new BaseDocument();
bDoc.setKey(v.getSiape());
bDoc.addAttribute("nomeServidor", v.getNomeServidor());
arangoUtil.db(ARANGODB_DATABASE)
.collection("Employees")
.insertDocument(bDoc);
}
private void registerVacations(Vacation v) throws ArangoDBException{
BaseEdgeDocument bDoc = createEdgeValue();
bDoc.setKey(sb.append(v.getSiape()).append("_").append(v.getDataInicio().getTime()).toString());
bDoc.addAttribute("exercicio", v.getExercicio());
bDoc.addAttribute("duracao", v.getDuracao());
bDoc.addAttribute("dataInicio", v.getDataInicio());
bDoc.addAttribute("dataFim", v.getDataFim());
bDoc.setFrom(“Employees”);
bDoc.setTo(“Stockings”);
arangoUtil.db(ARANGODB_DATABASE)
.graph(“MyGraph”)
.edgeCollection(“Vacations”)
.insertEdge(bDoc);
}
private static BaseEdgeDocument createEdgeValue(){
VertexEntity v1 = arangoUtil.db(ARANGODB_DATABASE)
.graph(“MyGraph”)
.vertexCollection(“Employees”)
.insertVertex(new BaseDocument(), null);
VertexEntity v2 = arangoUtil.db(ARANGODB_DATABASE)
.graph(“MyGraph”)
.vertexCollection(“Stockings”)
.insertVertex(new BaseDocument(), null);
BaseEdgeDocument bed = new BaseEdgeDocument();
bed.setFrom(v1.getId());
bed.setTo(v2.getId());
return bed;
}
Solution2
ArangoDB arangoUtil = new ArangoDB.Builder()
.serializer(new ArangoJack())
.host(ARANGODB_HOST, ARANGODB_PORT)
.user(ARANGODB_USER)
.password(ARANGODB_PASSWORD)
.useProtocol(Protocol.HTTP_JSON)
.build();
createCollections();
registerStockings();
//each employee has more than one vacation record per year
for(Vacation v : listVacations){
registerEmployees(v);
registerVacations(v);
}
private void createCollections() throws ArangoDBException{
arangoUtil.db(ARANGODB_DATABASE).createCollection(“Employees”,
new CollectionCreateOptions().type(CollectionType.DOCUMENT));
arangoUtil.db(ARANGODB_DATABASE).createCollection(“Stockings”,
new CollectionCreateOptions().type(CollectionType.DOCUMENT));
arangoUtil.db(ARANGODB_DATABASE).createCollection(“Vacations”,
new CollectionCreateOptions().type(CollectionType.EDGES));
List<EdgeDefinition> listEdgeDef = new ArrayList<EdgeDefinition>();
EdgeDefinition edgeDef = new EdgeDefinition();
edgeDef.collection(“Vacations”);
edgeDef.from(“Employees”);
edgeDef.to(“Stockings”);
listEdgeDef.add(edgeDef);
arangoUtil.db(ARANGODB_DATABASE).createGraph(“MyGraph”, listEdgeDef, null); **** error ****
}

Dynamics CRM SDK - IN operator for linq with OrganizationServiceContext

I'm using my OrganizationServiceContext implementation generated by the svcutil to retrieve entities from CRM:
context.new_productSet.First(p => p.new_name == "Product 1");
Is it possible to retrieve multiple entities with different attribute values at once - (smth like IN operator in SQL)?
Example: I would like to retrieve multiple products ("Product 1", "Product 2", ...) with a single call. The list of product names is dynamic, stored in an array called productNames.
No, you can't. CRM LINQ provider only allows variables to appear on the left side of expressions, while the right side must contain constants.
i.e.
Product.Where(e => e.Name == desiredName)
Is not supported and won't work (it will complain about using a variable on the right side of the comparison).
If you cannot avoid this kind of query, you have to .ToList() data first (this can lead to a huge result set and will probably turn up to be unconceivably slow):
Product.ToList().Where(e => e.Name == desiredName)
This will work, because now the .Where() is being applied on a List<> instead.
Another approach (I don't have data about performance, though) would be to create many queries, basically fetching the records one at a time:
// ... this is going to be a nightmare ... don't do it ...
var entities = new List<Product>();
entities.Add(Product.Where(e => e.Name == "Product 1"));
entities.Add(Product.Where(e => e.Name == "Product 2"));
Or use a QueryExpression like this (my personal favourite, because I always go late-bound)
var desiredNames = new string[]{"Product 1", "Product 2"};
var filter = new FilterExpression(LogicalOperator.And)
{
Conditions =
{
new ConditionExpression("name", ConditionOperator.In, desiredNames)
}
};
var query = new QueryExpression(Product.EntityLogicalName)
{
ColumnSet = new ColumnSet(true),
Criteria = filter
};
var records = service.RetrieveMultiple(query).Entities;
If combining Linq and Lambda expression is ok, it can be done. First you need to create an extension method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Kipon.Dynamics.Extensions.IQueryable
{
public static class Methods
{
public static IQueryable<TSource> WhereIn<TSource, TValue>(this IQueryable<TSource> source, Expression<Func<TSource, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == source) { throw new ArgumentNullException("source"); }
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
var equalExpressions = new List<BinaryExpression>();
foreach (var value in values)
{
var equalsExpression = Expression.Equal(valueSelector.Body, Expression.Constant(value));
equalExpressions.Add(equalsExpression);
}
ParameterExpression p = valueSelector.Parameters.Single();
var combined = equalExpressions.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
var combinedLambda = Expression.Lambda<Func<TSource, bool>>(combined, p);
return source.Where(combinedLambda);
}
}
}
With this method in place, you can now use it against your context. First remember to import the namespace of the extension to make the method available on IQueryable:
using System.Linq;
using Kipon.Dynamics.Extensions.IQueryable;
public class MyClass
{
void myQueryMethod(CrmContext ctx, Guid[] contacts)
{
var accounts = (from a in ctx.accountSet.WhereIn(ac => ac.primarycontactid.id,contacts)
where a.name != null
select a).toArray();
}
}
There is no way you can hook into the Dynamics 365 Linq expression compiler, as far as I know, but the above code will execute in one request against the CRM, and take advantage
of the fact that you do not need to consider paging and more when working with Linq.
As you can see, there whereIn clause is added with a lambda style expression, where the rest of the query is using the Linq style.
When using QueryExpression, we can add condtionexpression for where clause. ConditionExpression takes a ConditionOperator enumerator, and we can use ConditionOperator.In. Below is how you initiate a conidtionExpression with an “In” operator, the third argument can be an array or collection.
ConditionExpression ce = new ConditionExpression("EntityName",
ConditionOperator.In, collectionObject);
Please see below for further explanation.
http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.query.conditionexpression.conditionexpression.aspx
I do not know how to do this with Linq, as far as I know it is not possible.
It can be done with Query Expressions:
String[] productNames = new[] { "test1", "test2" };
QueryExpression products = new QueryExpression(Product.EntityLogicalName);
products.ColumnSet = new ColumnSet("name", "new_att1", "new_att2"); // fields to get
products.Criteria.AddCondition("name", ConditionOperator.In,
productNames.Cast<Object>().ToArray()); // filter by array
EntityCollection res = service.RetrieveMultiple(products);
IEnumerable<Product> opportunities = res.Entities
.Select(product => product.ToEntity<Product>()); // you can use Linq again from here

Get entities by multiple ids in N:N relation

Given entities:
Team, User. Relation between those is N:N.
Question:
How do I find users which belong to specified teams (with given list of ids).
PS.
I found how to do with single team, but have no clue how to deal with the list of teams?
var team_id = ...
QueryExpression query = new QueryExpression("user");
// setting up relation between teams and users
Relationship rel = new Relationship();
rel.SchemaName = "new_teams_users";
RelationshipQueryCollection relatedEntity = new RelationshipQueryCollection();
relatedEntity.Add(rel, query);
RetrieveRequest request = new RetrieveRequest();
request.RelatedEntitiesQuery = relatedEntity;
request.ColumnSet = new ColumnSet(new string[] {"id"});
request.Target = new EntityReference { Id = team_id, LogicalName = "new_team" };
// Results: List of users by team id.
RetrieveResponse response = (RetrieveResponse)CrmService.Execute(request);
QueryExpression build on intersect entity will help you. As example i used product and competitor N:N relationship
QueryExpression qe = new QueryExpression()
{
EntityName = "competitorproduct", //this name can be get from N:N rel properties (Relationship form, Relationship Entity Name field)
ColumnSet = new ColumnSet(true),
};
qe.Criteria.AddCondition(
"competitorid",
ConditionOperator.In,
new object[] { "GUID1", "GUID2"});
//Below is optional - if you need some details of entity, add LinkEntity object. This example adds all fields from product entity
LinkEntity lePorduct = new LinkEntity("competitorproduct", "product", "productid", "productid", JoinOperator.Inner);
lePorduct.Columns = new ColumnSet(true);
qe.LinkEntities.Add(lePorduct);
You would make your primary entity the intersection entity so in your example it would be "TeamMembership" the criteria would then be set against the attribute "SystemUserId".
To get more information on the team you need to add the team entity as a linked entity to your query like this
LinkEntity TeamLink = new LinkEntity();
TeamLink .EntityAlias = "TeamLink ";
TeamLink .JoinOperator = JoinOperator.Inner;
TeamLink .LinkFromEntityName = "teammembership";
TeamLink .LinkFromAttributeName = "teamid";
TeamLink .LinkToEntityName = "team";
TeamLink .LinkToAttributeName = "teamid";
You can then bring back what ever columns you want and get the data out.

Dynamics CRM - Accessing Custom Product Option Value

Is there a way to programmatically access the Label & Value fields that has been created as a custom Field in MS CRM Dynamics please?
I have added a custom field called "new_producttypesubcode" which, for example, has 2 options, Trophy = 1000000 and Kit = 10000001.
I am writing an import utility that mirrors products between the customers website and their CRM and I want to get a list of all possible product options in the CRM to see if they are matched in the website.
So, in essence I want to...
get the list of possible new_producttypesubcodes and their corresponding values.
Iterate through the product variants in the website.
if the product variant name matches any name in the list of new_producttypecodes then add the value 1000000
So, if I find a product added to the website and its marked as a "Trophy" and "Trophy" exists in the CRM then new OptionSetValue(100000001)
I hope that makes sense...
Thanks
This function retrieves a dictionary of possible values localised to the current user. Taken from: CRM 2011 Programatically Finding the Values of Picklists, Optionsets, Statecode, Statuscode and Boolean (Two Options).
static Dictionary<String, int> GetNumericValues(IOrganizationService service, String entity, String attribute)
{
RetrieveAttributeRequest request = new RetrieveAttributeRequest
{
EntityLogicalName = entity,
LogicalName = attribute,
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse response = (RetrieveAttributeResponse)service.Execute(request);
switch (response.AttributeMetadata.AttributeType)
{
case AttributeTypeCode.Picklist:
case AttributeTypeCode.State:
case AttributeTypeCode.Status:
return ((EnumAttributeMetadata)response.AttributeMetadata).OptionSet.Options
.ToDictionary(key => key.Label.UserLocalizedLabel.Label, option => option.Value.Value);
case AttributeTypeCode.Boolean:
Dictionary<String, int> values = new Dictionary<String, int>();
BooleanOptionSetMetadata metaData = ((BooleanAttributeMetadata)response.AttributeMetadata).OptionSet;
values[metaData.TrueOption.Label.UserLocalizedLabel.Label] = metaData.TrueOption.Value.Value;
values[metaData.FalseOption.Label.UserLocalizedLabel.Label] = metaData.FalseOption.Value.Value;
return values;
default:
throw new ArgumentOutOfRangeException();
}
}
So you would then need to do something like:
Dictionary<String, int> values = GetNumericValues(proxy, "your_entity", "new_producttypesubcode");
if(values.ContainsKey("Trophy"))
{
//Do something with the value
OptionSetValue optionSetValue = values["Trophy"];
int value = optionSetValue.Value;
}
Yes, that data is all stored in the metadata for an attribute (SDK article). You have to retrieve the entity metadata for the entity and then find the attribute in the list. Then cast that attribute to a PicklistAttributeMetadata object and it will contain a list of options. I would mention that typically retrieving Metadata from CRM is an expensive operation, so think about caching.
private static OptionSetMetadata RetrieveOptionSet(IOrganizationService orgService,
string entityName, string attributeName)
{
var entityResponse = (RetrieveEntityResponse)orgService.Execute(
new RetrieveEntityRequest
{ LogicalName = entityName, EntityFilters = EntityFilters.Attributes });
var entityMetadata = entityResponse.EntityMetadata;
for (int i = 0; i < entityMetadata.Attributes.Length; i++)
{
if (attributeName.Equals(entityMetadata.Attributes[i].LogicalName))
{
if (entityMetadata.Attributes[i].AttributeType.Value ==
AttributeTypeCode.Picklist)
{
var attributeMD = (PicklistAttributeMetadata)
entityMetadata.Attributes[i];
return attributeMD.OptionSet;
}
}
}
return null;
}
Here is how to write the options to the console using the above call.
var optionSetMD = RetrieveOptionSet(orgService, "account", "accountcategorycode");
var options = optionSetMD.Options;
for (int i = 0; i < options.Count; i++)
{
Console.WriteLine("Local Label: {0}. Value: {1}",
options[i].Label.UserLocalizedLabel.Label,
options[i].Value.HasValue ? options[i].Value.Value.ToString() : "null");
}
I believe this works for global option set attributes as well, but if you know it is a global option set there is a different message for it that would probably a bit more efficient (SDK article).

Creating a unattached Entity Framework DbContext entity

So I'm working on an app that will select data from one database and update an identical database based on information contained in a 'Publication' Table in the Authoring database. I need to get a single object that is not connected to the 'Authoring' context so I can add it to my 'Delivery' context.
Currently I am using
object authoringRecordVersion = PublishingFactory.AuthoringContext.Set(recordType.Entity.GetType()).Find(record.RecordId);
object deliveryRecordVersion = PublishingFactory.DeliveryContext.Set(recordType.Entity.GetType()).Find(record.RecordId));
to return my records. Then if the 'deliveryRecordVersion' is null, I need to do an Insert of 'authoringRecordVersion' into 'PublishingFactory.DeliveryContext'. However, that object is already connected to the 'PublishingFactory.AuthoringContext' so it won't allow the Add() method to be called on the 'PublishingFactory.DeliveryContext'.
I have access to PublishingFactory.AuthoringContext.Set(recordType.Entity.GetType()).AsNoTracking()
but there is no way to get at the specific record I need from here.
Any suggestions?
UPDATE:
I believe I found the solution. It didn't work the first time because I was referencing the wrong object on when setting .State = EntityState.Detached;
here is the full corrected method that works as expected
private void PushToDelivery(IEnumerable<Mkl.WebTeam.UWManual.Model.Publication> recordsToPublish)
{
string recordEntity = string.Empty;
DbEntityEntry recordType = null;
// Loop through recordsToPublish and see if the record exists in Delivery. If so then 'Update' the record
// else 'Add' the record.
foreach (var record in recordsToPublish)
{
if (recordEntity != record.Entity)
{
recordType = PublishingFactory.DeliveryContext.Entry(ObjectExt.GetEntityOfType(record.Entity));
}
if (recordType == null)
{
continue;
////throw new NullReferenceException(
//// string.Format("Couldn't identify the object type stored in record.Entity : {0}", record.Entity));
}
// get the record from the Authoring context from the appropriate type table
object authoringRecordVersion = PublishingFactory.AuthoringContext.Set(recordType.Entity.GetType()).Find(record.RecordId);
// get the record from the Delivery context from the appropriate type table
object deliveryRecordVersion = PublishingFactory.DeliveryContext.Set(recordType.Entity.GetType()).Find(record.RecordId);
// somthing happened and no records were found meeting the Id and Type from the Publication table in the
// authoring table
if (authoringRecordVersion == null)
{
continue;
}
if (deliveryRecordVersion != null)
{
// update record
PublishingFactory.DeliveryContext.Entry(deliveryRecordVersion).CurrentValues.SetValues(authoringRecordVersion);
PublishingFactory.DeliveryContext.Entry(deliveryRecordVersion).State = EntityState.Modified;
PublishingFactory.DeliveryContext.SaveChanges();
}
else
{
// insert new record
PublishingFactory.AuthoringContext.Entry(authoringRecordVersion).State = EntityState.Detached;
PublishingFactory.DeliveryContext.Entry(authoringRecordVersion).State = EntityState.Added;
PublishingFactory.DeliveryContext.SaveChanges();
}
recordEntity = record.Entity;
}
}
As you say in your comment the reason why you can't use .Single(a => a.ID == record.RecordId) is that the ID property is not known at design time. So what you can do is get the entity by the Find method and then detach it from the context:
PublishingFactory.AuthoringContext
.Entry(authoringRecordVersion).State = EntityState.Detached;

Resources