tag:blogger.com,1999:blog-69500322024-03-08T10:21:16.916-08:00Inside Microsoft CRMAn insider's view of Microsoft CRM. Rants, raves, and some advanced (read that: unsupported) tricks.Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comBlogger16125tag:blogger.com,1999:blog-6950032.post-1094662730561411822004-09-08T09:56:00.000-07:002004-09-08T09:58:50.560-07:00Moving onWell, sort of moving on. I'm moving my blog over to <a href="http://blogs.msdn.com/mikemill">MSDN</a> to be a bit closer to the rest of the development team. I'm not abandoning the Inside MS-CRM "column" though, I just thought it would be a little easier if I was closer to "home". So, join me over at MSDN and watch the <a href="http://blogs.msdn.com/mikemill/Rss.aspx">RSS</a> feed for more entries.Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1094659084114505892004-09-08T08:53:00.000-07:002004-09-24T07:45:35.860-07:00I'm movingI've made the decision to move over to the official Microsoft world of bloggers. For now I'm going to assume that I can pretty much say whatever I want over <a href="http://blogs.msdn.com/mikemill">there</a>, but if things get weird I'm coming back.
<br />
<br />Over time I'll try to figure out how to move my past posts over so they're not lost in the shuffle, but for now it'll look like I'm starting over. The added benefit is that I'll be closer to <a href="http://blogs.msdn.com/jasonhun">Jason</a> and <a href="http://blogs.msdn.com/aaronel">Aaron</a>, two other members of the MS-CRM team.
<br />
<br />I know I have a backlog of articles to write, but don't worry, I <strong>am</strong> writing them.
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1094576313273204172004-09-07T08:00:00.000-07:002004-09-07T09:58:33.273-07:00Where's MjI'm still around, but I've been kinda busy. It's funny, I set out to write this because I had a ton of stuff on the top of my head that I wanted to unload on the MS-CRM community. The problem is that there's just so much stuff and so little time that I've found it harder to prioritize the list. I'm getting closer though and will start writing more over the next few weeks while we're ramping to alpha for V2 (no, I can't give you a date, and no I won't give you a feature list, sorry). I've received a handful of great suggestions both publicly and privately, and I've been scouring the newsgroups for more of those nasty requests that keep popping up.
<br />
<br />As I mentioned previously I've finally got an instance of 1.2 installed and running, but I had to break the rules to get it to work. The biggest rule I broke, and I still haven't decided if I want to get around it or not, is that I installed to a named SQL instance. While the core product just doesn't care the replication story to the Outlook client has all kinds of issues with named instances. But, the good thing is that I've got an off-campus installation running and I can start breaking things in ways that should help the community get on with configuring and supporting the 1.2 product while we busily work on the next release.
<br />
<br />So, hang in there, I'll be writing more soon. Keep the ideas coming in too because they really help me prioritize what information is most useful. There's a ton of stuff to write about and I'm looking to you folks to guide that work. Look for a more complete serialization example, a forms XML example that adds a custom tab, and a more detailed callout sample.Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1093535917909067502004-08-26T08:43:00.000-07:002004-08-26T09:02:21.760-07:00More fun with XSD<a href="http://www.blogger.com/profile/4350748">Torsten Schuster</a> asked about <a href="http://inside-mscrm.blogspot.com/2004/05/why-ms-crm-soap-interfaces-suck.html">the XML serializer</a>, but unfortunately it was pretty buried. I saw it, but I'm not sure anyone else did. It sounds to me like another case of old CRM XSD and the VS XSD tool, but I'm not really sure.
<br />
<br />From what I gather the platform and APIs are behaving correctly. For example, on a sales order retrieve, the following column set can be passed in.
<br />
<br /><code>
<br /><columnset>
<br /><column>salesorderid</column>
<br /><column>ordernumber</column>
<br /><column>name</column>
<br /><column>customerid</column>
<br /><column>customfield</column>
<br /></columnset>
<br /></code>
<br />
<br />Which should (and in this case, does) result in the following XML.
<br />
<br /><code>
<br /><salesorder>
<br /><salesorderid>{CA033A28-25FD-47E4-A98A-BDDF134B11F3}</salesorderid>
<br /><ordernumber>ORD-01001-54ZQ8T</ordernumber>
<br /><name>Test</name>
<br /><customerid name="STM Ltd." dsc="0" type="1">{3E8CDBD0-FD2E-409D-BB8D-39870AB689C1}</customerid>
<br /><customfield>stm_4711</customfield>
<br /></salesorder>
<br /></code>
<br />
<br />The question is about the "customerid" element and why the serializer isn't pulling it into the hydrated object. I can only guess, but it sounds like the XSD doesn't have a correct definition for "salesorder" nor does it have a definition for "customerid".
<br />
<br />Ideally, the sales order XSD should have a "customerid" element of type "tns:customerType" which references this XSD
<br />
<br /><code>
<br /><xsd:complexType name="customerType">
<br /><xsd:simpleContent>
<br /><xsd:extension base="xsd:string">
<br /><xsd:attribute name="name" type="xsd:string" />
<br /><xsd:attribute name="type" type="xsd:int" use="required" />
<br /><xsd:attribute name="dsc" type="xsd:int" />
<br /></xsd:extension>
<br /></xsd:simpleContent>
<br /></xsd:complexType>
<br /></code>
<br />
<br />I can't guarantee that the VS XSD tool will cope well with the XSD that I talked about <a href="http://inside-mscrm.blogspot.com/2004/06/dealing-with-broken-xsd.html">earlier</a>. Although making the XSD tool deal with it is fairly trivial, I still prefer the other code generator.
<br />
<br />Like I said a while back, I can't support any of this stuff, but I can lend guidance on occasion. Hopefully this information is enough to get things moving again. If not, well, maybe someone else can chime in.Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1092156862996715652004-08-10T09:19:00.000-07:002004-08-10T09:54:22.996-07:00Entity size is always a problemRunning into the customization ceiling when adding attributes? I feel your pain. I really do. The team down the hall from me is working quite hard on making some of this pain go away, and they've done a bunch of work in the query processor layer in the platform. There's a reason the limitation exists in V1.x and there's a reason it wasn't "fixed" earlier.
<br />
<br />The original COLA (contact, opportunity, lead, and account) definitions were quite small and left a ton of room for extensions. One of the things we looked at was to allow customizations of the type where one could store everything one wanted in an XML document in the database. There were way too many problems with that approach (although there are some great upsides too). Simply put, any search and display is going to be a problem with the property bag approach. There really aren't any great mechanisms for telling <fetch> about the semantics of an attribute. It knows all about the entities, attributes, and relationships, but that's where its knowledge stops. The application, and most other display patterns (except reporting) would work fairly well because it's all just XML and XSLT, and writing another XPATH expression to reach into the bag and pull out the rabbit is a well understood problem.
<br />
<br />The second approach was to allow physical attributes to be added to entities in the form of database columns. There are some problems with this as well, particularly around name collisions and upgrade scenarios, but none of those couldn't be overcome with some decent engineering work.
<br />
<br />A little history lesson my help. This is an excerpt from a whitepaper I wrote when we first started looking at how to create an extensible product. This is really ancient history at this point so there's really no reason I can think of to not share it.
<br />
<br /><blockquote>
<br />Several proposals are on the table to allow application developers to customize the storage characteristics (the tables and fields).
<br />
<br />1) The approach taken for ClearLead 1.x (bCentral Customer Manager). Each interesting object (business, user, prospect, event) has a set of developer-defined named properties. This approach was an attempt at solving the problems inherent in approach 3. However, it quickly caused two severe problems. First, performance was horrible, each query required multiple outer joins to gather all the detail-level information. Secondly, the data stored rapidly exploded. Where it was be possible to store a single inbound email event record in a single row using an ntext blob, the CL model took the approach that all large data be broken into 2000 character chunks and stored individually. This required that any time this information was read or written, the data had to be reconstructed.
<br />
<br />2) Expose a single, opaque, application-specific blob field on every interesting object. This has some appeal since it leaves all the interpretation to the application and puts the burden on the developer to manage and render this information as necessary. The drawback here is that the blob isn't quickly searchable and can't be indexed (full-text indexing is an option, but isn't quite mature enough to be relied upon).
<br />
<br />Another drawback with this format is that simple queries against the data are difficult to construct and very expensive to run. For example, how would a query be constructed which found all contacts who brought a cat into a vet clinic in May and were serviced by Dr. Smothers. If this data is 'stuffed' into a single Xml blob, the format isn't controllable by the platform, so a generic query like this wouldn't be possible to construct.
<br />
<br />A secondary problem with this approach is the <em>opaqueness</em> of the data, neither the application nor the platform have any knowledge of the document structure. The platform would need to be written with the document structure in mind to make any reasonable use of the data, in which case the extensibility mechanism is defeated. The application on the other hand may have knowledge of the structure, but may not have any guarantee on its structure. That is, the structure may need to be interpreted differently for each individual object. [If the application were to force a fixed document structure on each class of objects, that would reduce some of the problems.]
<br />
<br />3) Supply a fixed number of customizable fields per object - say 5 or 10 <em>sql_variant</em> fields. The problem with this approach is that it breaks the <em>zero</em>, <em>one</em>, <em>infinity</em> rule. As soon as we present <em>n</em> fields to the developer they'd ask for <em>n + 1</em>. If we told them they had 255 UNICODE characters per field, they'd ask for 256. We can get around the second part of this problem by implementing the extra fields as a sql_variant, however this limits the field's usefulness by changing the meaning of the field in large searches.
<br />
<br />4) Use a metadata-driven model and "hide" the physical model from the application and platform developers. The appeal here is that each developer can actually think about the problem at hand and customize the object definition to meet their needs.
<br /></blockquote>
<br />
<br />For the longest time we (hell, I'll take the blame on this, this was my idea) were under the impression that spreading things across tables would be a way around the problem. That's one of the reasons that addresses are bound into the entities (although I really dislike that design, I have to say it does make sense at times). The issue is that SQL needs to create temp tables to hold the 'inserted' table data for the update triggers. While most people would never write all of the data to a record at once it is possible, and in those situations things will just break.
<br />
<br />The way we get around this is to remove the updatable views and triggers altogether and use the metadata to construct the cross-table queries. Until that happens there's really no way around the 8k limitation (at least not in the supported world).
<br />
<br />Looking back on this now I think I'd take either option 1 or 2 above. If I were to take the XML blob approach I'd likely work on an extension pattern that forced the extension author to describe the extension in terms of metadata (and in terms of an XSD) so the tools which manipulate metadata for presentation, query, and update operations would "know" how to interpret the data. It still doesn't solve the reporting problems and it likely won't until a reporting rendering engine can be built that knows about XML as source data and uses XPATH as the layout. There would still be problems with query, particularly around aggregate functions and ordering (what if someone wants to group on a element in the extension and order by another element - they're not columns to SQL so you'd need to lift that functionality out of the database where it should be and into an independent query processing layer...)
<br />
<br />
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1091115805596212002004-07-29T08:16:00.000-07:002004-07-29T08:43:25.596-07:00Third time's a charmWell, I'm back for a while. I just finished up my M2 features for our next release. Time to hand it off to the Test team for a while and let them beat on it. But, more on the V2 stuff later, after alpha, or beta, or something where we've made the feature set public. I don't want to say anything for fear of getting everyone excited about something and then having it cut at the last minute.
<br />
<br />But, anyway, what's the title got to do with that? I've been <em>trying</em> to install the 1.2 MSDN release. Normally that wouldn't be much of a problem, I've probably installed it at least a few dozen times (and now that I'm focused on dev work for V2 I'm installing V2 sometimes three times a day - and yes, the new setup is incredibly cool and easier to use). This time was different. I was trying to install on a VPC running Windows 2003 Server. Nothing particularly special about that VPC, it's a domain member and the database server will be running somewhere outside of a VPC. What was different was that I had installed the Whidbey community preview which dropped the 2.0 .NET Framework and I had forgotten to tell Windows that it was going to be just fine if ASP.NET was enabled.
<br />
<br />But that's just the start of the problems...
<br />
<br />The first time I ran the install I went through the usual stuff. Fill out the page, click next, read the complaint about Windows feature X not being installed, install feature X, click next, and try again. It didn't take long to work through that since I knew what to expect after the first complaint. What I didn't expect was the flat out failure while creating the databases. Since I wrote most of the V1 database install code I knew where it was when it failed, but I sure couldn't figure out why from the message. I mean, come on, the step it was working through was pretty simple. Turns out that when setup is rebuilding the foreign key constraints in the database it simply walks all of the ones it finds in the current database...
<br />
<br />Well, I turned on verbose setup logging (/l*v <em>logfile</em> if you haven't seen it yet) and tried again. Sure enough, it was failing at step '03 fknfr'. I stared at it for a while, cleaned everything up, fired up SQL profiler, and tried my third install of the night. Profiler showed that, yes, it <em>was</em> failing there. So I grabbed the script off the installation CD and tried to run it manually (but not before turning off the actual exec calls and replacing them with print) so I could see why things were failing. Well, that was a weird experience. I ran the script and the table names that were scrolling by were only vaguely related to the ones I would normally find in CRM. Oh, don't get me wrong, the expected tables were all there, but so were a bunch of other tables that I wasn't really expecting. To say the least I was getting nervous because the table names shown were actually tables from another very unrelated database on the same server and I started thinking that somehow setup had changed to an incorrect database context and trashed something. To make this a little less painful let's just say I spent quite a while staring at things until I remembered that my 'model' database on that server was actually set up to create a completely different type of table structure for each new database. Since CRM creates three databases during setup, it got seeded with three complete databases before it even started installing it's own stuff.
<br />
<br />The moral of the story is simple - make sure you have an empty 'model' database or very bad things might happen.
<br />
<br />By the way, even after switching database servers, I was still unsuccessful installing the product. Sure, things seemed to be working well, but then I ran into some weirdness with importing the default data (aka roles, privileges, and other goodies). Even after what seemed like a complete install things didn't work, which I'm attributing to the Whidbey bits cluttering up that server. I'm also assuming that switching from a 'normal' SQL installation to one running in a named instance is causing me some grief.
<br />
<br />As I'm recounting this fun I'm watching a new Server 2003 installation happen in the background. This time I'm going to make sure all the stuff that's needed is installed when I start and I won't install anything else. Guess I should have read the IG...
<br />
<br />
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1089302918510894412004-07-08T09:04:00.000-07:002004-07-08T09:08:38.516-07:00Code generatorsI opened my inbox this morning to find a <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=89e6b1e5-f66c-4a4d-933b-46222bb01eb0&displaylang=en">link</a> to a brand-new version of the XSD-to-code generator that I've been talking about. Just wanted to share the good news. There was a strong feeling that taking a dependency on a code generator from an outside team would be a bad thing because a) it wasn't built "here" and b) because it was a code generator. Well, I guess none of that matters now...
<br />
<br />If you're doing any work with the platform XSD (not the as-shipped ones, we know they're not friendly), then grab this tool and give it a try. I use it every day in unit test development - no more building XML strings for this developer.
<br />
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1088617787915916562004-06-30T10:12:00.000-07:002004-06-30T10:49:47.916-07:00Programming modelsSome days I really wonder what's right for the ISV / VAR community when it comes to programming models. I know what I'd like to see if I were writing code against MS-CRM. The problem is that I don't know what you want.
<br />
<br />We have this great infrastructure at MSFT that allows us to stay in touch with the community. It works well when the contacts are asked the right questions and when the PM knows what to listen for in an answer. It breaks down when the questions are too generic, too specific, or flat out wrong. That's why I don't really know what you guys want in a programming model.
<br />
<br />In MS-CRM there's only one way to go after the platform and that's using the SOAP proxy. Sure, it's possible to use the WSDL and generate client-side code directly, but it's a pain because of name collisions and all the other goo that happens when you add a web reference. There are also the well-known problems with the interfaces as they stand today which I've <a href="http://inside-mscrm.blogspot.com/2004/05/why-ms-crm-soap-interfaces-suck.html">commented on </a> before.
<br />
<br />We've spent a lot of time talking about the ideal interfaces, programming model, and interaction model for the next releases. The problem is that we're talking about it but your voices aren't being represented anywhere. So, I'd like to see if 1) anyone's interested, 2) anyone's listening and 3) if anyone wants to comment.
<br />
<br />Option 1 - we leave things the way they are. I won't go into this because everyone understands how things work. You get to keep Intellisense on the API signatures but everything else is a string.
<br />
<br />Option 2 - clean up the interfaces by choosing a better naming convention, getting rid of the XML strings, and removing some of the 'extra' parameters. You get IntelliSense on the API signatures here to, but no more strings. (But no IntelliSense on the entities themselves). This one saving grace here is that the "objects" are really extensible. Because they're nothing more than a property bag it's very easy to add new attributes without breaking things (this is one of the reasons we have XML strings today).
<br />
<br />Option 3 - get really radical and move to a type-safe, SOA-based model with only 6 methods but with a pile of messages. You get full IntelliSense here including type-safe entities and interfaces. The price you pay is dealing with the extensibility problem (i.e. what happens to your entities when another customer modifies the entity schema - this might be a recompilation or a property bag over the extra attributes).
<br />
<br />Option 2 might look a lot like the interfaces do today with the exception that they'll take an "object" instead of XML. This object simply wraps a property bag of strings. You'd get run-time type checking like you do today but with the expense of either having a client-side copy of the metadata or by round-tripping to the server.
<br />
<br />The code might be something like this (after adding a reference to the metadata, entity, and service assemblies):
<br />
<br /><code>
<br />AccountWebService accountService = new AccountWebService();
<br />Account account = new Account(); // or BusinessEntity
<br />
<br />account["ownerid"] = ownerid;
<br />account["owneridtype"] = 8;
<br />account["name"] = 64;
<br />account["customertypecode"] = "21"; // is this a string?
<br />
<br />string accountId = accountService.Create(account);
<br />
<br />Principal p = new Principal();
<br />p.Type = sptUser;
<br />p.Id = someOtherUserId;
<br />
<br />uint rights = 1; // READ?
<br />
<br />accountService.GrantAccess(accountId, p, rights);
<br /></code>
<br />
<br />As you can see there's an account "object" on the client, but it's really just a name. The real class is BusinessEntity which is a thin wrapper around a NameValueCollection.
<br />
<br />The third option would look structurally the same, but there would be a few key differences. This is after adding a web reference to the CRMWebService (which would get the interfaces and schemas).
<br />
<br /><code>
<br />CRMWebService crmService = new CRMWebService();
<br />
<br />// get a new account instance with default values
<br />Account account = crmService.CreateNew(typeof(Account));
<br />
<br />account.name = "account name";
<br />account.customertypecode = 21; // this is an int
<br />
<br />Guid accountId = crmService.Save(account);
<br />
<br />GrantAccessMessage grantAccess = new GrantAccessMessage();
<br />grantAccess.Moniker.Type = typeof(Account);
<br />grantAccess.Moniker.Id = accountId;
<br />grantAccess.Principal.Type = sptUser;
<br />grantAccess.Principal.Id = otherUserId; // Guid
<br />grantAccess.GrantRead = true;
<br />
<br />crmService.Exectue(grantAccess);
<br /></code>
<br />
<br />I won't cover the V1.x flavor because I don't feel like typing &lt; and &gt; all over the place. That's just too much work.
<br />
<br />I'd like to hear from you which option is prefered (and option 1 is still an option). If there's another model you'd prefer, I'm listening. Just don't ask for DataSets everywhere, please.
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1087522698363230712004-06-17T18:07:00.000-07:002004-06-17T18:40:29.056-07:00Dealing with broken XSDOk, I've been dealing with this broken XSD issue for so long now that I just can't stand it. The platform has ways to give you back XSD, but it's not really XSD, and it's not really friendly about doing so. Given the current v1.x API, which aren't exactly friendly to deal with, and their insistence on using strings of XML for everything, I put this script together to start hiding the complexities. Now, one thing you'll notice is that this script doesn't generate XSD in a flavor that the VS .NET XSD.EXE tool likes (it's not the greatest either, but it does work usually).
<br />
<br />The cool thing is that there is a <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=de0cc46b-4dea-4786-9eb0-8733e41bf5a8&displaylang=en">great tool</a> available from the SD West Conference. This tool will happily eat the resulting XSD that this script generates and will create some very powerful client-side classes that should make everyone's life much easier. You'll still need to serialize the platform XML into a class, but that's pretty simple. The following C# snippit should do just fine (you'll have to grab some of the XML serialization references, IO, and Text, but that's left as an exercise for the reader).
<br />
<br />The third code snip is the SQL script you've been waiting for. I recommend always generating the whole pile, you'll end up with about 3,000 lines of XSD and about 30,000 lines of C# when you're done, but it's worth it. Remember though, as shown, this script will not generate XSD that the XSD.EXE tool likes. Don't ask me why, it just doesn't (and that goes for generating typed DataSets too). There are ways to make it work, but would you want to when XSDObjectGen does all the right things in terms of creating real platform XML and dealing well with minimal update sets? Oh yeah, the classes work well with the ColumnSetXml parameter on Retrieve methods. I've even created XSD that represents collections of platform entities and serialized those properly.
<br />
<br />As usual, nothing you've read here is remotely supported, and if you call support they'll likely have no idea what you're talking about. They might even ask why you're running SQL scripts in the metadata. <strong>I won't support this either.</strong> So don't ask. I'm making this available because it's something that needed to happen and never did. The bug with the bad XSD was found the day we RTM'd v1.0 and we never looked back (who'd ever want the schemas anyway, aren't XML strings self-describing...)
<br />
<br /><code>
<br />public static string ToString(object o)
<br />{
<br /> StringBuilder sb = new StringBuilder();
<br /> XmlSerializer serializer = new XmlSerializer(o.GetType());
<br /> XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
<br />
<br /> ns.Add("", "");
<br /> TextWriter writer = new StringWriter(sb);
<br /> serializer.Serialize(writer, o, ns);
<br />
<br /> return sb.ToString();
<br />}
<br />
<br />public static object ToObject(Type t, string s)
<br />{
<br /> XmlSerializer serializer = new XmlSerializer(t);
<br /> TextReader reader = new StringReader(s);
<br />
<br /> object o = null;
<br /> try {
<br /> o = serializer.Deserialize(reader);
<br /> } catch(Exception e) {
<br /> throw new Exception("Failed to convert XML to object", e);
<br /> }
<br /> return o;
<br />}
<br />
<br />void fooBar()
<br />{
<br /> // create an account object in "client-space" and set some properties
<br /> Microsoft.Crm.WebServices.account a1 = new Microsoft.Crm.WebServices.account();
<br /> a1.accountcategorycode.Value = 6;
<br /> a1.accountcategorycode.name = "Corporate";
<br />
<br /> a1.creditlimit.Value = 123456.0F;
<br /> a1.creditlimit.value = "$123,456.00";
<br />
<br /> a1.creditonhold.Value = true;
<br /> a1.creditonhold.name = "Yes";
<br />
<br /> a1.createdon.type = 8;
<br /> a1.createdon.Value = userAuth.UserId;
<br />
<br /> a1.name = "This is account 1";
<br /> a1.description = "This is my sample account....";
<br />
<br /> // turn it into XML
<br /> string xml1 = ToString(a1);
<br />
<br /> Microsoft.Crm.Platform.Proxy.CRMAccount accountService =
<br /> new Microsoft.Crm.Platform.Proxy.CRMAccount();
<br />
<br /> // stuff in into the platform and get the new one back
<br /> string xml2 = accountService.CreateAndRetrieve(userAuth, xml1);
<br />
<br /> // turn the new one into an object
<br /> Microsoft.Crm.WebServices.account a2 =
<br /> (Microsoft.Crm.WebServices.account)ToObject(typeof(Microsoft.Crm.WebServices.account), xml2);
<br /></code>
<br />
<br /><code>
<br />set nocount on
<br />
<br />declare @view table (
<br /> idvalue int identity,
<br /> value nvarchar(4000)
<br />)
<br />
<br />declare @attributeName nvarchar(50)
<br />declare @typeName nvarchar(50)
<br />declare @entityName nvarchar(50)
<br />
<br />declare @buildDate datetime
<br />declare @buildNumber nvarchar(20)
<br />
<br />select @buildDate = coalesce(BuildDate, getutcdate()),
<br /> @buildNumber = cast(coalesce(MajorVersion, 1) as nvarchar) + '.' + cast(coalesce(MinorVersion, 0) as nvarchar) + '.' + cast(coalesce(BuildNumber, 0) as nvarchar)
<br />from BuildVersion
<br />
<br />declare entityCursor cursor for
<br />select LogicalName
<br />from Entity
<br />where IsIntersect = 0
<br /> and IsSecurityIntersect = 0
<br /> and IsLookupTable = 0
<br /> and IsAssignment = 0
<br /> and LogicalName not like '%activity%'
<br /> and LogicalName != 'activitypointer'
<br />order by 1
<br />
<br />-- write the top-level schema tags and namespace information
<br />insert @view (value) values ('<?xml version="1.0" encoding="utf-8" ?>')
<br />
<br />insert @view (value) values ('<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"')
<br />insert @view (value) values (' targetNamespace="http://www.microsoft.com/mbs/crm/schemas/2004"')
<br />insert @view (value) values (' xmlns:tns="http://www.microsoft.com/mbs/crm/schemas/2004"')
<br />
<br />insert @view (value) values (' elementFormDefault="unqualified" ')
<br />insert @view (value) values (' attributeFormDefault="unqualified" >')
<br />insert @view (value) values ('')
<br />insert @view (value) values (' <xsd:import namespace="http://www.w3.org/XML/1998/namespace"')
<br />insert @view (value) values (' schemaLocation="http://www.w3.org/2001/xml.xsd" />')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values ('')
<br />insert @view (value) values (' <xsd:annotation>')
<br />insert @view (value) values (' <xsd:documentation xml:lang="en">')
<br />insert @view (value) values (' Copyright (c) ' + cast(year(getutcdate()) as nvarchar) + ' Microsoft Corp. All rights reserved.')
<br />insert @view (value) values (' DO NOT EDIT - Schema automatically generated ')
<br />insert @view (value) values (' Built on : ' + cast(@buildDate as nvarchar))
<br />insert @view (value) values (' Version : ' + cast(@buildNumber as nvarchar))
<br />insert @view (value) values (' </xsd:documentation>')
<br />insert @view (value) values (' </xsd:annotation>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:simpleType name="uniqueidentifier">')
<br />insert @view (value) values (' <xsd:restriction base="xsd:string">')
<br />insert @view (value) values (' <xsd:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" /> ')
<br />insert @view (value) values (' </xsd:restriction>')
<br />insert @view (value) values (' </xsd:simpleType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="keyType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:string">')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="principalType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:string">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
<br />insert @view (value) values (' <xsd:attribute name="type" type="xsd:int" use="required" />')
<br />insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:int" />')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="ownerType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:string">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
<br />insert @view (value) values (' <xsd:attribute name="type" type="xsd:int" use="required" />')
<br />insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:int" />')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="customerType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:string">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
<br />insert @view (value) values (' <xsd:attribute name="type" type="xsd:int" use="required" />')
<br />insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:int" />')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="lookupType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:string">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string" />')
<br />insert @view (value) values (' <xsd:attribute name="type" type="xsd:int" use="required" />')
<br />insert @view (value) values (' <xsd:attribute name="dsc" type="xsd:int" />')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="picklistType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:int">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="booleanType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:boolean">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="moneyType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:float">')
<br />insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="numberType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:int">')
<br />insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="decimalType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:float">')
<br />insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="floatType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:float">')
<br />insert @view (value) values (' <xsd:attribute name="value" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="dateTimeType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:dateTime">')
<br />insert @view (value) values (' <xsd:attribute name="date" type="xsd:string"/>')
<br />insert @view (value) values (' <xsd:attribute name="time" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="statusType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:int">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />insert @view (value) values (' <xsd:complexType name="stateType">')
<br />insert @view (value) values (' <xsd:simpleContent>')
<br />insert @view (value) values (' <xsd:extension base="xsd:int">')
<br />insert @view (value) values (' <xsd:attribute name="name" type="xsd:string"/>')
<br />insert @view (value) values (' </xsd:extension>')
<br />insert @view (value) values (' </xsd:simpleContent>')
<br />insert @view (value) values (' </xsd:complexType>')
<br />insert @view (value) values ('')
<br />
<br />-- open the cursor
<br />open entityCursor
<br />fetch entityCursor into @entityName
<br />
<br />while @@fetch_status = 0
<br />begin
<br />
<br /> insert @view (value) values (' <xsd:complexType name="' + @entityName + '">')
<br /> insert @view (value) values (' <xsd:sequence>')
<br />
<br /> declare attributeCursor cursor for
<br /> select Attribute.LogicalName,
<br /> case
<br /> when AttributeTypes.XmlType in ('dateTime.tz', 'datetime') then 'tns:dateTimeType'
<br /> when AttributeTypes.XmlType = 'Boolean' then 'tns:booleanType'
<br />
<br /> when AttributeTypes.XmlType = 'picklist' then 'tns:picklistType'
<br /> when AttributeTypes.XmlType = 'state' then 'tns:stateType'
<br /> when AttributeTypes.XmlType = 'status' then 'tns:statusType'
<br />
<br /> when AttributeTypes.XmlType = 'primarykey' then 'tns:keyType'
<br /> when AttributeTypes.XmlType = 'customer' then 'tns:customerType'
<br /> when AttributeTypes.XmlType = 'lookup' then 'tns:lookupType'
<br /> when AttributeTypes.XmlType = 'owner' then 'tns:ownerType'
<br />
<br /> when AttributeTypes.XmlType = 'uuid' then 'tns:keyType'
<br />
<br /> when AttributeTypes.XmlType = 'timezone' then 'xsd:int'
<br /> when AttributeTypes.XmlType in ('integer', 'int', 'bigint', 'smallint', 'tinyint') then 'tns:numberType'
<br /> when AttributeTypes.Description = 'money' then 'tns:moneyType'
<br /> when AttributeTypes.Description = 'decimal' then 'tns:decimalType'
<br /> when AttributeTypes.Description = 'float' then 'tns:floatType'
<br />
<br /> else 'xsd:' + AttributeTypes.XmlType
<br /> end
<br /> from Entity join Attribute on (Entity.EntityId = Attribute.EntityId)
<br /> join AttributeTypes on (Attribute.AttributeTypeId = AttributeTypes.AttributeTypeId)
<br /> where Entity.LogicalName = @entityName
<br /> and (Attribute.ValidForReadAPI = 1 or Attribute.ValidForUpdateAPI = 1 or Attribute.ValidForCreateAPI = 1)
<br /> and Attribute.AttributeOf is NULL
<br /> and Attribute.AggregateOf is NULL
<br /> order by Attribute.LogicalName
<br />
<br /> open attributeCursor
<br /> fetch attributeCursor into @attributeName, @typeName
<br />
<br /> while @@fetch_status = 0
<br /> begin
<br /> insert @view (value) values (' <xsd:element name="' + @attributeName + '" type="' + @typeName + '" />')
<br /> fetch attributeCursor into @attributeName, @typeName
<br /> end
<br />
<br /> close attributeCursor
<br /> deallocate attributeCursor
<br />
<br /> insert @view (value) values (' </xsd:sequence>')
<br /> insert @view (value) values (' </xsd:complexType>')
<br />
<br /> fetch entityCursor into @entityName
<br />
<br /> if @@fetch_status = 0
<br /> begin
<br /> insert @view (value) values ('')
<br /> end
<br />end
<br />
<br />close entityCursor
<br />deallocate entityCursor
<br />
<br />insert @view (value) values ('</xsd:schema>')
<br />
<br />select value
<br />from @view order by idvalue
<br /></code>
<br />
<br />Bonus if you've read this far. To get collection classes too, add some more SQL like this before the second fetch, and make sure you add a column for the collection name.
<br />
<br /><code>
<br /> if @collectionname is not null
<br /> begin
<br /> insert @view (value) values ('')
<br /> insert @view (value) values (' <xsd:complexType name="' + @collectionname + '" final="#all">')
<br /> insert @view (value) values (' <xsd:sequence>')
<br /> insert @view (value) values (' <xsd:element name="' + @entityname + '" minOccurs="1" maxOccurs="unbounded" type="tns:' + @entityname + '" />')
<br /> insert @view (value) values (' </xsd:sequence>')
<br /> insert @view (value) values (' </xsd:complexType>')
<br /> end
<br /></code>
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1086056308715130852004-05-31T19:03:00.000-07:002004-05-31T19:18:28.716-07:00Setting default valuesSo, I was just thinking about the picklist / boolean problem with default values. Let's just say I ran across something that looked suspicious to me in the newsgroup. For example, let's say I've added a pair of custom picklist values, one to opportunity and one to lead and let's say that I've set default values for those picklists. Now, one might suspect that running a "convert lead to opportunity" process would automatically set the default value on the picklist. If you launch the lead in the UI, sure enough, you find the picklist with the correct value *displayed*. That's the catch. The application is being really smart here and has noticed that the platform didn't give it a value in the document, so it painted the picklist the best way it could - with a value.
<br />
<br />So, how can we get around this problem. There's the supported way of creating a mapping between the attributes on opportunity and lead. That works if you're trying to carry that data forward to the opportunity. The unsupported way is to tell the metadata what the default value should be, regenerate the views and triggers for that entity, and then start saving or updating things.
<br />
<br />So, how does one do that you might ask. Well, if you know the value of the picklist item that you want to set as the default, update the Attribute table's DefaultValue column with that value. The only thing is that you need to get the metadata format correct. For numbers, the format is "(<em>number</em>)" and for strings the value is "('<em>string</em>')". Look at some of the other default values that are set in the metadata for an example. By the way, this might work for booleans too since they're really just numbers in the database. Create a default of 0 for false, or 1 for true and see what happens.
<br />
<br />You do need to regenerate the views and triggers. The schema manager will do this for you when you add a new attribute. Unfortunately, it won't do it any other time. But, if you run the p_genSpecificViewAndTriggers with the base table name (say, LeadBase, for leads) as that parameter, you'll get the SQL necessary to recreate the bits. You need to run the sproc, copy the output, paste it to a new SQL QA window, and run it in the <em>orgname</em>_MSCRM database.
<br />
<br />This is off the top of my head. It's 100% unsupported. And it may not work. I suggest creating a backup before mucking about in the databases because if you break it following something I say here, the support team will not help you.
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1086052311215938982004-05-31T18:05:00.000-07:002004-05-31T18:11:51.216-07:00Feeling guiltyI've been meaning to add the next bit of somewhat useful information here, but I've just been slammed recently. The team has just entered M2 and we're cranking right along. I'm working on the customization bits that we missed in V1. But then again, I've been working on them for a long time now, we're just officially getting them into the build. I've also been working with the PM team and some of the partners on programming models. Eventually we'll come to an agreement about what we should build, how we should build it, and when we'll unleash it. Until then, I'll keep building prototypes and talking to partners to see what we can do better. And no, this isn't a slam on the PM team, they're really involved in the product this time around.
<br />
<br />Oh yeah, the reason I'm slammed has nothing to do with MSFT. I'm wrapping up Spring quarter at Seattle U and working on my final project for the Information Assurance track. That's why I really haven't had much time. I have been reading the newsgroup and looking for opportunities to set misunderstandings straight. But, I try to keep a low profile over there... that's for the support team to keep under control. I just use it for ideas about the product and things to think about.Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1085377397659830562004-05-23T22:00:00.000-07:002004-05-23T22:43:17.660-07:00Removing unwanted attributesFirst, let me start off by saying that this is a completely unsupported process. There are a number of things that can go wrong and probably will go wrong, and if you break it there's nothing the support team can do to help you.
<br />
<br />Second, if you're using the Outlook client and have replication set up, this process gets a lot more complicated, and I'm not going to be able to help out either.
<br />
<br />So, let's say you've gone into the schema manager and you've added an attribute, and let's assume you've done something you didn't want to do (like create it with the wrong type, or the wrong size, or the wrong name...). You probably want to remove that attribute. I can imagine there are other scenarios where removing an attribute might be a really helpful thing to do.
<br />
<br />Well, removing attributes is a fairly straightforward thing to do. First, you need to make sure you're not using the attribute anywhere. If you are, and you remove it from the metadata, then the product will appear to break when whatever tool is using the attribute tries to access it. Attributes can be consumed in a number of places - the web application uses then obviously; reporting will use them sometimes; integration might use them; and the Outlook client might too.
<br />
<br />Make sure you've removed the attribute from the forms first, that way you can test out what you've done. Use the tools supplied by the product, they're really good and can do the right thing.
<br />
<br />Here comes the part where things might break. You need to edit the Attribute table in the metadata database. Find the attribute you want to remove (make sure it's the right one and bound to the entity you're expecting it to be bound to). Keep the attribute id handy because you're going to need it. You need to remove all the references to the attribute first - look in AttributeMap for references to the attribute on both sides and remove those references (preferably do this from the mapping tool in schema manager - it knows the right incantations, and if this step breaks you're still in supported territory, I think).
<br />
<br />I'm also assuming that you're not doing anything with an attribute used in any relationship because that will flat out break stuff that you can't fix. This means that AttributeOf, SortAttribute, TypeAttribute, and AggregrateOf will all be NULL for the attribute you're removing. It also means that you won't find the attribute used in KeyAttributes anywhere.
<br />
<br />Once you've got all that cleaned up, you can delete the row from Attribute. There are some limitations to what you can delete, but since you're only deleting attributes you added, right, you shouldn't have a problem. However, it might be educational to talk about some attribute characteristics that describe attributes that just can't be removed. Any attribute with RequiredForGrid, IsPKAttribute, or RequiresPlatformAuthorization == 1 have to stay. Any attribute with IsNullable == 0 have to stay as well - they are required by the product. By the way, if you find your attribute in KeyAttributes or referenced by name in GeneratedJoins (or JoinAttributes), then don't do this - that attribute is being used in a relationship and removing it will change the shape of the entity graph - and you're going to break the software.
<br />
<br />Once you've deleted the attribute reference from the metadata, you're almost done. You just need to ALTER TABLE on the *Base table to drop the column (if you don't know how to do that, you shouldn't be trying any of this stuff...) and then regenerate the views and triggers. If you look at the stored procedures in the metabase you'll find one called p_genSpecificViewAndTrigger. It takes the *Base name of the table that holds the entity as a parameter and generates a script that you can use to recreate the views and triggers. It won't recreate them for you, you need to run it, copy the resulting script, and run that script in the MSCRM database.
<br />
<br />Now, if you're using replication you've got all kinds of other issues do deal with. You need to drop the column from the publication (look in SQL BOL for information on sp_repldropcolumn).
<br />
<br />One thing you need to do now is reset the cache. In fact, you should probably have the application shut down while you're doing all this stuff. Simply do an IISRESET on all the web servers that host MS-CRM - that'll definitely flush the metadata cache from the platform.
<br />
<br />This might look something like:
<br />
<br /><blockquote>
<br />declare @attributeid uniqueidentifier
<br />select @attributeid = a.attributeid
<br />from attribute a join entity e on a.entityid = e.entityid
<br />where e.name = 'contact'
<br /> and a.name = 'yomifullname'
<br />
<br />-- check that the attribute isn't used anywhere
<br />if exists (select * from keyattributes where attributeid = ...)
<br /> raiserror
<br />
<br />-- check that the attribute isn't used anywhere
<br />if exists (select * from keyattributes where referencedattributeid = ...)
<br /> raiserror
<br />
<br />-- check that the attribute isn't used anywhere
<br />if exists (select * from attributemap where sourceattributeid = ...)
<br /> raiserror
<br />
<br />-- check that the attribute isn't used anywhere
<br />if exists (select * from attributemap where targetattributeid = ...)
<br /> raiserror
<br />
<br />if exists (select *
<br /> from attribute
<br /> where (ispkattribute = 1
<br /> or isrequiredforgrid = 1
<br /> or requiresplatformauthorization = 1
<br /> or isnullable = 0)
<br /> and attributeid = @attributeid)
<br /> raiserror
<br />
<br />delete from attribute
<br />where attributeid = @attributeid
<br />
<br />p_genSpecificViewAndTriggers 'ContactBase'
<br />
<br />xp_cmdshell 'IISRESET'
<br /></blockquote>
<br />
<br />Like I said though, this is all unsupported stuff and isn't typically something you want to do, but in the off chance that you've done something, like put in an attribute that blows out the 8k row size limit, and you want to fix the problem, then this procedure will probably help out a bit. Most likely I've missed a number of steps in the process, probably the important ones around removing the attribute from all the replication infrastructure (and there's a lot of it).
<br />
<br />As a reminder, if you remove any of the attributes that we shipped with the product you're probably going to break during the upgrade process. If V2 suddendly decides that one of those ill-named empty columns from V1 look useful for a feature, it might get used, and if it's not there, upgrade might not work. I haven't seen the final designs for V2 around the 8k limit but I've heard that there's a lot of work in this area - your columns might move around on you during the upgrade too. So, just careful, and be willing to put those columns back if youre upgrade testing fails (you do plan on doing upgrade testing when the product ships, right?).
<br />
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1085161250820785862004-05-21T10:31:00.000-07:002004-05-21T10:40:50.820-07:00A little disclaimerIn case it's not clear to everyone, the opinions here are really my own and don't reflect my employer's opinions. Sometimes you might read something that sounds like it's a done deal, it probably isn't. The other thing to keep in mind, especially folks who are investing in the product, is that we are going out of our way to maintain backwards compatibility. There are a few exceptions that we have publicly talked about (activity platform interfaces) that I can say <em>may not</em> be available in 2.0. But, like I said, we are going out of our way to make sure your 1.x investment continues to work.
<br />
<br />That said, I will continue to make what sound like pretty bold statements, but that's just because, even after being involved with this product since the beginning, I'm still pretty excited and passionate about getting it right. I think we did a damned good job for a V1. Sure, the product has some warts, but if we waited to build something perfect we would never ship. Yeah, I trash the product, but that's because it's not quite how <em>I</em> think it should be - and you should probably read that as "we're doing everything we can to make the best CRM product ever".Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1084488935784135272004-05-13T15:18:00.000-07:002004-05-13T15:55:35.783-07:00Why MS-CRM SOAP interfaces suckI woke up this morning to an email from Matt Powell asking me to clarify why the platform interfaces suck. Well, "suck" wasn't really the word he used, it was more of an echo from Simon Fell about the interfaces because they "blow chunks". Nice way to start the morning, but not unusual. Great timing though because I had a 9am meeting about those very interfaces and what we're going to do about them.
<br />
<br />I can't go into exact detail because this stuff is still something we're just batting around, but we are looking at a very different flavor. The idea is to trim the endpoints down dramatically, make them very chunky (in a different way though Simon), bake in some WS-* stuff, and generally make the programming model more programmer friendly.
<br />
<br />Well, back to the topic at hand. The V1.x platform interfaces are kinda ugly to work with. No, let's be honest, they do suck to work with. So, as a client I need to build up an XML string without any help from the tools, make sure it's really XML and not just a string, slam it at a platform interface that's got a weird name, returns more string stuff, and takes other not useful parameters (CUserAuth is something that just doesn't make any sense any more, it's a left-over from a very early design that we couldn't take the time to rip out without breaking everything... trust me, I wish it had gone away)... anyway, the APIs suck.
<br />
<br />But, if we look at the tools that were available at the time we started designing and implementing V1 it becomes pretty clear that we really didn't have a choice. The .Net framework was in early betas without a clear ship date, the CLR hadn't been used for anything in the business applications space yet, and we were looking at hosted scenarios in bCentral (read: this was supposed to be fast and at the time the framework didn't look that way). So we did some looking around and found a project called Manta over in the ATL world. Well, Manta went on to become ATL Server and sure looked like it would work for us.
<br />
<br />[Oh yeah, and it was really simple for our soon-to-be-built application framework to build up those magic XML strings right in the browser, POST them to the application server, and have them forwarded untouched to the platform. Talk about few transforms... there were none, the XML was POSTed directly as an XML document, the application server reached in to the document and pulled out what it needed, if anything, and the document was handed to the platform as a string. Pretty much the same thing on the way from the platform to the application and on to the browser - the platform handed the string to the application layer which ran an XSLT over it, and HTML went out to the browser.]
<br />
<br />The only problem was that we had a hard requirement that our entity definitions had to support change once deployed. That is, our customers typically customize CRM solutions to meet their business needs. That's what CRM is all about. Well, ATL Server only supported C++/COM types. So, the problem became how do we provide a SOAP interface ('cause damn it we were going to support SOAP and web services even if it killed us) that wasn't too hard to program against, was cross-platform friendly, supported WSDL, and supported extensible types. We chose the XML-as-strings path for V1.x. To make some of the pain go away we provided a client-side proxy so developers wouldn't need to spend a lot of cycles trying to make the WSDL-based client-side classes behave properly (can you say namespace collisions?).
<br />
<br />For V1.2 we spent a lot of time looking at ways to wrap the platform API set in something friendlier but the closest we came (because of the extensibility issues) was to use an "object" called a property bag. Well, that sucks more than XML strings, or at least that's my feeling. That, combined with the fast turnaround time, and the need to not break any deployed applications, meant that we weren't able to do anything better in that version.
<br />
<br />I've always hated the fact that our interface names seemed to match the entity against which they operated, sometimes. It gets really confusing when someone asks why they can't create a CRMAccount class in C#, set up some properties, then call a method on it. That's a general service-oriented disconnect between the SOA patterns and the old school OO patterns. One thing we're doing in early work is to start naming the interfaces FooService for example and the parameter is usually a Foo. But that leads to other problems; the same question comes up about mixing things but with the names cleaned up the confusion gets easier to clear up.
<br />
<br />On to V2 (don't ask me about timelines 'cause I won't say anything). We are seriously looking at introducing a cleaner model across the entire platform. We're doing this for a few reasons. The first is that we're introducing a managed platform for a number of V2 entities and that means that we'd have an ATL Server-based interface and a managed ASMX interface with two different programming models. We even considered butchering the V2 interfaces so they look and behave like 1.x interfaces. Ick.
<br />
<br />The current plan is that we're trashing both interfaces in favor of something very lightweight, very VS-friendly, and closer to an SOA model. The idea is to chop the API set down to:
<br />
<br /><blockquote>BusinessEntity Create(Type) -- create a new, initialized, but not persisted entity</blockquote>
<br /><blockquote>Delete(BusinessEntity) -- we're debating whether delete is exposed or if delete is really just a state change. There are strong feelings on both sides.</blockquote>
<br /><blockquote>EntityCollection Find(Criteria) -- using the criteria get a collection of entities</blockquote>
<br /><blockquote>BusinessEntity Get(EntityMoniker) -- retrieve exactly one (or zero) entity</blockquote>
<br /><blockquote>SaveOutcome Save(BusinessEntity) -- put the entity in the database or update an existing one</blockquote>
<br />
<br />and finally...
<br />
<br /><blockquote>Execute(BusinessDocument, Criteria) -- using Criteria, find a collection of entities and apply the business methods specified in BusinessDocument. There's a lot of cool stuff in this approach that allows us to support replaceable method implementations, workflow-driven implementations, and ISV-defined functionality. This would heavily leverage the current metadata but would greatly extend it.</blockquote>
<br />
<br />The parameters are really the driving force. The service is secondary. The client sets up the "command" they want executed by constructing a class, filling in some basic information, and submitting it to the service. Most of the methods can take their type information directly from the supplied parameter. For Execute the BusinessDocument describes the method, the parameters, and other interesting bits.
<br />
<br />Oh yeah, and those 6 methods are the entire API set. We'll see what happens. There's a long way to go defining this stuff and we still need to get some decent prototype work in place to make sure we can support the new model and have some back-compat for the old model for a while yet.
<br />
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1084313431145015052004-05-11T15:03:00.000-07:002004-05-11T15:10:31.146-07:00Some topics under considerationI just started thinking... what will I discuss around here and in what order? I'm not sure about the order yet, but I've got some starter topics that might be of interest.
<br />
<br />Adding a custom page to a detail view using an IFRAME
<br />How to remove your added attributes
<br />What's that SecurityDescriptor column all about
<br />Advanced callouts, why aren't there any "pre"-callouts, transactions, locking, and how to make it all work with MSMQ
<br />Scripting workflow without the workflow editor
<br />Using the control framework (in a completely unsupported way)
<br />What's the deal with SRF files
<br />How to write to the platform using web services
<br />What's this XSDObjectGen thing and how would it help
<br />How to convince MS-CRM to give real XSD schemas for entities
<br />How to build a live entity browser like the static one in MSDN
<br />How dates and times work
<br />What are these XML attributes all about and how do we make complex elements behave well
<br />What is <fetch> and why can't I use T-SQL
<br />How can I get aggregate SQL functions using <fetch>
<br />
<br />
<br />I'm sure there are a lot more topics that are interesting to people "in the trenches". This is just a list off the top of my head from taking a quick scan through the newsgroup.Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.comtag:blogger.com,1999:blog-6950032.post-1084289281723587842004-05-11T08:11:00.000-07:002004-05-11T08:33:02.686-07:00Just getting startedSo this is something I've wanted to do for a long time. As one of the founding team members behind Microsoft CRM I've had an interesting viewpoint into the development process and the product itself. I've been reading the <a href="news:microsoft.public.crm">CRM public newsgroup</a> since its start and have really noticed a number of places where we could have done better (and, by the way, we do know about a lot of the issues and are trying to address them in upcoming releases, but we had to get this thing out the door).
<br />
<br />Sure, the product has some issues, but overall it's pretty cool. I'm not going to play it up all the time, there are just too many holes that need to be patched. But for a V1 product with a small team building on untried technology I think we did a pretty damned good job.
<br />
<br />I suppose I should give a little background on myself so people might think I know what I'm talking about. I was over in Carpoint when the DP team spun off to create a productize version of DP5.0 called MerchantPoint (we liked to point at things in those days). The only relevance to CRM I supposed was that I was responsible for taking the MP v1 code and making it work for one of our larger automotive partners. What a pain in the ass that was.
<br />
<br />When the company finally decided to get serious about CRM the MP team was completely spun out of MSN and was sent over to bCentral. I went with as a software architect and vowed to dump the entire code base and start from scratch. ...and that's what we did. MS-CRM was designed from the ground-up to follow a web service model and to be hosted on bCentral (along with any other ASP willing pay for the honor of running the platform).
<br />
<br />Depending on my mood I might go into the culture clash that happened when, what was then known as ClearLead and bCentral Customer Manager merged with a small team from Office-land called SBCM...
<br />
<br />Well, as many people know, MS-CRM doesn't really work all that well in a hosted environment. There are a number of reasons, but the biggest one was that during the MBS acquisition days (GPSI and Navision) MS-CRM got a reset and we decided it would be an on-premise solution. So, here we are building a product that was originally designed for data centers - hence the heavy MSFT stack and prerequisities - but tweaking bits here and there to run on a smaller scale. Oh yeah, and by smaller we actually meant to make both the web-based client and the Outlook-based client use not only the same code but the same binaries. It's some of those tweaks that are so painful to get around today (Security descriptors in the database... seemed like a brilliant idea at the time, and if we could have made it really performant and transparent it would have been great, but...)
<br />
<br />Well, anyway, welcome to what might be an interesting combination of history, lessons learned, and an insider's look at Microsoft CRM (and yes, I'll try to address some of those issues like removing attributes from shipped entities).
<br />Michaeljon Millerhttp://www.blogger.com/profile/13136909251877354921noreply@blogger.com