【问题标题】:EF CF Mapping complex relationship with Fluent APIEF CF Mapping 与 Fluent API 的复杂关系
【发布时间】:2011-10-06 09:19:02
【问题描述】:

我正在尝试在我的模型中创建以下约束,以便 Tag 对象的 TagType 有效。 有效的 TagType 是 OperatingCompanyId 与标签网站的 OperatingCompanyId 匹配的类型。我意识到这看起来很复杂,但从业务角度来看它是有道理的:

一家运营公司拥有网站。网站包含标签。标签有一个 TagType(单数)。 TagType 在运营公司中是相同的,这意味着如果一家运营公司有 20 个 TagType 和 5 个 WebSite,那么这 20 个 TagType 应该能够在所有 5 个 WebSite 中使用。我想确保一个 Tag 的 TagType 不能与另一个 OperatingCompany 关联。

在模型中创建此约束的最佳方法是什么?我需要更改我的 POCO,还是使用 Fluent API?

提前致谢!

[Table("OperatingCompanies")]
public class OperatingCompany : ConfigObject
{
    public OperatingCompany()
    {
        WebSites = new List<WebSite>();
    }

    [Required(ErrorMessage = "Name is a required field for an operating company.")]
    [MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters.")]
    public string Name { get; set; }

    public virtual ICollection<WebSite> WebSites { get; set; }
}

[Table("Websites")]
public class WebSite : ConfigObject
{
    public WebSite()
    {
        WebObjects = new List<WebObject>();
    }

    [Required(ErrorMessage = "URL is a required field for a web site.")]
    [MaxLength(100, ErrorMessage = "URL cannot exceed 100 characters for a web site.")]
    [RegularExpression(@"\b(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|]", ErrorMessage = "The value entered is not a valid URL.")]
    public string Url { get; set; }

    public OperatingCompany OperatingCompany { get; set; }

    [Required(ErrorMessage = "You must associate a web site with an operating company.")]
    public Guid OperatingCompanyId { get; set; }

    [InverseProperty("Website")]
    public virtual ICollection<WebObject> WebObjects { get; set; }
}

[Table("Tags")]
public class Tag : ConfigObject
{
    [Required(ErrorMessage = "Name is a required field for a tag.")]
    [MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters for a tag.")]
    public string Name { get; set; }

    public TagType TagType { get; set; }

    [Required(ErrorMessage = "You must associate a tag with a tag type.")]
    public Guid TagTypeId { get; set; }

    public WebSite WebSite { get; set; }

    [Required(ErrorMessage = "You must associate a tag with a web site.")]
    public Guid WebSiteId { get; set; }
}

[Table("TagTypes")]
public class TagType : ConfigObject
{
    [Required(ErrorMessage = "Name is a required field for a tag.")]
    [MaxLength(100, ErrorMessage = "Name cannot exceed 100 characters for a tag type.")]
    public string Name { get; set; }

    public OperatingCompany OperatingCompany { get; set; }

    [Required(ErrorMessage = "You must associate a tag type with an operating company.")]
    public Guid OperatingCompanyId { get; set; }
}

【问题讨论】:

    标签: entity-framework ef-code-first fluent-interface object-relationships


    【解决方案1】:

    实施此约束的一种方法是利用作为 EF 4.1 中新 DbContext API 的一部分引入的新验证功能。您可以编写自定义验证规则,以确保从该公司的有效标签类型中选择任何给定公司网站的标签类型。下面展示了它是如何完成的:

    public abstract class ConfigObject
    {
        public Guid Id { get; set; }
    }
    
    public class OperatingCompany : ConfigObject, IValidatableObject
    {
        public string Name { get; set; }
    
        public virtual ICollection<WebSite> WebSites { get; set; }
        public virtual List<TagType> TagTypes { get; set; }
    
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var allTagTypes = (from w in WebSites from t in w.Tags select t.TagType);
    
            if (!allTagTypes.All(wtt => TagTypes.Exists(tt => tt.Id == wtt.Id)))
            {
                yield return new ValidationResult("One or more of the website's tag types don't belong to this company");
            }            
        }
    }
    
    public class WebSite : ConfigObject
    {
        public string Url { get; set; }                
        public Guid OperatingCompanyId { get; set; }
    
        public virtual ICollection<Tag> Tags { get; set; }
        public OperatingCompany OperatingCompany { get; set; }                
    }
    
    public class Tag : ConfigObject
    {
        public string Name { get; set; }
        public Guid TagTypeId { get; set; }
        public Guid WebSiteId { get; set; } 
    
