I'm creating a phony instance of an entity beep. It has a required field of type picklist called pickaboo. First I omitted it but then the application started to lament throwing error messages at me due to some business logic demanding all the newly created instances of beep to have that field assigned.
Entity entity = new Entity { LogicalName = "beep" };
Guid guid = proxy.Create(entity);
proxy.Delete("beep", guid);
I don't give a rodent's tail section about that demand because right after I've created the instance, I'm removing it. However CRM gives a huge rodent and doesn't let me do my magic. So, I went clever and added an attribute for the missing attribute.
OptionSetValue option = new OptionSetValue(0);
Entity entity = new Entity { LogicalName = "beep" };
entity.Attributes.Add("pickaboo", option);
Guid guid = proxy.Create(entity);
proxy.Delete("beep", guid);
Of course, it didn't work because zero isn't a valid value. Apparently, CRM adds a hash number based on the solution so the actual "zero" has the numeric value like "846000000000", the actual "one" has "846000000001" etc.
How can I obtain that value programmatically?
Right now I have an ugly workaround obtaining all the beeps and getting the value from the first of them. Don't even get me started on how much sleep I'm loosing knowing how embarrassing it looks, would anybody take time to give me some feed-back. :(
You've got two options.
You can use the CrmSrvcUtil to generate your OptionSetValues as enums... This would create a pickaboo enum that you could then reference in your code
entity.Attributes.Add("pickaboo", new OptionSetValue((int)pickaboo.YourEnumValue);
You could also use the RetrieveOptionSetRequest message to get a list of all values for the particular option set you're interested in. See this SO question
Since I know that all CRM programmers are lazy pigs (own experience, haha), I know that you'd prefer a short and comprehensive solution. I realize that you're looking for a quick access to just a single valid value. If I'm mistaken, stop reading - use the suggestion of #Daryl - he's got a good answer for ya.
If I'm right, though, use this code to get the first valid option value (provided it exists). Just in case, remember to surround it with try/catch so if you misspell or such, you won't end up scratching your head.
RetrieveAttributeRequest request = new RetrieveAttributeRequest
{
EntityLogicalName = "beep",
LogicalName = "pickaboo",
RetrieveAsIfPublished = true
};
RetrieveAttributeResponse response
= proxy.Execute(request) as RetrieveAttributeResponse;
PicklistAttributeMetadata metaData
= response.AttributeMetadata as PicklistAttributeMetadata;
OptionSetValue option
= new OptionSetValue(metaData.OptionSet.Options[0].Value ?? -1);
NB - I'm assuming that you've got a working connection via a proxy called proxy.
Set a try/catch around the whole code, just in case out of bound exception occurs.
Make sure to handle the option of -1, since Value returned is a nullable integer.
This question has inspired me to blog.
If you have a look at my post below its got a function that can be used to find the int values of picklists (global and local optionsets), statecode, statuscode and the boolean (two option) fields.
CRM 2011 Programatically Finding the Values of Picklists, Optionsets, Statecode, Statuscode and Boolean (Two Options)
Related
When I'm creating new entities, I'm able to use the unit of work pattern and create parent and children at the same time while EF handles the relationships and either saves the whole result or rolls it back.
When I'm doing an update via DbSet<Xyz>.Update(xyz), and xyz has been updated such that navigation property (Apple) is now a new entity (newApple), I can't find any way to establish this relationship. If I manually call DbSet<Apple>.Add(newApple), the new apple is added to the database. EF just didn't do anything with the fact that I've set xyz.Apple = newApple, and I'd expect it to be able to based on how it handles creates. I also tried setting xyz.AppleId = default, but that just sets it to null if there is a value and doesn't do anything if there wasn't one.
What am I missing?
note:
Yes, I'm pulling the Apple property. That doesn't seem to make a difference.
I have tried setting the specific property's IsModified value to true as well. That is actually what I originally hoped to do as I usually only want to update a few fields at a time. When that didn't work, I reverted back to using the Update method which also failed with the exact same issues (setting it to a default value if I cleared out the AppleId property and set xyz.Apple = new Apple).
I've disabled AutoDetectChanges. I didn't think to mention it because I've done this for the last 7 or so years. Not sure that this makes a difference because my goal is to only explicitly inform EF of what I want it to operate on (I know that's not the recommended approach).
edit: Here's an actual code block so I can beat the "didn't try hard enough" accusations after staying up till 5am trying to make this work :)
// this just sets the tree's apple id to the default value (null or 0) and adds the new apple to the db
var tree = db.Trees.Include(x => x.Apple).FirstOrDefault();
var newApple = new Apple();
db.Apples.Add(newApple);
tree.AppleId = default;
tree.Apple = newApple;
db.Trees.Update(tree);
db.SaveChanges();
// this adds the new apple to the db and doesn't modify the tree at all
var tree = db.Trees.Include(x => x.Apple).FirstOrDefault();
var newApple = new Apple();
db.Apples.Add(newApple);
tree.AppleId = default;
tree.Apple = newApple;
db.Entry(entity).Navigation(nameof(Apple)).IsModified = true;
// note: I've also tried setting AppleId property to IsModified just in case, but that will null it out. Without that, it doesn't change at all
db.SaveChanges();
I'm using v2.0 of the API via the C# dll. But this problem also happens when I pass a Query String to the v2.0 API via https://rally1.rallydev.com/slm/doc/webservice/
I'm querying at the Artifact level because I need both Defects and Stories. I tried to see what kind of query string the Rally front end is using, and it passes custom fields and built-in fields to the artifact query. I am doing the same thing, but am not finding any luck getting it to work.
I need to be able to filter out the released items from my query. Furthermore, I also need to sort by the custom c_ReleaseType field as well as the built-in DragAndDropRank field. I'm guessing this is a problem because those built-in fields are not actually on the Artifact object, but why would the custom fields work? They're not on the Artifact object either. It might just be a problem I'm not able to guess at hidden in the API. If I can query these objects based on custom fields, I would expect the ability would exist to query them by built-in fields as well, even if those fields don't exist on the Ancestor object.
For the sake of the example, I am leaving out a bunch of the setup code... and only leaving in the code that causes the issues.
var request = new Request("Artifact");
request.Order = "DragAndDropRank";
//"Could not read: could not read all instances of class com.f4tech.slm.domain.Artifact"
When I comment the Order by DragAndDropRank line, it works.
var request = new Request("Artifact");
request.Query = (new Query("c_SomeCustomField", Query.Operator.Equals, "somevalue").
And(new Query("Release", Query.Operator.Equals, "null")));
//"Could not read: could not read all instances of class com.f4tech.slm.domain.Artifact"
When I take the Release part out of the query, it works.
var request = new Request("Artifact");
request.Query = (((new Query("TypeDefOid", Query.Operator.Equals, "someID").
And(new Query("c_SomeCustomField", Query.Operator.Equals, "somevalue"))).
And(new Query("DirectChildrenCount", Query.Operator.Equals, "0"))));
//"Could not read: could not read all instances of class com.f4tech.slm.domain.Artifact"
When I take the DirectChildrenCount part out of the query, it works.
Here's an example of the problem demonstrated by an API call.
https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(c_KanbanState%20%3D%20%22Backlog%22)&order=DragAndDropRank&start=1&pagesize=20
When I remove the Order by DragAndDropRank querystring, it works.
I think most of your trouble is due to the fact that in order to use the Artifact endpoint you need to specify a types parameter so it knows which artifact sub classes to include.
Simply adding that to your example WSAPI query above causes it to return successfully:
https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(c_KanbanState = "Backlog")&order=DragAndDropRank&start=1&pagesize=20&types=hierarchicalrequirement,defect
However I'm not tally sure if the C# API allows you to encode additional custom parameters onto the request...
Your question already contains the answer.
UserStory (HierarchicalRequirement in WS API) and Defect inherit some of their fields from Artifact, e.g. FormattedID, Name, Description, LastUpdateDate, etc. You may use those fields in the context of Artifact type.
The fields that you are trying to access on Artifact object do not exist on it. They exist on a child level, e.g. DragAndDropRank, Release, Iteration. It is not possible to use those fields in the context of Artifact type.
Parent objects don't have access to attributes specific to child object.
Artifact is an abstract type.
If you need to filter by Release, you need to make two separate requests - one for stories, the other for defects.
Lets say my c# model updated while correspondent collection still contains old documents, I want old and new documents to coexist in the collection, while using only new version of c# model to read them. I wish no inheritance is used if possible. So I wonder which of this issues are solvable and how:
there is a new property in c# model which does not present in database. I think it never should be an issue, Mongo knows nothing about it, and it will be initialized with default value. The only issue here is to initialize it with particular value for all old documents, anybody knows how?
one of property has gone from model. I want MongoDb to find out there is no more property in c# class to map the field of old document to, and to ignore it instead of crashing. This scenario probably sounds a bit strange as it would mean some garbage left in database, but anyway, is the behavior possible to implement/configure?
type if changed, new type is convertible to old one, like integer->string. Is there any way to configure mapping for old docs?
I can consider using inheritance for second case if it is not solvable otherwise
Most of the answers to your questions are found here.
BsonDefaultValue("abc") attribute on properties to handle values not present in the database, and to give them a default value upon deserialization
BsonIgnoreExtraElements attribute on the class to ignore extra elements found during deserialization (to avoid the exception)
A custom serializer is required to handle if the type of a member is changed, or you need to write an upgrade script to fix the data. It would probably be easier to leave the int on load, and save to a string as needed. (That will mean that you'll need a new property name for the string version of the property.)
I am trying to figure out how to keep an object useable between client sessions in DB4O. From what I understand, once a client session is closed, the object no longer resides in any cache and despite the fact that I have a valid UUID, I cannot call Store on it without causing a duplicate to be inserted. I searched for a way to manually re-add it to the cache but there is no such mechanism. Re-retrieving it will force me to copy over all the values from the now useless object.
Here's the above paragraph in code:
Person person = new Person() { FirstName = "Howdoyu", LastName = "Du" };
Db4oUUID uuid;
// Store the new person in one session
using (IObjectContainer client = server.OpenClient())
{
client.Store(person);
uuid = client.Ext().GetObjectInfo(person).GetUUID();
}
// Guy changed his name, it happens
person.FirstName = "Charlie";
using (var client = server.OpenClient())
{
// TODO: MISSING SOME WAY TO RE-USE UUID HERE
client.Store(person); // will create a new person, named charlie, instead of changing Mr. Du's first name
}
The latest version of Eloquera supports these scenarios, either through an [ID] attribute or via Store(uid, object).
Any thoughts?
This functionality is indeed missing in db4o =(. That makes db4o very difficult to use in many scenarios.
You basically have to write your own reattach method by coping all attributes over. Maybe a library like Automapper can help, but in the end you have to do it yourself.
Another question is if you really want to use the db4o UUIDs to identify an object. db4o UUIDs are huge and not a well known type. I personally would prefer regular .NET GUIDs.
By the way: There's the db4o .Bind() method, which binds a object to an existing id. However it hardly does what you really want. I guess that you want to store changes made to an object. Bind basically replaces the object and breaks the object graph. For example if you have a partially loaded objects and then bind it, you loose references to objects. So .Bind is not usable.
Okay, Gamlor's response about the db4o IExtContainer.Bind() method pointed me to the solution. Please note that this solution is only valid in very specific situations where access to the DB is tightly controlled, and no external queries can retrieve object instances.
Warning: This solution is dangerous. It can fill your database with all kinds of duplicates and junk objects, because it replaces the object and doesn't update its values, therefore breaking any references to it. Click here for a complete explanation.
UPDATE: Even in tightly controlled scenarios, this can cause endless headaches (like the one I'm having now) for anything other than a flat object with value type properties only (string, int, etc.). Unless you can design your code to retrieve, edit and save objects in a single db4o connection, then I recommend not to use db4o at all.
Person person = new Person() { FirstName = "Charles", LastName = "The Second" };
Db4oUUID uuid;
using (IObjectContainer client = server.OpenClient())
{
// Store the new object for the first time
client.Store(person);
// Keep the UUID for later use
uuid = client.Ext().GetObjectInfo(person).GetUUID();
}
// Guy changed his name, it happens
person.FirstName = "Lil' Charlie";
using (var client = server.OpenClient())
{
// Get a reference only (not data) to the stored object (server round trip, but lightweight)
Person inactiveReference = (Person) client.Ext().GetByUUID(uuid);
// Get the temp ID for this object within this client session
long tempID = client.Ext().GetID(inactiveReference);
// Replace the object the temp ID points to
client.Ext().Bind(person, tempID);
// Replace the stored object
client.Store(person);
}
First off, I am new to programming (especially with C#) and thanks for your help.
I have a static web form with about 150 form objects (most checkboxes). I decided to go 1 record per form submission in the sql db. So, for example, question X has a choice of 5 checkboxes. Each of these 5 checkboxes has a column in the db.
I have the post page complete(working) and am building an edit page where I load the record and then populate the form.
How I am doing this is by passing a stored proc the id and then putting all the returned column values into the according object properties, then setting the asp control object to them.
An example of setting the asp controls to the selected value:
questionX.Items[0].Selected = selectedForm.questionX0
questionX.Items[1].Selected = selectedForm.questionX1
questionX.Items[2].Selected = selectedForm.questionX2
As you see, this is very tiresome since there are over 150 of these to do. Also, I just found out if the response is NULL then I get the error that it cant be converted to a string. So, I have added this line of code to get past it:
This is the part where I am populating the returned column values into the object properties (entity is the object):
if (!String.IsNullOrEmpty((string)reader["questionX0"].ToString()))
{entity.patientUnderMdTreatment = (string)reader["questionX0"];}
So, instead of having to add this if then statement 150+ times. There must be a way to do this more efficiently.
First of all, it seems that you are using string.IsNullOrEmpty(value), but this won’t check for the special DBNull value that is returned from databases when the data is null. You should use something more akin to value is DBNull.
The rest of your problem sounds complex, so please don’t be put off if my answer is complex too. Personally I would use custom attributes:
Declare a custom attribute
The following is a skeleton to give you the idea. You may want to use the “Attribute” code snippet in Visual Studio to find out more about how to declare these.
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public sealed class QuestionColumnAttribute : Attribute
{
public string ColumnName { get; private set; }
public QuestionColumnAttribute(string columnName)
{
ColumnName = columnName;
}
}
Use the custom attribute in the entity class
Where you declare your entity class, add this custom attribute to every field, for example where patientUnderMdTreatment is declared:
[QuestionColumn("questionX0")]
public string patientUnderMdTreatment;
Iterate over the fields
Instead of iterating over the columns in the reader, iterate over the fields. For each field that has a QuestionColumnAttribute on it, get the relevant column from the reader:
foreach (var field in entity.GetType().GetFields())
{
var attributes = field.GetCustomAttributes(typeof(QuestionColumnAttribute), true);
if (attributes.Length == 0)
continue;
object value = reader[attributes[0].ColumnName];
if (!(value is DBNull))
field.SetValue(entity, value.ToString());
}
For the first part of your question where you set the ASP controls, you can use a similar strategy iterating over the fields of selectedForm, and this is probably simpler because you don’t need a custom attribute — just take only the fields whose name starts with “questionX”.
this is a quick & easy way of doing it.. there are some suggestions to investigate LINQ, and I'd go with those first.
for (int i = 0; i < 150; i++)
{
if (!String.IsNullOrEmpty((string)reader["questionX" + i.ToString()].ToString()))
{entity.patientUnderMdTreatment = (string)reader["questionX" + i.ToString()];}
}
... though this wouldn't be any good with the
questionX.Items[0].Selected = selectedForm.questionX0
questionX.Items[1].Selected = selectedForm.questionX1
questionX.Items[2].Selected = selectedForm.questionX2
lines
so I hear two questions:
- how to deal with null coming from IDataReader?
- how to deal with multiple fields?
Lets start with simple one. Define yourself a helper method:
public static T IsDbNull<T>(object value, T defaultValue)
{
return (T)(value is DBNull ? defaultValue : value);
}
then use it:
entity.patientUnderMdTreatment = IsDbNull<string>(reader["question"], null);
Now how to map entity fields to the form? Well that really is up to you. You can either hardcode it or use reflection. The difference of runtime mapping vs compile-time is likely to be completely irrelevant for your case.
It helps if your form fields have identical names to ones in the DB, so you don't have to do name mapping on top of that (as in Timwi's post), but in the end you'll likely find out that you have to do validation/normalization on many of them anyway at which point hardcoding is really what you need, since there isn't a way to dynamically generate logic according to the changing spec. It doesn't matter if you'll have to rename 150 db fields or attach 150 attributes - in the end it is always a O(n) solution where n is number of fields.
I am still a little unsure why do you need to read data back. If you need to preserve user's input on form reload (due to validation error?) wouldn't it be easier/better to reload them from the request? Also are entity and selectedForm the same object type? I assume its not a db entity (otherwise why use reader at all?).
Its possible that there are some shortcuts you may take, but I am having hard time following what are you reading and writing and when.
I recommend using the NullableDataReader. It eliminates the issue.