entity-framework - 如何在 Entity Framework 中维护有序列表?

标签 entity-framework .net-4.0

改变简单列表中元素的顺序,不坚持 Entity Framework 。原因很简单,因为订购信息从未存储在数据库中。

有没有人遇到过可以与 Entity Framework 一起使用的有序列表的通用实现?

要求是允许用户对所选项目的列表进行重新排序,并且需要保留项目的排序。

最佳答案

概述

尽管似乎没有任何“魔法”来实现这一点,但我们已经使用了一种模式来解决这个问题,尤其是在处理对象层次结构时。归结为三个关键点:

  • 构建与域模型分开的实体模型。这样做的好处是提供了良好的关注点分离,有效地允许您设计和更改域模型,而不会被持久性细节所困扰。
  • 使用 AutoMapper 克服实体和领域模型之间映射的麻烦。
  • 实现自定义值解析器以在两个方向映射列表。

  • 魔法

    因为模型通常包含对象之间的分层和循环引用,以下 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; }
    }
    

    Entity Framework 上下文
    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);
        }
    }
    

    瞧。希望有帮助!

    关于entity-framework - 如何在 Entity Framework 中维护有序列表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15059298/

    相关文章:

    c# - 无法在 DataGridView 单元格 BackColor 中使用 Color.FromArgb

    c# - Entity Framework : Insert causes violation of PRIMARY KEY constraint using Azure SQL, 不是本地的

    entity-framework - 如何管理生产/CI 中的数据库上下文更改

    c# - 将 Insert into select 翻译成 Entity Framework 6

    c# - 带有 0x3B 代码的 Windows 消息

    c# - Visual Studio 与 EXE 文件中的 DLL 加载时间不同

    .net - 是否可以连接到 WCF 缓存提供程序?

    entity-framework - 没有外键的数据库上的 Entity Framework

    c# - LINQ to Entities 和空字符串

    c# - 使用 Task 执行长时间运行的进程,如果有则显示 Exception