diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/StateEntryTableEntityAdapter.cs b/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/StateEntryTableEntityAdapter.cs index 9358f52bbcf..9a0cfc64ad7 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/StateEntryTableEntityAdapter.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/StateEntryTableEntityAdapter.cs @@ -44,12 +44,11 @@ public void ReadEntity(IDictionary properties, Operation foreach (var property in properties) { var entityProp = entityType.TryGetPropertyByStorageName(property.Key); - if (entityProp == null - || entityProp.IsClrProperty && MismatchedTypes(property.Value.PropertyType, entityProp.PropertyType)) + if (entityProp != null + && entityProp.IsClrProperty && EdmTypeMatchesClrType(property.Value.PropertyType, entityProp.PropertyType)) { - continue; + SetProperty(entityProp,property.Value.PropertyAsObject); } - SetProperty(entityProp,property.Value.PropertyAsObject); } } @@ -125,9 +124,9 @@ private void SetProperty(IProperty prop, object value) } } - private bool MismatchedTypes(EdmType propertyType, Type clrType) + private static bool EdmTypeMatchesClrType(EdmType edmType, Type clrType) { - switch (propertyType) + switch (edmType) { case EdmType.String: return typeof(string).IsAssignableFrom(clrType); diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/TableEntityAdapterFactory.cs b/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/TableEntityAdapterFactory.cs index 67b10e84579..93410fd0bed 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/TableEntityAdapterFactory.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/Adapters/TableEntityAdapterFactory.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Reflection; +using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.Data.Entity.AzureTableStorage.Utilities; using Microsoft.Data.Entity.ChangeTracking; +using Microsoft.Data.Entity.Utilities; using Microsoft.WindowsAzure.Storage.Table; namespace Microsoft.Data.Entity.AzureTableStorage.Adapters @@ -17,21 +17,30 @@ public ITableEntity CreateFromStateEntry([NotNull] StateEntry entry) { Check.NotNull(entry, "entry"); - var type = entry.Entity.GetType(); - var ctor = GetOrMakeStateAdapterCtor(type); - return (ITableEntity) ctor.Invoke(new object[] { entry }); + var entityType = entry.Entity.GetType(); + var ctor = GetOrMakeCreator(entityType); + return ctor(entry); } - private readonly IDictionary _stateCtors = new Dictionary(); - private ConstructorInfo GetOrMakeStateAdapterCtor(Type objType) + private readonly ThreadSafeDictionaryCache> _instanceCreatorCache = new ThreadSafeDictionaryCache>(); + private Func GetOrMakeCreator(Type objType) { - if (_stateCtors.ContainsKey(objType)) - { - return _stateCtors[objType]; - } - var ctor = typeof(StateEntryTableEntityAdapter<>).MakeGenericType(objType).GetConstructor(new[] { typeof(StateEntry) }); - _stateCtors[objType] = ctor; - return ctor; + return _instanceCreatorCache.GetOrAdd(objType, type => + { + var paramExpression = new[] + { + Expression.Parameter(typeof(StateEntry), "entry") + }; + var ctorExpression = Expression.New( + typeof(StateEntryTableEntityAdapter<>) + .MakeGenericType(type) + .GetConstructor(new[] { typeof(StateEntry) }), + paramExpression + ); + + var lambda = Expression.Lambda>(ctorExpression, paramExpression); + return lambda.Compile(); + }); } } } diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/AtsBatchedDataStore.cs b/src/Microsoft.Data.Entity.AzureTableStorage/AtsBatchedDataStore.cs index e22c96369c4..9fbb0d6b7e3 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/AtsBatchedDataStore.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/AtsBatchedDataStore.cs @@ -30,12 +30,16 @@ protected AtsBatchedDataStore(AtsConnection connection, TableEntityAdapterFactor { } - public AtsBatchedDataStore([NotNull] DbContextConfiguration configuration, [NotNull] AtsConnection connection, [NotNull] AtsQueryFactory queryFactory, [NotNull] TableEntityAdapterFactory tableEntityFactory) + public AtsBatchedDataStore([NotNull] DbContextConfiguration configuration, + [NotNull] AtsConnection connection, + [NotNull] AtsQueryFactory queryFactory, + [NotNull] TableEntityAdapterFactory tableEntityFactory) : base(configuration, connection, queryFactory, tableEntityFactory) { } - public override async Task SaveChangesAsync(IReadOnlyList stateEntries, CancellationToken cancellationToken = new CancellationToken()) + public override async Task SaveChangesAsync(IReadOnlyList stateEntries, + CancellationToken cancellationToken = new CancellationToken()) { cancellationToken.ThrowIfCancellationRequested(); var tableGroups = stateEntries.GroupBy(s => s.EntityType.StorageName); @@ -53,7 +57,10 @@ public AtsBatchedDataStore([NotNull] DbContextConfiguration configuration, [NotN foreach (var partitionGroup in partitionGroups) { var batch = new TableBatchOperation(); - foreach (var operation in partitionGroup.Select(entry => GetOperation(entry, EntityFactory.CreateFromStateEntry(entry))).Where(operation => operation != null)) + foreach (var operation in partitionGroup + .Select(entry => GetOperation(entry, EntityFactory.CreateFromStateEntry(entry))) + .Where(operation => operation != null) + ) { // TODO allow user access to config options: Retry Policy, Secondary Storage, Timeout batch.Add(operation); diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStore.cs b/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStore.cs index f28d5076719..278a20d7f97 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStore.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStore.cs @@ -56,7 +56,7 @@ public override IEnumerable Query(QueryModel queryModel, State Check.NotNull(stateManager, "stateManager"); var compilationContext = _queryFactory.MakeCompilationContext(Model); - var queryExecutor = compilationContext.CreateVisitor().CreateQueryExecutor(queryModel); + var queryExecutor = compilationContext.CreateQueryModelVisitor().CreateQueryExecutor(queryModel); var queryContext = _queryFactory.MakeQueryContext(Model, Logger, stateManager, Connection); return queryExecutor(queryContext, null); } diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStoreSource.cs b/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStoreSource.cs index c6e194f9080..af0a5a153df 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStoreSource.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/AtsDataStoreSource.cs @@ -6,7 +6,7 @@ namespace Microsoft.Data.Entity.AzureTableStorage { - internal class AtsDataStoreSource : DataStoreSource< + public class AtsDataStoreSource : DataStoreSource< AtsDataStore, AtsOptionsExtension, AtsDataStoreCreator, diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/AtsDatabaseExtensions.cs b/src/Microsoft.Data.Entity.AzureTableStorage/AtsDatabaseExtensions.cs index 270ee9d0968..857c90516f4 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/AtsDatabaseExtensions.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/AtsDatabaseExtensions.cs @@ -3,10 +3,12 @@ using System; using JetBrains.Annotations; +using Microsoft.Data.Entity.AzureTableStorage; using Microsoft.Data.Entity.AzureTableStorage.Utilities; using Microsoft.Data.Entity.Infrastructure; -namespace Microsoft.Data.Entity.AzureTableStorage +// ReSharper disable once CheckNamespace +namespace Microsoft.Data.Entity { public static class AtsDatabaseExtensions { diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/AtsOptionsExtension.cs b/src/Microsoft.Data.Entity.AzureTableStorage/AtsOptionsExtension.cs index 19086040121..5d82e4f744c 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/AtsOptionsExtension.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/AtsOptionsExtension.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.Data.Entity.Infrastructure; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.Data.Entity.AzureTableStorage { diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/EntityBuilderExtensions.cs b/src/Microsoft.Data.Entity.AzureTableStorage/EntityBuilderExtensions.cs index fd3f3f806e3..e71f9c2820a 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/EntityBuilderExtensions.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/EntityBuilderExtensions.cs @@ -5,9 +5,9 @@ using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.Data.Entity.AzureTableStorage.Utilities; -using Microsoft.Data.Entity.Metadata; -namespace Microsoft.Data.Entity.AzureTableStorage +// ReSharper disable once CheckNamespace +namespace Microsoft.Data.Entity.Metadata { public static class EntityBuilderExtensions { diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/EntityServicesBuilderExtensions.cs b/src/Microsoft.Data.Entity.AzureTableStorage/EntityServicesBuilderExtensions.cs index acb406df0ec..dcbc1d0e7c5 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/EntityServicesBuilderExtensions.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/EntityServicesBuilderExtensions.cs @@ -2,13 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; +using Microsoft.Data.Entity; +using Microsoft.Data.Entity.AzureTableStorage; using Microsoft.Data.Entity.AzureTableStorage.Adapters; using Microsoft.Data.Entity.AzureTableStorage.Query; using Microsoft.Data.Entity.AzureTableStorage.Utilities; using Microsoft.Data.Entity.Storage; -using Microsoft.Framework.DependencyInjection; -namespace Microsoft.Data.Entity.AzureTableStorage +// ReSharper disable once CheckNamespace +namespace Microsoft.Framework.DependencyInjection { public static class EntityServicesBuilderExtensions { diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/Query/AtsQueryCompilationContext.cs b/src/Microsoft.Data.Entity.AzureTableStorage/Query/AtsQueryCompilationContext.cs index 5df6394588a..eeb836d19e3 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/Query/AtsQueryCompilationContext.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/Query/AtsQueryCompilationContext.cs @@ -18,13 +18,13 @@ public class AtsQueryCompilationContext : QueryCompilationContext public TableFilterFactory TableFilterFactory { get; private set; } public AtsQueryCompilationContext([NotNull] IModel model, [NotNull] TableFilterFactory tableFilterFactory) - : base(model) + : base(model, new LinqOperatorProvider(), new ResultOperatorHandler()) { Check.NotNull(tableFilterFactory, "tableFilterFactory"); TableFilterFactory = tableFilterFactory; } - public override EntityQueryModelVisitor CreateVisitor() + public override EntityQueryModelVisitor CreateQueryModelVisitor() { return new AtsQueryModelVisitor(this); } diff --git a/src/Microsoft.Data.Entity.AzureTableStorage/Query/TableFilter.cs b/src/Microsoft.Data.Entity.AzureTableStorage/Query/TableFilter.cs index 75be80db9be..e4a61715790 100644 --- a/src/Microsoft.Data.Entity.AzureTableStorage/Query/TableFilter.cs +++ b/src/Microsoft.Data.Entity.AzureTableStorage/Query/TableFilter.cs @@ -8,7 +8,6 @@ using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; -using Microsoft.Data.Entity.AzureTableStorage.Utilities; using Microsoft.WindowsAzure.Storage.Table; namespace Microsoft.Data.Entity.AzureTableStorage.Query @@ -16,87 +15,53 @@ namespace Microsoft.Data.Entity.AzureTableStorage.Query [DebuggerDisplay("TableFilter")] public class TableFilter { - protected string _storageName; + private readonly string _storageName; + private readonly string _operator; + protected Func QueryStringMethod; private TableFilter(string storageName, FilterComparisonOperator op) { - ComparisonOperator = op; + _operator = FilterComparison.ToString(op); _storageName = storageName; } - public object Right { get; protected set; } - public FilterComparisonOperator ComparisonOperator { get; protected set; } - - protected static MethodInfo FilterMethodForConstant(Type type) + private string CreateQueryStringFromObject(object obj) { - if (type.IsAssignableFrom(typeof(string))) - { - return typeof(TableQuery).GetMethod("GenerateFilterCondition"); - } - else if (type.IsAssignableFrom(typeof(double))) - { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForDouble"); - } - else if (type.IsAssignableFrom(typeof(int))) + if (obj == null) { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForInt"); + return String.Empty; } - else if (type.IsAssignableFrom(typeof(long))) - { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForLong"); - } - else if (type.IsAssignableFrom(typeof(byte[]))) + if (_storageName == "PartitionKey" + || _storageName == "RowKey") { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForBinary"); + return TableQuery.GenerateFilterCondition(_storageName, _operator, obj.ToString()); } - else if (type.IsAssignableFrom(typeof(bool))) - { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForBool"); - } - else if (type.IsAssignableFrom(typeof(DateTimeOffset))) - { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForDate"); - } - else if (type.IsAssignableFrom(typeof(DateTime))) - { - var action = new Func( - (prop, op, time) => TableQuery.GenerateFilterConditionForDate(prop, op, new DateTimeOffset(time)) - ); - return action.Method; - } - else if (type.IsAssignableFrom(typeof(Guid))) - { - return typeof(TableQuery).GetMethod("GenerateFilterConditionForGuid"); - } - throw new ArgumentOutOfRangeException("type", "Cannot generate filter method for this type"); + return QueryStringMethod(_storageName, _operator, obj); } - internal class ConstantTableFilter : TableFilter { - private readonly MethodInfo _stringMethod; + public object Right { get; protected set; } public ConstantTableFilter(string storageName, FilterComparisonOperator op, ConstantExpression right) : base(storageName, op) { Right = right.Value; - _stringMethod = FilterMethodForConstant(right.Type); + QueryStringMethod = StringMethodFromType(right.Type); } public override string ToString() { - return (string)_stringMethod.Invoke(null, new object[] { _storageName, FilterComparison.ToString(ComparisonOperator), Right }); + return CreateQueryStringFromObject(Right); } } internal class MemberTableFilter : TableFilter { - private readonly MethodInfo _stringMethod; private readonly Func _getRightValue; - public new MemberInfo Right { get; protected set; } + public MemberInfo Right { get; protected set; } - [UsedImplicitly] public MemberTableFilter(string storageName, FilterComparisonOperator op, MemberExpression right) : base(storageName, op) { @@ -115,13 +80,13 @@ public MemberTableFilter(string storageName, FilterComparisonOperator op, Member if (right.Member is FieldInfo) { var fieldInfo = (FieldInfo)Right; - _stringMethod = FilterMethodForConstant(fieldInfo.FieldType); + QueryStringMethod = StringMethodFromType(fieldInfo.FieldType); _getRightValue = () => fieldInfo.GetValue(getTarget()); } else if (right.Member is PropertyInfo) { var propInfo = (PropertyInfo)Right; - _stringMethod = FilterMethodForConstant(propInfo.PropertyType); + QueryStringMethod = StringMethodFromType(propInfo.PropertyType); if (!constantTarget) { @@ -133,41 +98,76 @@ public MemberTableFilter(string storageName, FilterComparisonOperator op, Member { throw new ArgumentException("Cannot get member info", "right"); } - } public override string ToString() { - return (string)_stringMethod.Invoke(null, new[] { _storageName, FilterComparison.ToString(ComparisonOperator), _getRightValue() }); + return CreateQueryStringFromObject(_getRightValue()); } } internal class NewObjTableFilter : TableFilter { - private readonly ConstructorInfo objCtor; + private readonly ConstructorInfo _objCtor; private readonly IReadOnlyCollection _args; - private readonly MethodInfo _stringMethod; - [UsedImplicitly] public NewObjTableFilter(string storageName, FilterComparisonOperator op, NewExpression right) : base(storageName, op) { - objCtor = right.Constructor; + _objCtor = right.Constructor; _args = right.Arguments; - _stringMethod = FilterMethodForConstant(right.Type); + QueryStringMethod = StringMethodFromType(right.Type); } public override string ToString() { - return (string)_stringMethod.Invoke(null, new[] - { - _storageName, - FilterComparison.ToString(ComparisonOperator), - objCtor.Invoke( - _args.Select(a => ((ConstantExpression)a).Value).ToArray() - ) - }); + return CreateQueryStringFromObject(_objCtor.Invoke(_args.Select(a => ((ConstantExpression)a).Value).ToArray())); } } + + + protected static Func StringMethodFromType(Type type) + { + if (type.IsAssignableFrom(typeof(string))) + { + return (name, op, value) => TableQuery.GenerateFilterCondition(name, op, value.ToString()); + } + else if (type.IsAssignableFrom(typeof(double))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForDouble(name, op, (double)value); + } + else if (type.IsAssignableFrom(typeof(int))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForInt(name, op, (int)value); + } + else if (type.IsAssignableFrom(typeof(long))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForLong(name, op, (long)value); + } + else if (type.IsAssignableFrom(typeof(byte[]))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForBinary(name, op, value as byte[]); + } + else if (type.IsAssignableFrom(typeof(bool))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForBool(name, op, (bool)value); + ; + } + else if (type.IsAssignableFrom(typeof(DateTimeOffset))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForDate(name, op, (DateTimeOffset)value); + ; + } + else if (type.IsAssignableFrom(typeof(DateTime))) + { + return (prop, op, time) => TableQuery.GenerateFilterConditionForDate(prop, op, new DateTimeOffset((DateTime)time)); + } + else if (type.IsAssignableFrom(typeof(Guid))) + { + return (name, op, value) => TableQuery.GenerateFilterConditionForGuid(name, op, (Guid)value); + } + throw new ArgumentOutOfRangeException("type", "Cannot generate filter method for this type"); + } + } } diff --git a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/StateEntryTableEntityAdapterTest.cs b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Adapters/StateEntryTableEntityAdapterTest.cs similarity index 67% rename from test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/StateEntryTableEntityAdapterTest.cs rename to test/Microsoft.Data.Entity.AzureTableStorage.Tests/Adapters/StateEntryTableEntityAdapterTest.cs index ec33de6389b..189f9eaf1d7 100644 --- a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/StateEntryTableEntityAdapterTest.cs +++ b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Adapters/StateEntryTableEntityAdapterTest.cs @@ -2,16 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Microsoft.Data.Entity.AzureTableStorage.Adapters; using Microsoft.Data.Entity.AzureTableStorage.Query; using Microsoft.Data.Entity.ChangeTracking; using Microsoft.Data.Entity.Infrastructure; using Microsoft.Data.Entity.Metadata; using Microsoft.Framework.DependencyInjection; +using Microsoft.WindowsAzure.Storage.Table; using Moq; using Xunit; -namespace Microsoft.Data.Entity.AzureTableStorage.Tests.Query +namespace Microsoft.Data.Entity.AzureTableStorage.Tests.Adapters { public class StateEntryTableEntityAdapterTest { @@ -45,6 +47,12 @@ public class IntKeysPoco public int RowID { get; set; } } + public class ClrPocoWithProp : ClrPoco + { + public string StringProp { get; set; } + public int IntProp { get; set; } + } + private IModel CreateModel() { var model = new Model(); @@ -74,6 +82,15 @@ private IModel CreateModel() pb.RowKey(s => s.RowID); pb.Timestamp("Timestamp", true); }); + builder.Entity() + .Properties(pb => + { + pb.Property(s => s.PartitionKey); + pb.Property(s => s.RowKey); + pb.Property(s => s.Timestamp); + pb.Property(s => s.StringProp); + pb.Property(s => s.IntProp); + }); return model; } @@ -158,5 +175,68 @@ public void It_interprets_guid_keys() Assert.Equal(new Guid("80d401da-ef77-4bc6-a2b0-300025098a0e"), adapter.Entity.PartitionGuid); Assert.Equal(new Guid("4b240e4f-b886-4d23-a63c-017a3d79885a"), adapter.Entity.RowGuid); } + + [Fact] + public void It_reads_from_dictionary() + { + var data = new Dictionary + { + { "StringProp", new EntityProperty("StringVal") }, + { "IntProp", new EntityProperty(5) }, + }; + var instance = new ClrPocoWithProp(); + + var entityType = CreateModel().GetEntityType(typeof(ClrPocoWithProp)); + var entry = _factory.Create(entityType, instance); + var adapter = new StateEntryTableEntityAdapter(entry); + + adapter.ReadEntity(data, null); + + Assert.Equal("StringVal", instance.StringProp); + Assert.Equal(5, instance.IntProp); + } + + [Fact] + public void It_skips_mismatched_types() + { + var data = new Dictionary + { + { "StringProp", new EntityProperty(5) }, + { "IntProp", new EntityProperty("string input") }, + }; + var instance = new ClrPocoWithProp(); + + var entityType = CreateModel().GetEntityType(typeof(ClrPocoWithProp)); + var entry = _factory.Create(entityType, instance); + var adapter = new StateEntryTableEntityAdapter(entry); + + adapter.ReadEntity(data, null); + + Assert.Equal(default(string), instance.StringProp); + Assert.Equal(default(int), instance.IntProp); + } + + [Fact] + public void It_writes_to_dictionary() + { + var instance = new ClrPocoWithProp + { + PartitionKey = "A", + RowKey = "B", + StringProp = "C", + IntProp = 5, + Timestamp = DateTimeOffset.UtcNow, + }; + var entry = _factory.Create(CreateModel().GetEntityType(typeof(ClrPocoWithProp)), instance); + var adapter = new StateEntryTableEntityAdapter(entry); + + var expected = new Dictionary + { + { "IntProp", new EntityProperty(instance.IntProp) }, + { "StringProp", new EntityProperty(instance.StringProp) }, + }; + + Assert.Equal(expected,adapter.WriteEntity(null)); + } } } diff --git a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Adapters/TableEntityAdapterFactoryTests.cs b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Adapters/TableEntityAdapterFactoryTests.cs new file mode 100644 index 00000000000..f2462c25b3d --- /dev/null +++ b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Adapters/TableEntityAdapterFactoryTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Data.Entity.AzureTableStorage.Adapters; +using Microsoft.Data.Entity.AzureTableStorage.Tests.Helpers; +using Microsoft.Data.Entity.Metadata; +using Xunit; + +namespace Microsoft.Data.Entity.AzureTableStorage.Tests.Adapters +{ + public class TableEntityAdapterFactoryTests + { + private EntityType _entityType; + private TableEntityAdapterFactory _factory; + + public TableEntityAdapterFactoryTests() + { + _factory = new TableEntityAdapterFactory(); + _entityType = new EntityType(typeof(PocoTestType)); + _entityType.AddProperty("PartitionKey", typeof(string)); + _entityType.AddProperty("RowKey", typeof(string)); + _entityType.AddProperty("Timestamp", typeof(DateTime)); + } + [Fact] + public void It_creates_state_entry_adapter() + { + var entry = TestStateEntry.Mock().WithType(typeof(PocoTestType)).WithEntity(new PocoTestType()); + var adapter = _factory.CreateFromStateEntry(entry); + + Assert.NotNull(adapter); + Assert.IsType>(adapter); + } + } +} diff --git a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Helpers/TestStateEntry.cs b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Helpers/TestStateEntry.cs index 39ed211338d..233f1b802ec 100644 --- a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Helpers/TestStateEntry.cs +++ b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Helpers/TestStateEntry.cs @@ -95,5 +95,11 @@ public TestStateEntry WithProperty(string property, string value) _propertyBag[property] = value; return this; } + + public TestStateEntry WithEntity(object entity) + { + _entity = entity; + return this; + } } } diff --git a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Microsoft.Data.Entity.AzureTableStorage.Tests.csproj b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Microsoft.Data.Entity.AzureTableStorage.Tests.csproj index 124f0c038ce..a53f923ab6e 100644 --- a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Microsoft.Data.Entity.AzureTableStorage.Tests.csproj +++ b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Microsoft.Data.Entity.AzureTableStorage.Tests.csproj @@ -78,6 +78,8 @@ ApiConsistencyTestBase.cs + + @@ -95,9 +97,9 @@ - + diff --git a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterFactoryTests.cs b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterFactoryTests.cs index 2b4b7c8d4d4..d5036ff3e9b 100644 --- a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterFactoryTests.cs +++ b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterFactoryTests.cs @@ -6,7 +6,6 @@ using Microsoft.Data.Entity.AzureTableStorage.Query; using Microsoft.Data.Entity.AzureTableStorage.Tests.Helpers; using Microsoft.Data.Entity.Metadata; -using Microsoft.Data.Entity.Metadata.Compiled; using Xunit; namespace Microsoft.Data.Entity.AzureTableStorage.Tests.Query @@ -20,8 +19,8 @@ public TableFilterFactoryTests() { _factory = new TableFilterFactory(); _entityType = PocoTestType.EntityType(); - } + [Fact] public void It_makes_member_to_constant_expression() { diff --git a/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterTests.cs b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterTests.cs new file mode 100644 index 00000000000..dd3c69e351c --- /dev/null +++ b/test/Microsoft.Data.Entity.AzureTableStorage.Tests/Query/TableFilterTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; +using Microsoft.Data.Entity.AzureTableStorage.Query; +using Xunit; + +namespace Microsoft.Data.Entity.AzureTableStorage.Tests.Query +{ + public class TableFilterTests + { + [Fact] + public void It_always_uses_string_comparison_for_row_key() + { + var filter = new TableFilter.ConstantTableFilter("RowKey", FilterComparisonOperator.Equal, Expression.Constant(123456)); + Assert.Equal("RowKey eq '123456'",filter.ToString()); + } + + [Fact] + public void It_always_uses_string_comparison_for_partition_key() + { + var filter = new TableFilter.ConstantTableFilter("PartitionKey", FilterComparisonOperator.Equal, Expression.Constant(123456)); + Assert.Equal("PartitionKey eq '123456'", filter.ToString()); + } + + [Fact] + public void Int_literals() + { + var filter = new TableFilter.ConstantTableFilter("Count", FilterComparisonOperator.Equal, Expression.Constant(123456)); + Assert.Equal("Count eq 123456", filter.ToString()); + } + + [Fact] + public void Empty_for_nulls() + { + var filter = new TableFilter.ConstantTableFilter("Name", FilterComparisonOperator.Equal, Expression.Constant(null)); + Assert.Equal(string.Empty, filter.ToString()); + } + } +} \ No newline at end of file