Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Updates for dynamic ORMs #5

Merged
merged 4 commits into from

2 participants

@amirrajan

Here we go! Frans. I can't thank you enough for that insight. Converting literally 2 lines of code had a significant increase.

Here is what I have:

  1. I've included a bench mark for as close to the DLR as I could possibly get. The "typeless dynamic object" is a raw dictionary that reads straight from the data reader. This probably as fast as you can expect a dynamic object plus the DLR.

  2. The next one is a typed dynamic class, without change tracking.

  3. The final one is what I originally submitted (unaltered), but with the performance bottle neck that you found. Thanks again for the help. You didn't have to do that, and you didn't have to spend the time you've already spent. So it truly is appreciated (yay OSS).

I still think the stopwatch should be after the enumeration for these dynamic objects. It's important to show what kind of overhead the DLR adds.

@FransBouma
Owner

:)

Will merge tomorrow (thursday) if I have time. I have migrated locally to the 2008+ example db from microsoft, so some things changed locally, so merging will not be smooth I recon.

I'll also include enumeration on the results in all other results, that's rather easy, I have factored out the check in a separate method locally. Good point on the DLR overhead being visible this way!

Glad you could fix the slow down :) It was not a lot of effort, just attach the profiler, record snapshot, take a peek and it was easy to spot :) Don't know if you use profilers, but they can help a lot (I used dottrace, there are others like ants. )

@FransBouma FransBouma merged commit ad9a984 into from
@FransBouma
Owner

I've merged it, there were merge conflicts, I tried to resolve them. There are 3 tests for Oak, 2 of them are without change tracking. I want max 2 per ORM, so which one do you want to keep from the non-change tracking?

@amirrajan

Note with regards to the enumeration speed across the dynamic type: hydration of the dynamic object is done the first time a property is accessed and happens once on Oak's underlying dynamic construct. Subsequent enumerations and property access happen at compile time speed.

I kept the dto one, as the differences were minimal, only enumerating the sets showed some differences (24ms vs. 240ms). Commited into the next push.

