Binding NSArray between MonoTouch and Objective C - xamarin.ios

I have created a static library and trying to bind it to Monotouch using link
In the binding i want to pass NSArray from MonoTouch to Objective C. I have it defined in ApiDefinition.cs like
NSObject [] DataArray { get; set; }
When i try to pass string array from MonoTouch
var b = new[] { "Item 1", "Item 2", "Item 3" };
get following error
Cannot implicitly convert type 'string[]' to MonoTouch.Foundation.NSObject[]
When i try to pass NSArray from MonoTouch
NSArray array1 = NSArray.FromObjects ("Item 1", "Item 2", "Item 3");
get following error
Cannot implicitly convert type MonoTouch.Foundation.NSArray to MonoTouch.Foundation.NSObject[]
How do i pass an Array from MonotOuch to Objective C?

You have multiple choices when binding an objective-c array, e.g. you could have used NSArray over NSObject[]. I prefer the later but both are valid options.
NSArray array1 = NSArray.FromObjects ("Item 1", "Item 2", "Item 3");
Using an NSArray would make the above code work since it returns an NSArray.
OTOH you have chosen NSObject[] so you must follow your decision, i.e. use an array of NSObject
var b = new[] { "Item 1", "Item 2", "Item 3" };
The above would use an array of System.String. There's no direct conversion between string and NSObject. You can re-write this as:
var b = new NSObject [] { new NSString ("Item 1"), new NSString ("Item 2"), new NSString ("Item 3") };
which should satisfy the compiler and likely, can't be sure without seeing it, is what your native API wants.
Note: if the native API accept an array of NSString then you can bind it at as NSString[].

Related

cast NSString! to String in swift

