From 47d079263100f98947187bdf236a295ae6d8110d Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 18:56:53 +0200 Subject: [PATCH 01/10] Add attributes --- InfluxData.Net.Common/Attributes/FieldAttribute.cs | 10 ++++++++++ InfluxData.Net.Common/Attributes/TagAttribute.cs | 10 ++++++++++ InfluxData.Net.Common/Attributes/TimestampAttribute.cs | 10 ++++++++++ 3 files changed, 30 insertions(+) create mode 100644 InfluxData.Net.Common/Attributes/FieldAttribute.cs create mode 100644 InfluxData.Net.Common/Attributes/TagAttribute.cs create mode 100644 InfluxData.Net.Common/Attributes/TimestampAttribute.cs diff --git a/InfluxData.Net.Common/Attributes/FieldAttribute.cs b/InfluxData.Net.Common/Attributes/FieldAttribute.cs new file mode 100644 index 0000000..4f021d9 --- /dev/null +++ b/InfluxData.Net.Common/Attributes/FieldAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace InfluxData.Net.Common.Attributes +{ + public class FieldAttribute : Attribute + { + } +} diff --git a/InfluxData.Net.Common/Attributes/TagAttribute.cs b/InfluxData.Net.Common/Attributes/TagAttribute.cs new file mode 100644 index 0000000..aec87a4 --- /dev/null +++ b/InfluxData.Net.Common/Attributes/TagAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace InfluxData.Net.Common.Attributes +{ + public class TagAttribute : Attribute + { + } +} diff --git a/InfluxData.Net.Common/Attributes/TimestampAttribute.cs b/InfluxData.Net.Common/Attributes/TimestampAttribute.cs new file mode 100644 index 0000000..f6dce2a --- /dev/null +++ b/InfluxData.Net.Common/Attributes/TimestampAttribute.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace InfluxData.Net.Common.Attributes +{ + public class TimestampAttribute : Attribute + { + } +} From 8d06042b01175636c2dbe5cd6e1eee56fbbad9bf Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 19:31:46 +0200 Subject: [PATCH 02/10] Add measurement attribute --- InfluxData.Net.Common/MeasurementAttribute.cs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 InfluxData.Net.Common/MeasurementAttribute.cs diff --git a/InfluxData.Net.Common/MeasurementAttribute.cs b/InfluxData.Net.Common/MeasurementAttribute.cs new file mode 100644 index 0000000..b155058 --- /dev/null +++ b/InfluxData.Net.Common/MeasurementAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace InfluxData.Net.Common.Attributes +{ + public class MeasurementAttribute : Attribute + { + } +} From 480e121763fcdea901e99f3a82a6481ae277294a Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 20:12:05 +0200 Subject: [PATCH 03/10] Move measurement attribute into correct namespace --- InfluxData.Net.Common/{ => Attributes}/MeasurementAttribute.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename InfluxData.Net.Common/{ => Attributes}/MeasurementAttribute.cs (100%) diff --git a/InfluxData.Net.Common/MeasurementAttribute.cs b/InfluxData.Net.Common/Attributes/MeasurementAttribute.cs similarity index 100% rename from InfluxData.Net.Common/MeasurementAttribute.cs rename to InfluxData.Net.Common/Attributes/MeasurementAttribute.cs From cf4efef4a29a4d5706a751f14e9c438cbf4ebc6f Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 21:11:39 +0200 Subject: [PATCH 04/10] Add MissingExpectedAttribyteException --- .../MissingExpectedAttributeException.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 InfluxData.Net.Common/Infrastructure/MissingExpectedAttributeException.cs diff --git a/InfluxData.Net.Common/Infrastructure/MissingExpectedAttributeException.cs b/InfluxData.Net.Common/Infrastructure/MissingExpectedAttributeException.cs new file mode 100644 index 0000000..6af0abb --- /dev/null +++ b/InfluxData.Net.Common/Infrastructure/MissingExpectedAttributeException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace InfluxData.Net.Common.Infrastructure +{ + public class MissingExpectedAttributeException : Exception + { + public MissingExpectedAttributeException(Type attributeType) + : base($"The expected attribute: {attributeType.Name} is missing") + { + } + } +} From cbdaebc1e4c4507d22bd67d878dd0fb47166bf05 Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 22:11:08 +0200 Subject: [PATCH 05/10] Add point extension for converting decorated types into 'Point' --- .../Helpers/PointExtensions.cs | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs diff --git a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs new file mode 100644 index 0000000..2b08508 --- /dev/null +++ b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs @@ -0,0 +1,187 @@ +using InfluxData.Net.Common.Attributes; +using InfluxData.Net.InfluxDb.Models; +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using InfluxData.Net.Common.Infrastructure; +using System.Reflection; + +namespace InfluxData.Net.InfluxDb.Helpers +{ + public static class PointExtensions + { + /// + /// Allows for converting attribute decorated types into a + /// Attribute rules: + /// 1) Must have exactly ONE [Measurement] attribute + /// 2) Must not have more than ONE [Timestamp] attribute + /// 3) Must have at least ONE [Field] attribute + /// 4) [Tag] attribute is optional + /// + /// + /// + /// + /// + /// Example of valid type: + /// + /// + /// public class MyType + /// { + /// [Measurement] + /// public string MyMeasurement { get; set; } + /// + /// [Timestamp] + /// public DateTime Time { get; set; } + /// + /// [Tag] + /// public string SignalName { get; set; } + /// + /// [Field] + /// public double Value { get; set; } + /// } + /// + /// + /// + public static Point ToPoint(this TModel model) + { + var type = model.GetType(); + + Point point = new Point + { + Fields = new Dictionary(), + Tags = new Dictionary() + }; + + var properties = type.GetProperties(); + + point.SetTimestamp(model, properties); + point.SetMeasurement(model, properties); + point.SetFields(model, properties); + point.SetTags(model, properties); + + return point; + } + + private static Point SetTimestamp(this Point point, TModel model, PropertyInfo[] properties) + { + var timestampProperties = properties.Where(x => x.IsDefined(typeof(TimestampAttribute), false)); + + // Make sure only one TimestampAttribute is defined + if (timestampProperties.Any()) + { + if (timestampProperties.Count() != 1) + throw new InvalidOperationException($"Cannot have multiple {typeof(TimestampAttribute).Name} attributes defined"); + + var timestampProperty = timestampProperties.FirstOrDefault(); + var timestampPropertyValue = timestampProperty.GetValue(model); + + if (!timestampProperty.PropertyType.Equals(typeof(DateTime))) + throw new InvalidOperationException($"{nameof(timestampProperty.Name)} is not of type {typeof(DateTime).Name}"); + + if (timestampPropertyValue == null) + throw new InvalidOperationException($"{nameof(timestampProperty.Name)} cannot be null"); + + point.Timestamp = (DateTime)timestampPropertyValue; + } + + return point; + } + + private static Point SetMeasurement(this Point point, TModel model, PropertyInfo[] properties) + { + var measurementProperties = properties.Where(x => x.IsDefined(typeof(MeasurementAttribute), false)); + + if(!measurementProperties.Any()) + { + throw new MissingExpectedAttributeException(typeof(MeasurementAttribute)); + } + + // Make sure only one MeasurementAttribute is defined + if (measurementProperties.Count() != 1) + { + throw new InvalidOperationException($"Must have exactly one {typeof(MeasurementAttribute).Name} attribute defined"); + } + + // Make sure at least one FieldAttribute is defined + if (!properties.Any(x => x.IsDefined(typeof(FieldAttribute), false))) + { + throw new MissingExpectedAttributeException(typeof(FieldAttribute)); + } + + var measurementProperty = measurementProperties.FirstOrDefault(); + var measurementPropertyValue = measurementProperty.GetValue(model); + + if (!measurementProperty.PropertyType.Equals(typeof(string))) + { + throw new InvalidOperationException($"{nameof(measurementProperty.Name)} is not of type {typeof(string).Name}"); + } + + if ((string.IsNullOrWhiteSpace((string)measurementPropertyValue))) + { + throw new InvalidOperationException($"{nameof(measurementProperty.Name)} cannot be null or whitespace"); + } + + point.Name = (string)measurementPropertyValue; + + return point; + } + + private static Point SetTags(this Point point, TModel model, PropertyInfo[] properties) + { + var tagProperties = properties.Where(x => x.IsDefined(typeof(TagAttribute), false)); + + if (tagProperties.Any(x => !x.PropertyType.Equals(typeof(string)))) + { + throw new InvalidOperationException($"Tags can only be string values"); + } + + foreach (var tagProperty in tagProperties) + { + var tagType = tagProperty.PropertyType; + var tagValue = tagProperty.GetValue(model); + + if (tagValue == null) + continue; + + var converted = Convert.ChangeType(tagValue, tagType); + + point.Tags.Add(tagProperty.Name, converted); + } + + return point; + } + + private static Point SetFields(this Point point, TModel model, PropertyInfo[] properties) + { + var fieldProperties = properties.Where(x => x.IsDefined(typeof(FieldAttribute), false)); + + if (fieldProperties.Any(x => !x.PropertyType.IsSimple())) + { + throw new InvalidOperationException($"Fields can only be primitive or string values"); + } + + foreach (var fieldProperty in fieldProperties) + { + var fieldType = fieldProperty.PropertyType; + var fieldValue = fieldProperty.GetValue(model); + + if (fieldValue == null) + continue; + + var converted = Convert.ChangeType(fieldValue, fieldType); + + point.Fields.Add(fieldProperty.Name, converted); + } + + return point; + } + + private static bool IsSimple(this Type type) + { + return + type.IsPrimitive || + type.Equals(typeof(String)); + } + } +} From ad447d45867ec1576061b1c5e61c842422f76dfd Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 22:11:28 +0200 Subject: [PATCH 06/10] Add unit tests for point extensions --- .../InfluxDb/Helpers/PointExtensionsTests.cs | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs diff --git a/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs b/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs new file mode 100644 index 0000000..4c54907 --- /dev/null +++ b/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs @@ -0,0 +1,259 @@ +using InfluxData.Net.Common.Attributes; +using InfluxData.Net.Common.Infrastructure; +using InfluxData.Net.InfluxDb.Helpers; +using InfluxData.Net.InfluxDb.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; + +namespace InfluxData.Net.Tests.InfluxDb.Helpers +{ + [Trait("InfluxDb Helpers", "Point extensions")] + public class PointExtensionsTests + { + [Fact] + public void ToPoint_OnMissingMeasurementAttribute_ThrowsException() + { + TestModelWithoutMeasurement model = new TestModelWithoutMeasurement + { + Time = DateTime.Now, + TestTag = "TestTag", + TestField = "TestField" + }; + + Assert.Throws(() => model.ToPoint()); + } + + [Fact] + public void ToPoint_OnMultipleMeasurementAttributes_ThrowsException() + { + TestModelWithMultipleMeasurements model = new TestModelWithMultipleMeasurements + { + Time = DateTime.Now, + Measurement1 = "FakeMeasurement1", + Measurement2 = "FakeMeasurement2", + TestTag = "TestTag", + TestField = "TestField" + }; + + Assert.Throws(() => model.ToPoint()); + } + + [Fact] + public void ToPoint_OnMultipleTimestampAttributes_ThrowsException() + { + TestModelWithMultipleTimestamps model = new TestModelWithMultipleTimestamps + { + Time1 = DateTime.Now, + Time2 = DateTime.Now, + Measurement = "FakeMeasurement", + TestTag = "TestTag", + TestField = "TestField" + }; + + Assert.Throws(() => model.ToPoint()); + } + + [Fact] + public void ToPoint_OnNonPrimitiveTagProperties_ThrowsException() + { + TestModelWithNonStringTags model = new TestModelWithNonStringTags + { + Measurement = "FakeMeasurement", + Time = DateTime.Now, + PrimitiveTag = "PrimitiveTag", + NonPrimitiveTag = new NonPrimitiveType { Whatever = "Whatever", Whatever2 = "Whatever" }, + PrimitiveField = "PrimitiveField" + }; + + Assert.Throws(() => model.ToPoint()); + } + + [Fact] + public void ToPoint_OnNonPrimitiveFieldProperties_ThrowsException() + { + TestModelWithNonPrimitiveFields model = new TestModelWithNonPrimitiveFields + { + Measurement = "FakeMeasurement", + Time = DateTime.Now, + PrimitiveTag = "PrimitiveTag", + PrimitiveField = "PrimitiveField", + NonPrimitiveField = new NonPrimitiveType { Whatever = "Whatever", Whatever2 = "Whatever" } + }; + + Assert.Throws(() => model.ToPoint()); + } + + [Fact] + public void ToPoint_OnValidType_IsConverted() + { + var measurement = "FakeMeasurement"; + var time = DateTime.Now; + + var firstTagValue = "FirstTag"; + var secondTagValue = "SecondTag"; + + double firstFieldValue = 23.2; + float secondFieldValue = 2323; + + ValidTestModel model = new ValidTestModel + { + Measurement = measurement, + Time = time, + Tag1 = firstTagValue, + Tag2 = secondTagValue, + PrimitiveField1 = firstFieldValue, + PrimitiveField2 = secondFieldValue + }; + + var expectedPoint = new Point + { + Name = measurement, + Timestamp = time, + Tags = new Dictionary + { + { "Tag1", firstTagValue }, + { "Tag2", secondTagValue } + }, + Fields = new Dictionary + { + { "PrimitiveField1", firstFieldValue }, + { "PrimitiveField2", secondFieldValue } + } + }; + + var actualPoint = model.ToPoint(); + + // Compare fields in point + Assert.Equal(expectedPoint.Fields.Count, actualPoint.Fields.Count); + Assert.Equal(expectedPoint.Fields.First().Key, actualPoint.Fields.First().Key); + Assert.Equal(expectedPoint.Fields.Last().Key, actualPoint.Fields.Last().Key); + Assert.Equal(expectedPoint.Fields.First().Value, actualPoint.Fields.First().Value); + Assert.Equal(expectedPoint.Fields.Last().Value, actualPoint.Fields.Last().Value); + + // Compare tags in point + Assert.Equal(expectedPoint.Tags.Count, actualPoint.Tags.Count); + Assert.Equal(expectedPoint.Tags.First().Key, actualPoint.Tags.First().Key); + Assert.Equal(expectedPoint.Tags.Last().Key, actualPoint.Tags.Last().Key); + Assert.Equal(expectedPoint.Tags.First().Value, actualPoint.Tags.First().Value); + Assert.Equal(expectedPoint.Tags.Last().Value, actualPoint.Tags.Last().Value); + + // Compare name and timestamp in point + Assert.Equal(expectedPoint.Name, actualPoint.Name); + Assert.Equal(expectedPoint.Timestamp, actualPoint.Timestamp); + } + + private class TestModelWithoutMeasurement + { + [Timestamp] + public DateTime Time { get; set; } + + [Tag] + public string TestTag { get; set; } + + [Field] + public string TestField { get; set; } + } + + private class TestModelWithMultipleMeasurements + { + [Measurement] + public string Measurement1 { get; set; } + + [Measurement] + public string Measurement2 { get; set; } + + [Timestamp] + public DateTime Time { get; set; } + + [Tag] + public string TestTag { get; set; } + + [Field] + public string TestField { get; set; } + } + + private class TestModelWithMultipleTimestamps + { + [Measurement] + public string Measurement { get; set; } + + [Timestamp] + public DateTime Time1 { get; set; } + + [Timestamp] + public DateTime Time2 { get; set; } + + [Tag] + public string TestTag { get; set; } + + [Field] + public string TestField { get; set; } + } + + private class TestModelWithNonStringTags + { + [Measurement] + public string Measurement { get; set; } + + [Timestamp] + public DateTime Time { get; set; } + + [Tag] + public string PrimitiveTag { get; set; } + + [Tag] + public NonPrimitiveType NonPrimitiveTag { get; set; } + + [Field] + public string PrimitiveField { get; set; } + } + + private class TestModelWithNonPrimitiveFields + { + [Measurement] + public string Measurement { get; set; } + + [Timestamp] + public DateTime Time { get; set; } + + [Tag] + public string PrimitiveTag { get; set; } + + [Field] + public string PrimitiveField { get; set; } + + [Field] + public NonPrimitiveType NonPrimitiveField { get; set; } + } + + private class ValidTestModel + { + [Measurement] + public string Measurement { get; set; } + + [Timestamp] + public DateTime Time { get; set; } + + [Tag] + public string Tag1 { get; set; } + + [Tag] + public string Tag2 { get; set; } + + [Field] + public double PrimitiveField1 { get; set; } + + [Field] + public float PrimitiveField2 { get; set; } + } + + private class NonPrimitiveType + { + public string Whatever { get; set; } + public string Whatever2 { get; set; } + } + } +} From 7e203db8e1a460351fe15ee3f289c525c45ce4d2 Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Tue, 17 Oct 2017 23:34:10 +0200 Subject: [PATCH 07/10] Add option to override property name in field and tag attribute --- .../Attributes/FieldAttribute.cs | 5 ++- .../Attributes/InfluxBaseAttribute.cs | 19 ++++++++++ .../Attributes/MeasurementAttribute.cs | 2 +- .../Attributes/TagAttribute.cs | 5 ++- .../Attributes/TimestampAttribute.cs | 4 ++- .../Helpers/PointExtensions.cs | 8 +++-- .../InfluxDb/Helpers/PointExtensionsTests.cs | 36 +++++++++---------- 7 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 InfluxData.Net.Common/Attributes/InfluxBaseAttribute.cs diff --git a/InfluxData.Net.Common/Attributes/FieldAttribute.cs b/InfluxData.Net.Common/Attributes/FieldAttribute.cs index 4f021d9..a74821a 100644 --- a/InfluxData.Net.Common/Attributes/FieldAttribute.cs +++ b/InfluxData.Net.Common/Attributes/FieldAttribute.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; namespace InfluxData.Net.Common.Attributes { - public class FieldAttribute : Attribute + public class FieldAttribute : InfluxBaseAttribute { + public FieldAttribute([CallerMemberName]string name = null) + : base(name) { } } } diff --git a/InfluxData.Net.Common/Attributes/InfluxBaseAttribute.cs b/InfluxData.Net.Common/Attributes/InfluxBaseAttribute.cs new file mode 100644 index 0000000..9741038 --- /dev/null +++ b/InfluxData.Net.Common/Attributes/InfluxBaseAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace InfluxData.Net.Common.Attributes +{ + public class InfluxBaseAttribute : Attribute + { + + public InfluxBaseAttribute() { } + + public InfluxBaseAttribute(string name) + { + Name = name; + } + + public string Name { get; set; } + } +} diff --git a/InfluxData.Net.Common/Attributes/MeasurementAttribute.cs b/InfluxData.Net.Common/Attributes/MeasurementAttribute.cs index b155058..86c7b6f 100644 --- a/InfluxData.Net.Common/Attributes/MeasurementAttribute.cs +++ b/InfluxData.Net.Common/Attributes/MeasurementAttribute.cs @@ -2,7 +2,7 @@ namespace InfluxData.Net.Common.Attributes { - public class MeasurementAttribute : Attribute + public class MeasurementAttribute : InfluxBaseAttribute { } } diff --git a/InfluxData.Net.Common/Attributes/TagAttribute.cs b/InfluxData.Net.Common/Attributes/TagAttribute.cs index aec87a4..19e4ec6 100644 --- a/InfluxData.Net.Common/Attributes/TagAttribute.cs +++ b/InfluxData.Net.Common/Attributes/TagAttribute.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; namespace InfluxData.Net.Common.Attributes { - public class TagAttribute : Attribute + public class TagAttribute : InfluxBaseAttribute { + public TagAttribute([CallerMemberName]string name = null) + : base(name) { } } } diff --git a/InfluxData.Net.Common/Attributes/TimestampAttribute.cs b/InfluxData.Net.Common/Attributes/TimestampAttribute.cs index f6dce2a..eae1d85 100644 --- a/InfluxData.Net.Common/Attributes/TimestampAttribute.cs +++ b/InfluxData.Net.Common/Attributes/TimestampAttribute.cs @@ -4,7 +4,9 @@ namespace InfluxData.Net.Common.Attributes { - public class TimestampAttribute : Attribute + public class TimestampAttribute : InfluxBaseAttribute { + public TimestampAttribute() + : base("time") { } } } diff --git a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs index 2b08508..d0518f7 100644 --- a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs +++ b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs @@ -146,7 +146,9 @@ private static Point SetTags(this Point point, TModel model, PropertyInf var converted = Convert.ChangeType(tagValue, tagType); - point.Tags.Add(tagProperty.Name, converted); + var propertyName = tagProperty.GetCustomAttribute().Name; + + point.Tags.Add(propertyName, converted); } return point; @@ -171,7 +173,9 @@ private static Point SetFields(this Point point, TModel model, PropertyI var converted = Convert.ChangeType(fieldValue, fieldType); - point.Fields.Add(fieldProperty.Name, converted); + var propertyName = fieldProperty.GetCustomAttribute().Name; + + point.Fields.Add(propertyName, converted); } return point; diff --git a/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs b/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs index 4c54907..bbfc581 100644 --- a/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs +++ b/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs @@ -114,13 +114,13 @@ public void ToPoint_OnValidType_IsConverted() Timestamp = time, Tags = new Dictionary { - { "Tag1", firstTagValue }, - { "Tag2", secondTagValue } + { "Tag1", firstTagValue }, // Tag name is set explicitly in attribute name argument + { "tag2", secondTagValue } }, Fields = new Dictionary { - { "PrimitiveField1", firstFieldValue }, - { "PrimitiveField2", secondFieldValue } + { "PrimitiveField1", firstFieldValue }, // Field name is set explicitly in attribute name argument + { "field2", secondFieldValue } } }; @@ -150,10 +150,10 @@ private class TestModelWithoutMeasurement [Timestamp] public DateTime Time { get; set; } - [Tag] + [Tag("testtag")] public string TestTag { get; set; } - [Field] + [Field("testfield")] public string TestField { get; set; } } @@ -168,10 +168,10 @@ private class TestModelWithMultipleMeasurements [Timestamp] public DateTime Time { get; set; } - [Tag] + [Tag("testtag")] public string TestTag { get; set; } - [Field] + [Field("testfield")] public string TestField { get; set; } } @@ -186,10 +186,10 @@ private class TestModelWithMultipleTimestamps [Timestamp] public DateTime Time2 { get; set; } - [Tag] + [Tag("testtag")] public string TestTag { get; set; } - [Field] + [Field("testfield")] public string TestField { get; set; } } @@ -201,13 +201,13 @@ private class TestModelWithNonStringTags [Timestamp] public DateTime Time { get; set; } - [Tag] + [Tag("primitivetag")] public string PrimitiveTag { get; set; } - [Tag] + [Tag("nonprimitivetag")] public NonPrimitiveType NonPrimitiveTag { get; set; } - [Field] + [Field("primitivetag")] public string PrimitiveField { get; set; } } @@ -219,13 +219,13 @@ private class TestModelWithNonPrimitiveFields [Timestamp] public DateTime Time { get; set; } - [Tag] + [Tag("primitivetag")] public string PrimitiveTag { get; set; } - [Field] + [Field("primitivefield")] public string PrimitiveField { get; set; } - [Field] + [Field("nonprimitivefield")] public NonPrimitiveType NonPrimitiveField { get; set; } } @@ -240,13 +240,13 @@ private class ValidTestModel [Tag] public string Tag1 { get; set; } - [Tag] + [Tag("tag2")] public string Tag2 { get; set; } [Field] public double PrimitiveField1 { get; set; } - [Field] + [Field("field2")] public float PrimitiveField2 { get; set; } } From 65125e050d29afed74f67caa2a1657b2c4df8248 Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Wed, 18 Oct 2017 00:04:20 +0200 Subject: [PATCH 08/10] Remove unecessary initialization of Tags and Fields dictionary --- InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs index d0518f7..407e6f6 100644 --- a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs +++ b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs @@ -47,11 +47,7 @@ public static Point ToPoint(this TModel model) { var type = model.GetType(); - Point point = new Point - { - Fields = new Dictionary(), - Tags = new Dictionary() - }; + Point point = new Point(); var properties = type.GetProperties(); From c12596fe126938ccedfa33941a403dda82ed5d04 Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Wed, 18 Oct 2017 00:14:22 +0200 Subject: [PATCH 09/10] Minor cleanup and misplaced if statement --- .../Helpers/PointExtensions.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs index 407e6f6..83f5a0a 100644 --- a/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs +++ b/InfluxData.Net.InfluxDb/Helpers/PointExtensions.cs @@ -51,15 +51,15 @@ public static Point ToPoint(this TModel model) var properties = type.GetProperties(); - point.SetTimestamp(model, properties); - point.SetMeasurement(model, properties); - point.SetFields(model, properties); - point.SetTags(model, properties); + point.TrySetTimestamp(model, properties); + point.TrySetMeasurement(model, properties); + point.TrySetTags(model, properties); + point.TrySetFields(model, properties); return point; } - private static Point SetTimestamp(this Point point, TModel model, PropertyInfo[] properties) + private static Point TrySetTimestamp(this Point point, TModel model, PropertyInfo[] properties) { var timestampProperties = properties.Where(x => x.IsDefined(typeof(TimestampAttribute), false)); @@ -84,7 +84,7 @@ private static Point SetTimestamp(this Point point, TModel model, Proper return point; } - private static Point SetMeasurement(this Point point, TModel model, PropertyInfo[] properties) + private static Point TrySetMeasurement(this Point point, TModel model, PropertyInfo[] properties) { var measurementProperties = properties.Where(x => x.IsDefined(typeof(MeasurementAttribute), false)); @@ -99,12 +99,6 @@ private static Point SetMeasurement(this Point point, TModel model, Prop throw new InvalidOperationException($"Must have exactly one {typeof(MeasurementAttribute).Name} attribute defined"); } - // Make sure at least one FieldAttribute is defined - if (!properties.Any(x => x.IsDefined(typeof(FieldAttribute), false))) - { - throw new MissingExpectedAttributeException(typeof(FieldAttribute)); - } - var measurementProperty = measurementProperties.FirstOrDefault(); var measurementPropertyValue = measurementProperty.GetValue(model); @@ -123,7 +117,7 @@ private static Point SetMeasurement(this Point point, TModel model, Prop return point; } - private static Point SetTags(this Point point, TModel model, PropertyInfo[] properties) + private static Point TrySetTags(this Point point, TModel model, PropertyInfo[] properties) { var tagProperties = properties.Where(x => x.IsDefined(typeof(TagAttribute), false)); @@ -150,10 +144,16 @@ private static Point SetTags(this Point point, TModel model, PropertyInf return point; } - private static Point SetFields(this Point point, TModel model, PropertyInfo[] properties) + private static Point TrySetFields(this Point point, TModel model, PropertyInfo[] properties) { var fieldProperties = properties.Where(x => x.IsDefined(typeof(FieldAttribute), false)); + // Make sure at least one FieldAttribute is defined + if (!fieldProperties.Any()) + { + throw new MissingExpectedAttributeException(typeof(FieldAttribute)); + } + if (fieldProperties.Any(x => !x.PropertyType.IsSimple())) { throw new InvalidOperationException($"Fields can only be primitive or string values"); From 77ab44e1e851db2f486e1d80896237b362740ea7 Mon Sep 17 00:00:00 2001 From: Joakim Hansson Date: Wed, 18 Oct 2017 00:14:46 +0200 Subject: [PATCH 10/10] Add test for POCO without any field attributes --- .../InfluxDb/Helpers/PointExtensionsTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs b/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs index bbfc581..fc57d60 100644 --- a/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs +++ b/InfluxData.Net.Tests/InfluxDb/Helpers/PointExtensionsTests.cs @@ -26,6 +26,19 @@ public void ToPoint_OnMissingMeasurementAttribute_ThrowsException() Assert.Throws(() => model.ToPoint()); } + [Fact] + public void ToPoint_OnMissingFieldAttribute_ThrowsException() + { + TestModelWithoutFields model = new TestModelWithoutFields + { + Time = DateTime.Now, + MyMeasurement = "FakeMeasurement", + TestTag = "TestTag" + }; + + Assert.Throws(() => model.ToPoint()); + } + [Fact] public void ToPoint_OnMultipleMeasurementAttributes_ThrowsException() { @@ -157,6 +170,18 @@ private class TestModelWithoutMeasurement public string TestField { get; set; } } + private class TestModelWithoutFields + { + [Timestamp] + public DateTime Time { get; set; } + + [Measurement] + public string MyMeasurement { get; set; } + + [Tag] + public string TestTag { get; set; } + } + private class TestModelWithMultipleMeasurements { [Measurement]