Skip to content

Commit

Permalink
Add data seeding support for owned types
Browse files Browse the repository at this point in the history
Part of #629
  • Loading branch information
AndriySvyryd committed Oct 6, 2017
1 parent f711fbe commit ad8ae06
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 34 deletions.
11 changes: 1 addition & 10 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
Expand Up @@ -369,16 +369,7 @@ public new virtual EntityTypeBuilder<TEntity> UsePropertyAccessMode(PropertyAcce
/// An array of seed data.
/// </param>
public virtual EntityTypeBuilder<TEntity> SeedData([NotNull] params TEntity[] data)
{
foreach (var entity in data)
{
if (entity.GetType() != typeof(TEntity))
{
throw new InvalidOperationException(CoreStrings.SeedDatumDerivedType(Metadata.DisplayName(), entity.GetType().ShortDisplayName()));
}
}
return (EntityTypeBuilder<TEntity>)base.SeedData(data);
}
=> (EntityTypeBuilder<TEntity>)base.SeedData(data);

private InternalEntityTypeBuilder Builder => this.GetInfrastructure<InternalEntityTypeBuilder>();
}
Expand Down
15 changes: 15 additions & 0 deletions src/EFCore/Metadata/Builders/ReferenceOwnershipBuilder.cs
Expand Up @@ -593,5 +593,20 @@ public virtual ReferenceOwnershipBuilder UsePropertyAccessMode(PropertyAccessMod

return this;
}