I have a instance variable name in String
var name: String
My class implements the NSCoding protocol. So for name I had
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: kName)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObjectForKey(kName) as String // CRASH HERE
}
Result? I was getting a run time crash during initiation with decoder. I changed init to this:
var temp = aDecoder.decodeObjectForKey(kName) as NSString!
self.name = aDecoder.decodeObjectForKey(kName) as String
and realised the value temp is holding the right NSString value. so I thought the line below is going to fix it but it issues a linker error:
self.name = aDecoder.decodeObjectForKey(kName) as NSString!
the questions is how to take the temp and put it into name?
decodeObjectForKey returns an optional AnyObject?, so you have to guard your code against possible nil values. Using a cast to a forced unwrapped doesn't sound safe.
I am unable to reproduce the error (so the problem could be somewhere else in your code), but this is how I would approach when initializing the name property:
var tempName = aDecoder.decodeObjectForKey("name") as? String
if let tempName = tempName {
self.name = tempName
} else {
self.name = "some initial value"
}
Note the usage of the optional downcasting as?, which always produce a result (nil or a valid type value) as opposed to as NSString!, which triggers an exception if the downcast is not possible (for instance if you are expecting a string, but it's an int instead).
That code would also allow you to better debugging - if tempName is nil, then either the key doesn't exist or the corresponding value is of a different type.
There were two things I needed to do to get it working:
clean build with removing the derived data folder
For some reason bridging between the NSString and String is not working in this situation. so the casting should be done in two stages
if let name = aDecoder.decodeObjectForKey(kName) as? NSString {
self.name = name as String
} else {
assert(false, "ERROR: could not decode")
self.name = "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

How to obtain members of a marketing list using QueryExpression object?

I'm using QueryExpression often but this far, it's been a straigh-forward get-this-from-that or put-this-in-that. Recently, I learned that there's something called LinkedEntity and I started looking for it. As an example I got inspired by a related question here on SO and I started to create an expression for getting all the members of a list given it's guid.
All the examples I've found follow the same pattern, though - as this example illustrates. From this question, I've learned that it's an obsolete approach (CRM 4.0). I've failed founding a more up-to-date example and I'm not sure how to design the linkage.
Anybody cares to provide a sample code?
Guid guid = ...;
QueryExpression request = new QueryExpression
{
EntityName = "account",
ColumnSet = new ColumnSet(true),
LinkEntities= ???, // How to link the entities correctly?
Criteria = new FilterExpression { ??? } // How to filter for *guid* only?
};
I've created a fetch-XML linking two entities but I'm not clear on how to translate it to QueryExpression entity. I've got something like this. Any suggestions?
LinkEntity linkListToMember = new LinkEntity(
"list", "listmember", "listid", "listid", JoinOperator.Natural);
LinkEntity linkMemberToContact = new LinkEntity(
"listmember", "account", "entityid", "accountid", JoinOperator.Natural);
A Link Entity serves as your SQL Join. Use the Constructor with the from and to entity and attribute names
public LinkEntity(
string linkFromEntityName, // This is the Entity Logical Name of your Query Expression, or parent LinkEntity
string linkToEntityName, // This is the Entity Logical Name of the entity you'd like to link to
string linkFromAttributeName, // This is the attribute name on your from entity, containing your join key
string linkToAttributeName, // This is the attribute name on your to entity, containing your join key
JoinOperator joinOperator) // This is the type of Join you'd like to perform
Using the Link Entity, you can add Link Criteria to filter the results returned. You can also add Columns and return data from related entities.
Edit, an Addition to Konrad's Answer
If the 52 lines of code that Konrad lists seems too verbose, this will do the same exact thing, in 15 lines, using the extension methods defined here.
Guid guid = ...;
IOrganizationService service;
QueryExpression request = new QueryExpression("account")
{
ColumnSet = new ColumnSet("name", "region"),
};
request.Criteria.AddCondition("name", ConditionOperator.NotNull);
request.Criteria.AddCondition("region", ConditionOperator.NotNull);
var listLink = request.AddLink("listmember", "accountid", "entityid").
AddChildLink("list", "listid");
listLink.Columns.AddColumn("listname");
listLink.LinkCriteria.AddCondition("listid", ConditionOperator.Equal, guid);
Here's a method for getting all the members of a marketing list, given that you have its guid and a server connection. What you did with the conditions is right spot on but you need to jack the one into the other. On Saturday I'll put it with a larger description on my blog.
Guid guid = ...;
IOrganizationService service;
QueryExpression request = new QueryExpression
{
EntityName = "account",
ColumnSet = new ColumnSet("name", "region"),
LinkEntities =
{
new LinkEntity
{
JoinOperator = JoinOperator.Inner,
LinkFromEntityName = "account",
LinkFromAttributeName = "accountid",
LinkToEntityName = "listmember",
LinkToAttributeName = "entityid",
LinkCriteria = { },
LinkEntities =
{
new LinkEntity
{
JoinOperator = JoinOperator.Inner,
Columns = new ColumnSet("listname"),
EntityAlias = "MarketingList",
LinkFromEntityName = "listmember",
LinkFromAttributeName = "listid",
LinkToEntityName = "list",
LinkToAttributeName = "listid",
LinkCriteria = { Conditions =
{
new ConditionExpression("listid", ConditionOperator.Equal, guid)
} }
}
}
}
},
Criteria = new FilterExpression
{
Filters =
{
new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression("name", ConditionOperator.NotNull),
new ConditionExpression("region", ConditionOperator.NotNull)
}
}
}
}
};
Then, of course you need to execute the call.
EntityCollection result = service.RetrieveMultiple(request);
Finally, you might want to order and structure whatever you've got from the server. I'm using the following LINQ-to-Data expression.
IEnumerable<Member> output = result.Entities.Where(element
=> Member.IsWellFormed(element)).Select(element
=> new Member(element));
More on the subject, see the blog.

replacing objects in array while enumerating?

