単純なリスト内の要素の順序を変更しても、EntityFrameworkに固執しません。注文情報がデータベースに保存されることはないため、理由は非常に単純です。
Entity Frameworkと連携して機能する順序付きリストの一般的な実装に出くわした人はいますか?
要件は、ユーザーが選択したアイテムのリストを並べ替えることができることであり、アイテムの順序を保持する必要があります。
単純なリスト内の要素の順序を変更しても、EntityFrameworkに固執しません。注文情報がデータベースに保存されることはないため、理由は非常に単純です。
Entity Frameworkと連携して機能する順序付きリストの一般的な実装に出くわした人はいますか?
要件は、ユーザーが選択したアイテムのリストを並べ替えることができることであり、アイテムの順序を保持する必要があります。
これを実装するための「魔法」はないようですが、特にオブジェクトの階層を処理する場合に、この問題を解決するために使用したパターンがあります。それは3つの重要なことに要約されます:
モデルにはオブジェクト間の階層的および循環的な参照が含まれることが多いためMap<>()
、カスタムマッピング中のStackOverflowエラーを回避するために次のメソッドを使用できます
private class ResolveToDomain : IValueResolver
{
ResolutionResult Resolve(ResolutionResult rr)
{
//...
((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
//...
}
}
ドメインモデル。サブプロダクトリストの順序が重要であることに注意してください。
class Product
{
public Product ParentProduct { get; set; }
public string Name { get; set; }
public IList<Product> Subproducts { get; set; }
}
エンティティモデル
class ProductEntity
{
public int Id { get; set; }
public ProductEntity ParentProduct { get; set; }
public string Name { get; set; }
public IList<ProductSubproductEntity> Subproducts { get; set; }
}
class ProductSubproductEntity
{
public int ProductId { get; set; }
public ProductEntity Product { get; set; }
public int Order { get; set; }
public ProductEntity Subproduct { get; set; }
}
エンティティフレームワークコンテキスト
class Context : DbContext
{
public DbSet<ProductEntity> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductEntity>()
.HasOptional(e => e.ParentProduct);
modelBuilder.Entity<ProductSubproductEntity>()
.HasKey(e => new {e.ProductId, e.Order})
.HasRequired(e => e.Product)
.WithMany(e => e.Subproducts)
.HasForeignKey(e => e.ProductId);
base.OnModelCreating(modelBuilder);
}
}
AutoMapper構成
class Mappings : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Product, ProductEntity>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductResolver>());
Mapper.CreateMap<ProductEntity, Product>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductEntityResolver>());
base.Configure();
}
}
class ProductSubproductResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var result = new List<ProductSubproductEntity>();
var subproductsSource = ((Product) rr.Context.SourceValue).Subproducts;
if (subproductsSource == null) return rr.New(null);
for (int i = 0; i < subproductsSource.Count; i++)
{
var subProduct = subproductsSource[i];
result.Add(new ProductSubproductEntity()
{
Product = (ProductEntity)rr.Context.DestinationValue,
Order = i,
Subproduct = ((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
});
}
return rr.New(result);
}
}
class ProductSubproductEntityResolver: IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var subproductEntitiesSource = ((ProductEntity) rr.Context.SourceValue).Subproducts;
if (subproductEntitiesSource == null) return rr.New(null);
var result = subproductEntitiesSource.OrderBy(p => p.Order).Select(p =>
((MappingEngine) rr.Context.Engine).Map<ProductEntity, Product>(rr.Context, p.Subproduct))
.ToList();
return rr.New(result);
}
}
使用法
private static IList<Product> CreateDomainProducts()
{
var result = new List<Product>();
var mainProduct1 = new Product()
{
Name = "T-Shirt"
};
var subProduct1 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Medium)",
};
var subProduct2 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Large)",
};
mainProduct1.Subproducts = new []
{
subProduct1,
subProduct2
};
var mainProduct2 = new Product()
{
Name = "Shorts"
};
result.Add(mainProduct1);
result.Add(mainProduct2);
return result;
}
static void Main(string[] args)
{
Mapper.Initialize(a => a.AddProfile<Mappings>());
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
var products = CreateDomainProducts();
var productEntities = Mapper.Map<IList<ProductEntity>>(products);
using (var ctx = new Context())
{
ctx.Products.AddRange(productEntities);
ctx.SaveChanges();
}
// Simulating a disconnected scenario...
using (var ctx = new Context())
{
var productEntity = ctx.Products
.Include(p => p.Subproducts)
.Include(p => p.Subproducts.Select(p2 => p2.Subproduct))
.OrderBy(p=>p.Name)
.ToList();
var productsResult = Mapper.Map<IList<Product>>(productEntity);
// Should be 'T-Shirt (Medium)'
Console.WriteLine(productsResult[1].Subproducts[0].Name);
// Should be 'T-Shirt (Large)'
Console.WriteLine(productsResult[1].Subproducts[1].Name);
}
}
出来上がり。お役に立てば幸いです。
ここには魔法はありません。リスト内のアイテムの特定の順序(名前などによる再現可能な順序以外)を保持する場合は、データベースにシーケンス番号を保存する必要があります。
データベースで並べ替えるためのこれの実装はありません。データベース内のデータは、デフォルトでクラスター化インデックスによって物理的に順序付けられます。クラスター化インデックスは、基本的に主キーによって順序付けられます。
なぜあなたはこれをしたいのですか?EFは、すべての順序付けがLINQクエリを介して行われることを推奨しています。
ルックアップを最適化する場合は、移行用に生成されたコードを変更することで、データベースに追加の非クラスター化インデックスを作成できます。
CreateTable(
"dbo.People",
c => new
{
ID = c.Int(nullable: false, identity: true),
Name = c.String()
})
.PrimaryKey(t => t.ID)
.Index(t => t.Name); // Create an index
これはデータベース内の物理的な順序には影響しませんが、検索を高速化することに注意してください。ただし、書き込み/更新を遅くすることでバランスを取る必要があります。
この課題の解決策を見つけるために、私は次のリンクの記事に直面しました。
この記事では、リストの順序を変更するときに順序インデックス値を生成するためのさまざまなアプローチを分析しました。この記事で言及されているアルゴリズムは、最小限の制限で非常にパフォーマンスが高いことがわかりました。このアルゴリズムはTrueFractionsと呼ばれ、次の図のような順序インデックスを生成します。
EFCoreとInMemoryデータベースによってこのアプローチを実装するコードサンプルを用意しました。