EF CF Отображение сложных отношений с Fluent API - PullRequest
1 голос
/ 15 июля 2011

Я пытаюсь создать следующее ограничение в моей модели, чтобы TagType объекта Tag был действительным. Действительный TagType - это тот, чей OperatingCompanyId совпадает с OperatingCompanyId веб-сайта тега. Я понимаю, что это кажется запутанным, однако это имеет смысл с точки зрения бизнеса:

У операционной компании есть веб-сайты.Сайты содержат теги.Теги имеют TagType (единственное).TagTypes одинаковы во всех операционных компаниях. Это означает, что если у одной операционной компании есть двадцать TagTypes и пять веб-сайтов, эти двадцать TagTypes должны использоваться на всех пяти этих веб-сайтах.Я хочу убедиться, что 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 ]

2 голосов
/ 17 июля 2011

однако ... если я понимаю цель MVC / EF, это иметь это бизнес-логика внутри модели ...

А какую модель вы имеете в виду? Если вы возьмете ASP.NET MVC и EF, вы закончите тремя областями, которые иногда называют моделью:

  • EF модель - это набор классов с отображением в базу данных
  • Model-View-Controller - модель здесь означает что-то (обычно бизнес-логика), используемое вашим контроллером для подготовки данных для просмотра
  • Модель представления - В ASP.NET MVC модель представления - это класс с данными, которыми обмениваются контроллер и представление

Если я смотрю на ваши уроки, я вижу, что первая и третья модели связаны друг с другом (большую часть времени это считается плохой практикой). Ваше понимание верно, но в основном с точки зрения второй модели, которая не представлена ​​вашими классами. Не всякая «бизнес-логика» может быть представлена ​​отображением. Более того, бизнес-логика не является точкой уровня данных.

Ваше отображение частично работает (тип тега связан только с одной действующей компанией), но все же ваш уровень данных не обеспечивает соблюдение всех ваших бизнес-правил. Уровень данных по-прежнему позволяет веб-сайту назначать теги с типом тегов от другой операционной компании, и ваша бизнес-логика должна гарантировать, что этого не произойдет. Избежать этого в базе данных было бы сложно, потому что это, вероятно, потребовало бы сложных первичных ключей и передачи идентификатора операционной компании каждому зависимому объекту.

2 голосов
/ 19 июля 2011

Одним из способов применения этого ограничения является использование новой функции проверки, представленной как часть нового API DbContext в EF 4.1.Вы можете написать собственное правило проверки, чтобы убедиться, что типы тегов для веб-сайта любой компании выбраны из допустимых типов тегов для этой компании.Ниже показано, как это можно сделать:

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 выбросит (ипрервать транзакцию), если метод возвращает ошибку проверки.Вы также можете заранее проверить наличие ошибок проверки, вызвав метод GetValidationErrors класса DbContext, чтобы получить список ошибок проверки в объектах модели, с которыми вы работаете.

Стоит также отметить, что, поскольку вы используете модель своего домена в качестве модели представления для своего уровня MVC, MVC распознает и соблюдает это правило проверки, и вы можете проверить результат проверки, изучив ModelState в контроллере.Так что это действительно проверяется в двух местах: один раз на вашем уровне представления MVC и один раз на заднем конце EF.

Надеюсь, это поможет.

0 голосов
/ 15 июля 2011

На вашем месте я буду использовать бизнес-уровень для фильтрации Tagtype вместо того, чтобы делать такое ограничение в базе данных. Для меня такой подход может быть проще.

...