/// <summary>
/// Configures this entity to have seed data. It is used to generate data motion migrations.
/// </summary>
/// <param name="data">
/// An array of seed data of the same type as the entity we're building.
/// </param>
public virtual ReferenceOwnershipBuilder SeedData([NotNull] params object[] data)
{
Check.NotNull(data, nameof(data));

OwnedEntityType.AddSeedData(data);

return this;
}
}
}
9 changes: 9 additions & 0 deletions src/EFCore/Metadata/Builders/ReferenceOwnershipBuilder`.cs
Expand Up @@ -466,5 +466,14 @@ public virtual IndexBuilder HasIndex([NotNull] Expression<Func<TRelatedEntity, o
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual ReferenceOwnershipBuilder<TEntity, TRelatedEntity> UsePropertyAccessMode(PropertyAccessMode propertyAccessMode)
=> (ReferenceOwnershipBuilder<TEntity, TRelatedEntity>)base.UsePropertyAccessMode(propertyAccessMode);

/// <summary>
/// Configures this entity to have seed data. It is used to generate data motion migrations.
/// </summary>
/// <param name="data">
/// An array of seed data.
/// </param>
public virtual ReferenceOwnershipBuilder<TEntity, TRelatedEntity> SeedData([NotNull] params TRelatedEntity[] data)
=> (ReferenceOwnershipBuilder<TEntity, TRelatedEntity>)base.SeedData(data);
}
}
15 changes: 14 additions & 1 deletion src/EFCore/Metadata/Internal/EntityType.cs
Expand Up @@ -1812,7 +1812,20 @@ public override void OnTypeMemberIgnored(string name)
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual void AddSeedData([NotNull] params object[] data) => _seedData.UnionWith(data);
public virtual void AddSeedData([NotNull] params object[] data)
{
foreach (var entity in data)
{
if (ClrType != null
&& ClrType != entity.GetType()
&& ClrType.GetTypeInfo().IsAssignableFrom(entity.GetType().GetTypeInfo()))
{
throw new InvalidOperationException(CoreStrings.SeedDatumDerivedType(
this.DisplayName(), entity.GetType().ShortDisplayName()));
}
}
_seedData.UnionWith(data);
}

#endregion

Expand Down
Expand Up @@ -448,6 +448,109 @@ public void Can_split_entity_in_two_using_shared_table_with_seed_data()
});
}

[Fact]
public void Add_owned_type_with_seed_data()
{
Execute(
modelBuilder =>
{
modelBuilder.Entity(
"Order",
x =>
{
x.Property<int>("Id");
x.SeedData(new { Id = 42 });
});
},
_ => { },
modelBuilder =>
{
modelBuilder.Entity(
"Order",
x =>
{
x.OwnsOne("Address", "ShippingAddress", s =>
{
s.Property<string>("Street");
s.Property<string>("City");
s.SeedData(new { OrderId = 42, Street = "Lombard", City = "San Francisco" });
});
x.OwnsOne("Address", "BillingAddress", s =>
{
s.Property<string>("Street");
s.Property<string>("City");
s.SeedData(new { OrderId = 42, Street = "Abbey Road", City = "London" });
});
});
},
upOps => Assert.Collection(upOps,
o =>
{
var m = Assert.IsType<AddColumnOperation>(o);
Assert.Equal("BillingAddress_City", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<AddColumnOperation>(o);
Assert.Equal("BillingAddress_Street", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<AddColumnOperation>(o);
Assert.Equal("ShippingAddress_City", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<AddColumnOperation>(o);
Assert.Equal("ShippingAddress_Street", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<UpdateDataOperation>(o);
AssertMultidimensionalArray(m.KeyValues,
v => Assert.Equal(42, v));
AssertMultidimensionalArray(m.Values,
v => Assert.Equal("London", v),
v => Assert.Equal("Abbey Road", v),
v => Assert.Equal("San Francisco", v),
v => Assert.Equal("Lombard", v));
Assert.Collection(m.Columns,
v => Assert.Equal("BillingAddress_City", v),
v => Assert.Equal("BillingAddress_Street", v),
v => Assert.Equal("ShippingAddress_City", v),
v => Assert.Equal("ShippingAddress_Street", v));
}),
downOps => Assert.Collection(downOps,
o =>
{
var m = Assert.IsType<DropColumnOperation>(o);
Assert.Equal("BillingAddress_City", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<DropColumnOperation>(o);
Assert.Equal("BillingAddress_Street", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<DropColumnOperation>(o);
Assert.Equal("ShippingAddress_City", m.Name);
Assert.Equal("Order", m.Table);
},
o =>
{
var m = Assert.IsType<DropColumnOperation>(o);
Assert.Equal("ShippingAddress_Street", m.Name);
Assert.Equal("Order", m.Table);
}));
}

[Fact]
public void Rename_entity_type_with_seed_data()
{
Expand Down Expand Up @@ -5656,27 +5759,6 @@ private class Post
public Blog Blog { get; set; }
}

private void AssertMultidimensionalArray<T>(T[,] values, params Action<T>[] assertions)
{
Assert.Collection(ToOnedimensionalArray(values), assertions);
}

private static T[] ToOnedimensionalArray<T>(T[,] values, bool firstDimension = false)
{
Debug.Assert(values.GetLength(firstDimension ? 1 : 0) == 1,
$"Length of dimension {(firstDimension ? 1 : 0)} is not 1.");

var result = new T[values.Length];
for (var i = 0; i < values.Length; i++)
{
result[i] = firstDimension
? values[i, 0]
: values[0, i];
}

return result;
}

protected override ModelBuilder CreateModelBuilder() => RelationalTestHelpers.Instance.CreateConventionBuilder();
}
}
Expand Up @@ -3,13 +3,15 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.EntityFrameworkCore.Update.Internal;
using Xunit;

namespace Microsoft.EntityFrameworkCore.Migrations.Internal
{
Expand Down Expand Up @@ -59,6 +61,27 @@ public abstract class MigrationsModelDifferTestBase
}
}

protected void AssertMultidimensionalArray<T>(T[,] values, params Action<T>[] assertions)
{
Assert.Collection(ToOnedimensionalArray(values), assertions);
}

protected static T[] ToOnedimensionalArray<T>(T[,] values, bool firstDimension = false)
{
Debug.Assert(values.GetLength(firstDimension ? 1 : 0) == 1,
$"Length of dimension {(firstDimension ? 1 : 0)} is not 1.");

var result = new T[values.Length];
for (var i = 0; i < values.Length; i++)
{
result[i] = firstDimension
? values[i, 0]
: values[0, i];
}

return result;
}

protected abstract ModelBuilder CreateModelBuilder();

protected virtual MigrationsModelDiffer CreateModelDiffer(DbContext ctx)
Expand Down
56 changes: 54 additions & 2 deletions test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs
Expand Up @@ -12,7 +12,6 @@
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.Update.Internal;
using Xunit;

Expand Down Expand Up @@ -314,6 +313,59 @@ public void Create_shared_table_with_two_entity_types()
});
}

[Fact]
public void Add_SequenceHiLo_with_seed_data()
{
Execute(
common => common.Entity(
"Firefly",
x =>
{
x.ToTable("Firefly", "dbo");
x.Property<int>("Id");
x.Property<int>("SequenceId");
x.SeedData(new { Id = 42 });
}),
_ => { },
target => target.Entity(
"Firefly",
x =>
{
x.ToTable("Firefly", "dbo");
x.Property<int>("SequenceId").ForSqlServerUseSequenceHiLo(schema: "dbo");
x.SeedData(
new { Id = 42 },
new { Id = 43 });
}),
upOps => Assert.Collection(upOps,
o =>
{
var operation = Assert.IsType<CreateSequenceOperation>(o);
Assert.Equal("dbo", operation.Schema);
Assert.Equal("EntityFrameworkHiLoSequence", operation.Name);
},
o =>
{
var m = Assert.IsType<InsertDataOperation>(o);
AssertMultidimensionalArray(m.Values,
v => Assert.Equal(43, v),
v => Assert.Equal(0, v));
}),
downOps => Assert.Collection(downOps,
o =>
{
var operation = Assert.IsType<DropSequenceOperation>(o);
Assert.Equal("dbo", operation.Schema);
Assert.Equal("EntityFrameworkHiLoSequence", operation.Name);
},
o =>
{
var m = Assert.IsType<DeleteDataOperation>(o);
AssertMultidimensionalArray(m.KeyValues,
v => Assert.Equal(43, v));
}));
}

[Fact]
public void Alter_index_clustering()
{
Expand All @@ -334,7 +386,7 @@ public void Alter_index_clustering()
x.ToTable("Mutton", "bah");
x.Property<int>("Id");
x.Property<int>("Value");
x.HasIndex("Value").ForSqlServerIsClustered(true);
x.HasIndex("Value").ForSqlServerIsClustered();
}),
operations =>
{
Expand Down
10 changes: 10 additions & 0 deletions test/EFCore.Tests/Infrastructure/CoreModelValidatorTest.cs
Expand Up @@ -559,6 +559,16 @@ public virtual void Detects_derived_seeds()
Assert.Throws<InvalidOperationException>(() => modelBuilder.Entity<A>().SeedData(new D { Id = 2, P0 = 3 })).Message);
}

[Fact]
public virtual void Detects_derived_seeds_for_owned_types()
{
var modelBuilder = CreateModelBuilder();

Assert.Equal(CoreStrings.SeedDatumDerivedType("B.A#A", nameof(D)),
Assert.Throws<InvalidOperationException>(() => modelBuilder.Entity<B>()
.OwnsOne(b => b.A, a => a.SeedData(new D { Id = 2, P0 = 3 }))).Message);
}

[Fact]
public virtual void Detects_missing_required_values_in_seeds()
{
Expand Down
2 changes: 2 additions & 0 deletions test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
Expand Up @@ -69,6 +69,8 @@ protected class B
public int? P1 { get; set; }
public int? P2 { get; set; }
public int? P3 { get; set; }

public A A { get; set; }
}

protected class D : A
Expand Down

0 comments on commit ad8ae06

Please sign in to comment.