EF CF 与 Fluent API 映射复杂关系

发布于 2024-11-24 15:50:32 字数 2673 浏览 7 评论 0原文

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

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

I am trying to create the following constraint in my model so that a Tag object's TagType is valid. A valid TagType is one whose OperatingCompanyId matches the Tag's Website's OperatingCompanyId. I realize that this seems convoluted however it makes sense from a business standpoint:

An Operating Company has WebSites. Websites contain Tags. Tags have a TagType(singular). TagTypes are the same across Operating Companies, meaning that if one Operating Company has twenty TagTypes and five WebSites, those twenty TagTypes should be able to be used across all fives of those WebSites. I want to ensure that a Tag's TagType cannot be one associated with another OperatingCompany.

What is the best way to create this constraint in the model? Do I need to change my POCO, or use the Fluent API?

Thanks in advance!

[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; }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

北城半夏 2024-12-01 15:50:32

强制执行此约束的一种方法是利用作为 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 都会调用该验证方法,如果该方法返回,则 EF 将抛出异常(并中止事务)任何验证错误。您还可以通过调用 DbContext 类上的 GetValidationErrors 方法来主动检查验证错误,以检索您正在使用的模型对象中的验证错误列表。

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

希望这有帮助。

One way to enforce this constraint is to take advantage of the new validation feature introduced as part of new DbContext API in EF 4.1. You can write a custom validation rule to make sure that tag types for any given company's website are selected from the valid tag types for that company. The following shows how it can be done:

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);
    }
}

As a result, EF will invoke that validate method each time you call DbContext.SaveChanges() to save an OperatingCompany object into database and EF will throw (and abort the transaction) if the method yields back any validation error. You can also proactively check for validation errors by calling the GetValidationErrors method on the DbContext class to retrieve a list of validation errors within the model objects you are working with.

It also worth noting that since you use your domain model as also a View Model for your MVC layer, MVC will recognize and honor this Validation rule and you can check for the validation result by looking into the ModelState in the controller. So it really get checked in two places, once in your presentation layer by MVC and once in the back end by EF.

Hope this helps.

向日葵 2024-12-01 15:50:32

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

你的意思是什么模型?如果您采用 ASP.NET MVC 和 EF,您将得到三个有时称为模型的区域:

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

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

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

however... if I understand the purpose of MVC / EF it is to have that
business logic inside of the Model...

And what model do you mean? If you take ASP.NET MVC and EF you will end with three areas which are sometimes called model:

  • EF model - that is set of classes with their mapping to database
  • Model-View-Controller - model here means something (usually business logic) consumed by your controller to prepare data for view
  • View model - In ASP.NET MVC view model is class with data exchanged between controller and view

If I look at your classes I see first and third model coupled together (most of the time this is considered as a bad practice). Your understanding is correct but mostly in terms of second model which is not represented by your classes. Not every "business logic" can be represented by mapping. Moreover it is not a point of data layer to do business logic.

Your mapping partially works (tag type is related only to one operating company) but still your data layer doesn't enforce all your business rules. Data layer still allows web site to have assigned tag with tag type from different operating company and your business logic must ensure that this will not happen. Avoiding this in database would be complicated because it would probably require complex primary keys and passing operating company Id to every dependent object.

夏末染殇 2024-12-01 15:50:32

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

If I were you, I will use business layer to filter Tagtype instead of do such constraint in database. For me that approach may be easier.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文