        public TagType TagType { get; set; }               
        public WebSite WebSite { get; set; }
    }
    
    public class TagType : ConfigObject
    {
        public string Name { get; set; }
        public Guid OperatingCompanyId { get; set; }
    
        public OperatingCompany OperatingCompany { get; set; }                
    }
    
    public class Context : DbContext
    {
        public DbSet<OperatingCompany> OperatingCompanies { get; set; }
        public DbSet<WebSite> WebSites { get; set; }
        public DbSet<Tag> Tags { get; set; }
        public DbSet<TagType> TagTypes { get; set; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Tag>().HasRequired(t => t.WebSite)
                                      .WithMany(w => w.Tags)
                                      .HasForeignKey(t => t.WebSiteId)
                                      .WillCascadeOnDelete(false);
        }
    }
    

    因此,EF 将在您每次调用 DbContext.SaveChanges() 以将 OperatingCompany 对象保存到数据库时调用该验证方法,如果该方法返回任何验证错误,EF 将抛出(并中止事务)。您还可以通过调用 DbContext 类上的 GetValidationErrors 方法来主动检查验证错误,以检索您正在使用的模型对象中的验证错误列表。

    还值得注意的是,由于您将域模型也用作 MVC 层的视图模型,因此 MVC 将识别并遵守此验证规则,您可以通过查看 ModelState 来检查验证结果 em> 在控制器中。所以它确实会在两个地方进行检查,一次在 MVC 的表示层中,一次在 EF 的后端。

    希望这会有所帮助。

    【讨论】:

    • 这正是我所追求的完全 - 非常感谢 Morteza!仅供参考 - 任何看到此内容的人...如果您对实体框架/代码优先有其他问题,我强烈建议您查看 Morteza Manavi 的博客,因为他在这里有很多很好的示例和解释,很有意义:weblogs.asp.net/manavi
    【解决方案2】:

    但是...如果我了解 MVC / EF 的目的,那就是 Model 内部的业务逻辑...

    你的意思是什么型号?如果你使用 ASP.NET MVC 和 EF,你会以三个有时被称为模型的区域结束:

    • EF 模型 - 一组类及其到数据库的映射
    • Model-View-Controller - 这里的模型是指控制器使用的东西(通常是业务逻辑),用于为视图准备数据
    • 视图模型 - 在 ASP.NET MVC 中,视图模型是在控制器和视图之间交换数据的类

    如果我查看您的课程,我会看到第一个和第三个模型耦合在一起(大多数时候这被认为是一种不好的做法)。您的理解是正确的,但主要是在您的类没有代表的第二个模型方面。并不是每一个“业务逻辑”都可以用映射来表示。而且做业务逻辑也不是数据层的一个点。

    您的映射部分有效(标签类型仅与一家运营公司相关),但您的数据层仍不能强制执行您的所有业务规则。数据层仍然允许网站分配来自不同运营公司的标签类型的标签,您的业务逻辑必须确保不会发生这种情况。在数据库中避免这种情况会很复杂,因为它可能需要复杂的主键并将运营公司 ID 传递给每个依赖对象。

    【讨论】:

    • 我的课程到底有什么“不好的做法”?我意识到这个模型目前允许标签具有来自不同运营公司的标签类型,虽然它可能很复杂,但这肯定会受到数据库中外键的约束;我只是不知道如何让 CF 通过 fluent API 来了解这一点。
    • 这个网络上有很多人认为将实体模型与 UI 验证混为一谈是不好的做法。
    • 我确定有。但是您没有解释原因,也没有回答我的问题。
    • 为什么?阅读关注点分离。通常你不应该在单个类中搞乱映射和验证。对于非常简单和小型的项目来说,这还可以,但一旦你做了更复杂的事情,你很快就会发现它不起作用,因为你的各个 UI 屏幕可能需要稍微不同的验证逻辑。
    • 你没有回答我的问题——你说的是关注点分离,即使我做了你建议的改变,也不能解决我原来的问题:如何在 MVC3 中创建这个约束?
    【解决方案3】:

    如果我是你,我会使用业务层来过滤 Tagtype,而不是在数据库中做这样的约束。对我来说,这种方法可能更容易。

    【讨论】:

    • 感谢 John 的回复,但是...如果我了解 MVC / EF 的目的,就是在模型中包含该业务逻辑...我一定会在视图中添加代码以过滤器,但我想在模型中强制执行约束。这有意义吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-06
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 1970-01-01
    相关资源
    最近更新 更多