EF CF Mapping与Fluent API的复杂关系

时间:2011-07-15 18:13:58

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

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

运营公司拥有网站。网站包含标签。标签有TagType(单数)。运营公司的TagTypes是相同的,这意味着如果一个运营公司有20个TagTypes和5个网站,那么这20个TagTypes应该可以在这些网站的所有五个网站中使用。我想确保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; }
}

3 个答案:

答案 0 :(得分:2)

  

然而......如果我理解MVC / EF的目的就是拥有它   模型中的业务逻辑......

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

  • EF模型 - 这是一组具有映射到数据库的类
  • 模型 - 视图 - 控制器 - 这里的模型意味着控制器为视图准备数据所消耗的东西(通常是业务逻辑)
  • 查看模型 - 在ASP.NET中,MVC视图模型是在控制器和视图之间交换数据的类

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

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

答案 1 :(得分:2)

实施此约束的一种方法是利用作为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);
    }
}

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

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

希望这有帮助。

答案 2 :(得分:0)

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