I am sending an update for my app that will require that the users databases be updated. I am storing data in a property list. Basically a every point in the array are NSMutableDictionaries and I need to add keys, replace keys etc.
I attempted the following, however it generates an NSException,
for (NSMutableDictionary *dict in myArray) {
if ([dict objectForKey:#"someKey"] == nil) {
//Extract the value of the old key and remove the old key
int oldValue = [[dict objectForKey:#"key1"] intValue];
[dict removeObjectForKey:#"key1"];
[dict setValue:[NSString stringWithFormat:#"%d pts", oldValue] forKey:#"newKey"];
//Add new keys to dictionnary
[dict setValue:#"some value" forKey:#"key2"];
[dict setValue:#"some value" forKey:#"key3"];
[dict setValue:#"some value" forKey:#"key4"];
[self.myArray replaceObjectAtIndex:index withObject:dict];
}
What should I do to update my data in the above manner?
The problem is that you cannot modify the array you are iterating over with fast enumeration.
The code snippet has no need for that replaceObjectAtIndex:withObject: call at all, as you replace the object with the very same object! So if you remove that line everything should work.
Generally, you can avoid similar problems if you use the plain old for loop with indexing, i.e.
for (int i = 0; i < [array count]; i++) {
id obj = [array objectAtIndex:i];
// ...
}
as this will not mess up with fast enumeration.
Create a copy of the array and enumerate through the copy. In this way you can safely modify the original one:
for (id obj in [NSArray arrayWithArray:entries]) {
[entries removeObject:obj];
}
Do not use:
for (int i = 0; i < [array count]; i++) {
id obj = [array objectAtIndex:i];
[array removeObject:obj];
}
Do this because, after the removal the array indexes will be offset!
First, make sure that myArray is an NSMutableArray. If so, you'll probably see some error if you debug the code that says something like _NSArrayI unrecognized selector sent to instance _NSArrayI means it's an immutable array. This is very annoying, but try to test by doing this. You can then just replace your myArray with the mutableArray.
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:self.myArray];
for (NSMutableDictionary *dict in mutableArray) {
if ([dict objectForKey:#"someKey"] == nil) {
//Extract the value of the old key and remove the old key
int oldValue = [[dict objectForKey:#"key1"] intValue];
[dict removeObjectForKey:#"key1"];
[dict setValue:[NSString stringWithFormat:#"%d pts", oldValue] forKey:#"newKey"];
//Add new keys to dictionnary
[dict setValue:#"some value" forKey:#"key2"];
[dict setValue:#"some value" forKey:#"key3"];
[dict setValue:#"some value" forKey:#"key4"];
[mutableArray replaceObjectAtIndex:index withObject:dict];
}
}

Cocoa Bindings: Binding to the "many" end of a to-many relationship

Using the Employees-Departments example what I want to do is bind a column to "Departments.arrangedObjects.employees.#sum.hoursWorked" as outlined below:
Entity Employee
attributes: "firstName", "lastName", "hoursWorked"
relationship: "departments"
Entity Department
attributes: "name"
relationship: "employees"
I want a table which will display some summary info about departments.
I bind the first column to my "Departments" array controller, "arrangedObjects.name".
I can have a column displaying the number of employees in a department by binding to "arrangedObjects.employees.#count"
However I can't get a sum of the hoursWorked by employees as I assume I might by binding to "arrangedObjects.employees.#sum.hoursWorked"
The error I get is along the lines of "[<_NSFaultingMutableSet 0x1acea0> addObserver:forKeyPath:options:context:] is not supported. Key path: #sum.hoursWorked"
I believe this is because it is not possible to bind to the many end of a to-many relationship. If so how can I do what I want to do?
For extra credit, say each employee also has another attribute, "race", I would also like my summaries table to show the number of unique races in each department.
Thanks in advance.
I encountered the same errors you did. It seems that while you can get the set of employees and perform some set of aggregate operations on it by doing something like:
Department* dept = ;
NSSet* employees = dept.employees;
NSNumber* sumOfHoursWorked = [employees valueForKeyPath: #"#sum.hoursWorked"];
There's a difference when you bind. Bindings are asking to observe the key path, not evaluate it once. Given that, it kinda, sorta makes sense why you can't bind to these key paths. Kinda. Sorta.
Now. As for a solution, what I usually do in cases like this is write a tiny little NSValueTransformer subclass to do just what I need, and then plug that into IB. This way, I write the ten lines of code I need, but don't end up doing the whole NSTableView data source spiel for want of a simple aggregate. In this case, you might do something like this:
// Declaration
#interface MySumOfHoursWorkedTransformer : NSValueTransformer
#end
#interface MyNumberOfRacesTransformer : NSValueTransformer
#end
// Implementation
#implementation MySumOfHoursWorkedTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
double hoursWorked = 0.0;
for (Employee* employee in [value valueForKey: #"employees"])
{
NSNumber* hoursWorkedNumber = employee.hoursWorked;
hoursWorked += hoursWorkedNumber ? [hoursWorkedNumber doubleValue] : 0.0;
}
retVal = [NSNumber numberWithDouble: hoursWorked];
}
return retVal;
}
#end
#implementation MyNumberOfRacesTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
NSMutableSet* raceSet = [NSMutableSet set];
for (Employee* employee in [value valueForKey: #"employees"])
{
id raceVal = employee.race;
if (raceVal)
[raceSet addObject: raceVal];
}
retVal = [NSNumber numberWithUnsignedInteger: raceSet.count];
}
return retVal;
}
#end
Then, just bind those TableColumns to ArrayController.arrangedObjects and plug in the appropriate value transformer subclass. Now, you won't be able to edit those values, but what would it mean to edit an aggregate value anyway?
Hope that helps. I've used this approach a bunch, and it sure beats giving up on bindings.

Resources