Once you have added the tool to Visual Studio, generating classes
is a matter of starting the wizard by choosing the menu item.
I skipped over some of the options that I have never personally
used in the dialog. A lot of this same documentation can be found
in the Cache docs under the .NET section "Using the Cache Managed
Provider for .NET", chapter 5 "Using Cache Proxy Classes". This
chapter has good documentation on all options for the .Net Wizard.
Now that you have generated your proxy classes, you need to add a
references in your .Net project to be able to use them. The .dll
is located at
<CacheInstall>\dev\dotnet\bin\InterSystems.Data.CacheClient.dll.
Make sure to use the .dll from the same folder that you chose for
your proxy class wizard. This .dll contains everything necessary
to interact with your proxy classes. In most classes you will add
the following using statements to work with your proxy classes:
using InterSystems.Data.CacheClient; using InterSystems.Data.CacheTypes;
I also recommend using a function to easily grab a connection for you. The function we use is:
public static CacheConnection CacheConnect(string user, string password) { var conStr = new CacheConnectionStringBuilder { Server = WebConfigurationManager.AppSettings["server"], Port = int.Parse(WebConfigurationManager.AppSettings["port"]), Namespace = WebConfigurationManager.AppSettings["namespace"], User = user, Password = password, LogFile = null }; var con = new CacheConnection(conStr.ToString()) {ConnectionTimeout = 30, AppNamespace = "Atlas.InterSystemsCache"}; return con; }
This is for our web application and it grabs settings from our
web.config file so different locations can point to different
Cache servers. One thing to point out about the code is the line
where we actually connect.
var con = new CacheConnection(conStr.ToString()) {ConnectionTimeout = 30, AppNamespace = "Atlas.InterSystemsCache"};
In this line, we specify an AppNamespace. You need to provide
this property with the exact namespace you chose from the .Net
wizard above if you plan on calling methods from your proxy
classes that will return a %ListOfObjects back to your .Net
application. For example, we have a method that I can send in a
sale date and it will return a %ListOfObjects back containing
Brashers.Lane objects from Cache. In the .Net application, a
lookup is required at runtime to determine what type of class the
%ListOfObjects could contain. If you don't provide the
AppNamespace pointing to the same place as your proxy classes you
can get an error stating:
Cannot instantiate a proxy class for Brashers.Lane. Please check that you generated the proxy class and that the Application namespace is configured correctly.
Once you have this set up, you can start using your proxy
classes. To do this, you need to get a CacheConnection (easy if
you have a method to do it for you) and then open the object the
same way you would in Cache. You have a projection of all the
unique index opens that are available on the Cache side also. For
example, here is some code used to do a vehicle lookup on our
system. You can see that we are using the .Net managed provider to
query the database and then using proxy classes to read the cars:
public static List<VehicleFindModel> FindVehicles(string query, CacheConnection con) { var cars = new List<VehicleFindModel>(); var url = new UrlHelper(HttpContext.Current.Request.RequestContext); using (CacheCommand com = con.CreateCommand()) { com.CommandText = "select" + " id," ...snip... + " from" + " Brashers.VehicleBase" + " where" + " CarNumber = ?" + " or INumber = ?" + " or SerialNumber like ?"; com.Parameters.Add(new CacheParameter("query", query.ToUpperInvariant())); com.Parameters.Add(new CacheParameter("query", query.ToUpperInvariant())); com.Parameters.Add(new CacheParameter("query", "%" + query)); using (CacheDataReader dr = com.ExecuteReader()) { while (dr.Read()) { string id = (dr["id"] ?? "").ToString(); if (Vehicle.ExistsId(con, id) ?? false) { using(var veh = Vehicle.OpenId(con, id)) { cars.Add(CreateVehicleFindModel(url, veh, id)); } } else if (VehicleArchive.ExistsId(con, id) ?? false) { using (var veh = VehicleArchive.OpenId(con, id)) { cars.Add(CreateVehicleFindModel(url, veh, id)); } } } } } return cars; }
All proxy classes implement IDisposable so I recommend wrapping
them with using() statements to clean up the resources they use to
avoid memory leaks. As you can see, the code to open a proxy class
is very similar to both the MV And COS methods:
using(var veh = Vehicle.OpenId(con, id)) { cars.Add(CreateVehicleFindModel(url, veh, id)); }
And the code from CreateVehicleFindModel demonstrates that it is easy to work with the classes too:
private static VehicleFindModel CreateVehicleFindModel(UrlHelper url, VehicleBase veh, string id) { return new VehicleFindModel { Id = id, RunNumber = veh.CarNumber, RunNumberLink = RunNumberLink(veh, url), INumber = veh.INumber, Description = string.Format("{0} {1} {2} {3}", veh.Year, veh.MakeLongLC, veh.ModelLongLC, veh.TrimLC), BodyStyle = veh.Style, Color = veh.ExteriorColor == null ? null : veh.ExteriorColor.Name, Status = veh.VehicleStatus, SaleDate = ConnectionUtility.GetDateTime(veh.SaleDate), ArchiveDate = ConnectionUtility.GetDateTime(veh.ArchiveDate), Vin = veh.SerialNumber, ViewLink = url.Action("DispCar", "Vehicle", new {id}) }; }
You interact with the objects in the same way that you would a
Cache object or .Net object.
One thing to note is that conversion from a CacheDate or
CacheTime to a .Net DateTime? can be problematic. For some reason,
a null CacheDate or CacheTime does not convert to a null
DateTime?. The CacheTime is particularly problematic for people
using MV code. The reason is that Cache MV stores times as
strings, but the .Net class expects a float. This causes
conversion errors when trying to work with MV times in .Net. To
get around these issues, we added a couple of methods:
/// <summary> /// Gets the date from cache and puts it into a DateTime? variable. /// </summary> /// <param name="cacheDate">The cache date.</param> /// <returns></returns> public static DateTime? GetDateTime(CacheDate cacheDate) { if (cacheDate.IsNull) return null; return cacheDate; } /// <summary> /// Gets the time from cache and puts it into a DateTime? variable /// </summary> /// <param name="cacheTime">The cache time.</param> /// <returns></returns> public static DateTime? GetTimeOnly(CacheTime cacheTime) { if (cacheTime.IsNull) return null; return cacheTime; } /// <summary> /// Fixes the cache time. /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="TU"></typeparam> /// <param name="cacheObject">The cache object.</param> /// <param name="expression">The expression.</param> /// <returns></returns> public static DateTime? FixCacheTime<T, TU>(T cacheObject, Expression<Func<TU>> expression) where T : CacheObject { string propName = ReflectionHelper.GetPropertyName(expression); try { System.Threading.Monitor.Enter(cacheObject.Connection); cacheObject.AssertIsConnected(); cacheObject.Connection.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly(); CacheMethodSignature mtdSignature = cacheObject.Connection.GetMtdSignature(); try { //We're going to assume all times are strings because of the Cache bug... mtdSignature.SetReturnType(cacheObject.Connection, 4); cacheObject.GetPropertyS(propName, mtdSignature); var timeString = ((CacheStringReturnValue)(mtdSignature.ReturnValue)).Value; return CacheToDateTime(timeString); } finally { mtdSignature.Clear(); cacheObject.Connection.PostProcessing(); } } finally { System.Threading.Monitor.Exit(cacheObject.Connection); } } private static DateTime? CacheToDateTime(string timeString) { if (!string.IsNullOrWhiteSpace(timeString)) { double ticks; if (double.TryParse(timeString, out ticks)) { var time = new CacheTime((long)(ticks * CacheTime.CacheTicksPerSecond)); if (!time.IsNull) return time; } } return null; } public static List<DateTime?> FixCacheTimeArray<T, TU>(T cacheObject, Expression<Func<TU>> expression) where T : CacheObject { string propName = ReflectionHelper.GetPropertyName(expression); var toReturn = new List<DateTime?>(); try { System.Threading.Monitor.Enter(cacheObject.Connection); cacheObject.AssertIsConnected(); cacheObject.Connection.GeneratedAssembly = System.Reflection.Assembly.GetExecutingAssembly(); CacheMethodSignature mtdSignature = cacheObject.Connection.GetMtdSignature(); try { //We're going to assume all times are strings because of the Cache bug... mtdSignature.SetReturnType(cacheObject.Connection, 1, 4); cacheObject.GetPropertyS(propName, mtdSignature); var timeString = ((CacheListOfStrings)(((CacheObjReturnValue)(mtdSignature.ReturnValue)).Value)); toReturn.AddRange(timeString.Select(CacheToDateTime)); } finally { mtdSignature.Clear(); cacheObject.Connection.PostProcessing(); } } finally { System.Threading.Monitor.Exit(cacheObject.Connection); } return toReturn; }
The last two method rely on Cache's dynamic proxy classes. You
can find this documented very well in the Cache docs mentioned
above.
One last thing to mention is that some versions of Cache have a problem when using null values in collections. We have written some extension methods that allow us to get around this. They behave the same as the SetAt() and RemoveAt() available on collections in Cache. I have attached them to this email if you are interested.
I enjoyed this opportunity to share some of our experiences at
Brasher's Auto Auctions with the community and look forward to
sharing more in the future.
Jason Warner
Code Monkey
Brasher's Auto Auctions
One of the great things about Cache is how easily it integrates with other technologies. Here at Brasher's, we rely heavily on .Net proxy classes for our .Net code. Proxy classes have some great benefits. They are essentially projections of Cache objects to the .Net framework so you can work with them in much the same way you write MV (or COS) code and they are very fast in our experience.
Proxy classes also have some drawbacks to consider when making a decision about whether or not to use them. First, they are generated against a version of your Cache classes. If the classes they were generated against change significantly, you will need to rebuild your proxies and recompile your application with the new proxy classes. Finally, if your underlying Cache classes are large, your proxy classes can grow quite large also.
To work with proxy classes, Cache has provided a great wizard to generate the .Net code for you. The tool is found at <CacheInstall>\dev\dotnet\bin\CacheNetWizard.exe. I would recommend adding this to your External Tools in Visual Studio.
- Go to the menu Tools->External Tools...
- In the dialog the appears select the "Add" button
- <Mail Attachment.png>
- Fill out the information on the form. I usually name mine "Cache Proxy Tool". If I have multiple versions of Cache installed, I make sure to point to the version that we are using in production for the Wizard. Different versions of Cache will generate different .Net code.
- Press "OK" to add to your Tools menu
- <Mail Attachment.png>
Once you have added the tool to Visual Studio, generating classes is a matter of starting the wizard by choosing the menu item.
- Choose the item you added from the Tools menu. You will be presented with a dialog
- <Mail Attachment.png>
--
You received this message because you are subscribed to the Google Groups "InterSystems: MV Community" group.
To post to this group, send email to Cac...@googlegroups.com
To unsubscribe from this group, send email to CacheMV-u...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/CacheMV?hl=en<CacheExtensionException.cs><CacheExtensions.cs>
Jason, thanks for that great posting. I spend most of my life in Visual Studio with C#, and the world between thick/thin clients and MV. So it's nice to see so much detail about that tier here. I only wish there were more sites looking to use .NET with MV on Caché. This is such a sub-sub-sub-specialty.
You're using a SQL-style query and data reader. Isn't there a way for MV developers to pass in a more MV-style Select? I'm guessing this would be done through a method which then executes a query in the class. In one respect that's good because it abstracts how data is selected from applications. On the other hand that would restrict .NET developers freedom in this area, and perhaps require more proxy regeneration. Have you dabbled with any of that?
And is ISC looking to incorporate your extensions into their own to eliminate some of the mismatch between tiers?
Regards,
T
Tony Gravagno
Nebula Research and Development
TG@ remove.pleaseNebula-RnD.com
Nebula-RnD.com/blog
Visit http://PickWiki.com! Contribute!