Using a typeless dto/dictionary backed object does not take have this late bound behavior (which is why it's 10x faster to enumerate).

I want max 2 per ORM, so which one do you want to keep from the non-change tracking

I think you should keep both of them. Here is why.

There are 2 without change tracking specially to show what the difference is between a typed dynamic object and a typeless dynamic object. Many .Net developers (when they think of dynamic), think of ExpandoObject or ViewBag or some form of dictionary (such as DataSets or IDictionary<string, object>). All these implementation of dynamic are essentially typeless. Meaning, you can't ask an ExpandoObject if it's a SalesOrderHeader, because it will never be that.

The two test without change tracking help clarify the difference between a typeless dynamic object and a typed dynamic object. It shows that an object can be typed, but still dynamic. You can say entry is SalesOrderHeaderDto and have it return true.

At the end of the day it's up to you. If you still feel that you want to limit it to two, I'd remove the typeless example and keep the typed/DTO implementation. Personally, I've had too many conversations with developers about dynamic typing and I always end up asking the following question:

"Have you tried dynamic types in C# or are you just using typeless objects? Just because you used the dynamic keyword doesn't mean you are using dynamic typing."

Keeping both examples shows the difference between using typed and typless objects (and the trades offs that come with it).

@FransBouma
Owner

Ok good point. I've refactored a lot of code. It's now much easier to add benchers. I'll add the other code you added manually, so you don't need to create a pull request.

@FransBouma
Owner

I kept the dto one, as the differences were minimal, only enumerating the sets showed some differences (24ms vs. 240ms). Commited into the next push.

@amirrajan

Cool. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
4 OakDynamicDb.Bencher/Oak/Gemini.cs
@@ -660,11 +660,11 @@ public virtual IEnumerable<string> Members()
{
var dictionary = new Prototype() as IDictionary<string, object>;
- var delegates = Delegates();
+ var hashset = new HashSet<string>(Delegates().Select(s => s.Key));
Hash().ForEach<KeyValuePair<string, object>>(s =>
{
- if (!delegates.Contains(s)) dictionary.Add(s.Key, s.Value);
+ if (!hashset.Contains(s.Key)) dictionary.Add(s.Key, s.Value);
});
return dictionary;
View
22 OakDynamicDb.Bencher/Oak/Massive.cs
@@ -95,11 +95,23 @@ public static List<dynamic> ToGeminiList(this IDataReader rdr, Func<dynamic, dyn
public static dynamic RecordToGemini(this IDataReader rdr, Func<dynamic, dynamic> projection)
{
- dynamic e = new Gemini();
- var d = e.Prototype as IDictionary<string, object>;
- for (int i = 0; i < rdr.FieldCount; i++)
- d.Add(rdr.GetName(i), DBNull.Value.Equals(rdr[i]) ? null : rdr[i]);
- return projection(e);
+ if (projection == null)
+ {
+ var e = new Prototype() as IDictionary<string, object>;;
+ PopluateDynamicDictionary(rdr, e);
+ return e;
+ }
+ else
+ {
+ dynamic e = new Gemini();
+ PopluateDynamicDictionary(rdr, e.Prototype);
+ return projection(e);
+ }
+ }
+
+ public static void PopluateDynamicDictionary(this IDataReader rdr, IDictionary<string, object> d)
+ {
+ for (int i = 0; i < rdr.FieldCount; i++) d.Add(rdr.GetName(i), DBNull.Value.Equals(rdr[i]) ? null : rdr[i]);
}
}
View
14 OakDynamicDb.Bencher/Oak/ReleaseNotes.txt
@@ -1,4 +1,18 @@
============== FOR VERSIONS ==============
+gemini 1.3.2+
+oak 2.7.0+
+oak-edge 2.1.0+
+cambium 1.5.7+
+oak-json 1.1.5+
+==============
+Gemini method sped up to use hash set instead of a list. Performance
+improvements for determinging which members are methods vs properties.
+
+Performance improvements to DynamicRepository. Setting the projection to null
+will give you a fast dictionary object as opposed to a dynamic type.
+
+
+============== FOR VERSIONS ==============
gemini 1.3.1+
oak 2.6.9+
oak-edge 2.0.9+
View
8 OakDynamicDb.Bencher/SalesOrderHeaders.cs
@@ -15,11 +15,17 @@ public class SalesOrderHeader : DynamicModel
public SalesOrderHeader() : base() { }
}
+ public class SalesOrderHeaderDto : Gemini
+ {
+ public SalesOrderHeaderDto(object dto) : base(dto) { }
+ public SalesOrderHeaderDto() : base() { }
+ }
+
public class SalesOrderHeaders : DynamicRepository
{
public SalesOrderHeaders() : base("Sales.SalesOrderHeader", "SalesOrderID")
{
- Projection = d => new SalesOrderHeader(d);
+
}
}
}
View
2  OakDynamicDb.Bencher/packages.config
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="cambium" version="1.5.6" targetFramework="net45" />
+ <package id="cambium" version="1.5.7" targetFramework="net45" />
</packages>
View
57 RawBencher/Program.cs
@@ -98,8 +98,17 @@ static void Main(string[] args)
}
for (int i = 0; i < loopAmount; i++)
{
+ FetchSalesOrderHeaderOakDynamicDbTypeless();
+ }
+ for (int i = 0; i < loopAmount; i++)
+ {
+ FetchSalesOrderHeaderOakDynamicDbDto();
+ }
+ for (int i = 0; i < loopAmount; i++)
+ {
FetchSalesOrderHeaderOakDynamicDb();
}
+
Console.WriteLine("\nIndividual entity fetch benches");
Console.WriteLine("------------------------------------------");
@@ -488,13 +497,56 @@ private static void FetchSalesOrderHeaderNH()
private static void FetchSalesOrderHeaderOakDynamicDb()
{
- var frameworkName = "Oak.DynamicDb hydrating a dynamic type";
+ var frameworkName = "Oak.DynamicDb hydrating and binding a dynamic class, with change tracking";
+ var sw = new Stopwatch();
+ sw.Start();
+ var db = new OakDynamicDb.Bencher.SalesOrderHeaders();
+ db.Projection = d => new OakDynamicDb.Bencher.SalesOrderHeader(d);
+ var headers = db.All();
+
+ foreach (var header in headers)
+ {
+ if (header.SalesOrderID <= 0)
+ {
+ Console.WriteLine("Oak: Data is empty");
+ break;
+ }
+ }
+ sw.Stop();
+
+ ReportResult(frameworkName, sw.ElapsedMilliseconds, headers.Count());
+ }
+
+ private static void FetchSalesOrderHeaderOakDynamicDbTypeless()
+ {
+ var frameworkName = "Oak.DynamicDb hydrating and binding a typeless dynamic object, without change tracking";
var sw = new Stopwatch();
sw.Start();
var db = new OakDynamicDb.Bencher.SalesOrderHeaders();
+ db.Projection = null;
var headers = db.All();
+
+ foreach (var header in headers)
+ {
+ if (header.SalesOrderID <= 0)
+ {
+ Console.WriteLine("Oak: Data is empty");
+ break;
+ }
+ }
sw.Stop();
+
ReportResult(frameworkName, sw.ElapsedMilliseconds, headers.Count());
+ }
+
+ private static void FetchSalesOrderHeaderOakDynamicDbDto()
+ {
+ var frameworkName = "Oak.DynamicDb hydrating and binding a dynamic class, without change tracking";
+ var sw = new Stopwatch();
+ sw.Start();
+ var db = new OakDynamicDb.Bencher.SalesOrderHeaders();
+ db.Projection = d => new OakDynamicDb.Bencher.SalesOrderHeaderDto(d);
+ var headers = db.All();
foreach (var header in headers)
{
@@ -504,6 +556,9 @@ private static void FetchSalesOrderHeaderOakDynamicDb()
break;
}
}
+ sw.Stop();
+
+ ReportResult(frameworkName, sw.ElapsedMilliseconds, headers.Count());
}
private static void GetVersionStrings(Assembly a, out string fileVersion, out string assemblyVersion)
Something went wrong with that request. Please try again.