更改简单列表中元素的顺序,不会停留在实体框架中。原因很简单,因为订购信息从未存储在数据库中。
有没有人遇到过可以与实体框架一起使用的有序列表的通用实现?
要求是允许用户重新排序所选项目的列表,并且需要保留项目的排序。
更改简单列表中元素的顺序,不会停留在实体框架中。原因很简单,因为订购信息从未存储在数据库中。
有没有人遇到过可以与实体框架一起使用的有序列表的通用实现?
要求是允许用户重新排序所选项目的列表,并且需要保留项目的排序。
尽管似乎没有任何“魔法”来实现这一点,但我们已经使用了一种模式来解决这个问题,尤其是在处理对象层次结构时。它归结为三个关键点:
因为模型中经常包含对象之间的分层和循环引用,所以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 查询完成所有排序。
如果您希望优化查找,您可以通过修改为 Migrations 生成的代码在数据库上创建其他非聚集索引:
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
请注意,这不会影响数据库中的物理顺序,但会加快查找速度,尽管这需要通过较慢的写入/更新来平衡。
为了找到解决这个挑战的方法,我通过以下链接看到了一篇文章:
本文分析了在改变列表顺序时产生顺序索引值的不同方法。我发现本文中提到的算法在最小限制下非常高效。该算法称为True Fractions,它生成的订单索引如下图所示:
我准备了一个代码示例,我通过 EF Core 和 InMemory 数据库实现了这种方法。