Избегайте раскрытия свойств частной коллекции в Entity Framework. Принципы DDD - PullRequest
1 голос
/ 15 марта 2019

Я пытаюсь придерживаться принципов DDD для коллекций C # , см. Больше здесь

И я замечаю, что метод построения модели для начального начального числа HasData опирается на метод Add ICollection .Есть ли способ обойти или обмануть этот метод, когда он вызывается из процесса обновления / переноса базы данных?

Все, что я до сих пор делал, чтобы обмануть его, следует по этому пути.

1) Создание оболочкивокруг ICollection с именем ReadOnlyKollection

2) Иметь закрытую ICollection на модели, чтобы не подвергать внешний мир коллекции.

3) Выставлять обертку, делающую устаревшейAdd и некоторые другие методы, которые будут вызывать NotImplementedException, если они используются.

Тем не менее, метод Add, несмотря на устаревшее предупреждение, можно использовать, поскольку он все еще общедоступен и необходим для начального метода HasData, используемого в процессе обновления / переноса базы данных.

Я думаю, по крайней мере, ограничить вызывающие методы изнутри метода Add класса оболочки.

Мне было бы полезно узнать вызывающий член, когда HasData будет запускаться и разрешать только этот методобработать и выдать исключение для любого другого.

Обратите внимание, что CallerMethodNФункцию типа компиляции ame нельзя использовать, поскольку она нарушит контракт интерфейса ICollectoion.

Есть какие-либо идеи, чтобы избежать предоставления свойств частной коллекции Entity Framework в соответствии с принципами DDD?(и все еще есть расширение метода HasData для обновления / переноса процесса базы данных).см. код ниже ..

public interface IReadOnlyKollection<T> : ICollection<T>
{
}

public class ReadOnlyKollection<T> : IReadOnlyKollection<T>
{
    private readonly ICollection<T> _collection;

    public ReadOnlyKollection(ICollection<T> collection)
    {
        _collection = collection;
    }

    public int Count => _collection.Count;
    public bool IsReadOnly => _collection.IsReadOnly;

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    public IEnumerator<T> GetEnumerator() => _collection.GetEnumerator();

    public bool Contains(T item) => _collection.Contains(item);
    public void CopyTo(T[] array, int arrayIndex) => _collection.CopyTo(array, arrayIndex);

    [Obsolete]
    public void Add(T item) => _collection.Add(item); // CallerMethodName trick to be applied here or ??

    [Obsolete] 
    public void Clear() => throw new NotImplementedException();

    [Obsolete] 
    public bool Remove(T item) => throw new NotImplementedException();
}

public class StateProvince
{
    public StateProvince() //EF Constructor
    {
    }

    public StateProvince(string id, string name)
    : this(name)
    {
        Id = id;
    }

    public string Id { get; protected set; }
    public string Name { get; protected set; }

    public string CountryRegionId { get; protected set; }
    public virtual CountryRegion CountryRegion { get; protected set; }
}

public class CountryRegion
{
    public CountryRegion() //EF Constructor
    {
    }

    public CountryRegion(string id, string name)
    : this(name)
    {
        Id = id;
    }

    public string Id { get; protected set; }
    public string Name { get; protected set; }

    private readonly ICollection<StateProvince> _stateProvinces = new List<StateProvince>(); // Private collection for DDD usage
    public IReadOnlyKollection<StateProvince> StateProvinces => new ReadOnlyKollection<StateProvince>(_stateProvinces); // Public like read only collection public immutable exposure
}


EntityTypeBuilder<StateProvince> // Code reduced for brevity

builder.HasIndex(e => e.CountryRegionId);
builder.Property(e => e.Id).IsUnicode(false).HasMaxLength(3).ValueGeneratedNever();
builder.Property(e => e.CountryRegionId).IsRequired().IsUnicode(false).HasMaxLength(3);
builder.Property(e => e.Name).IsRequired().HasMaxLength(50);


EntityTypeBuilder<CountryRegion> builder // Code reduced for brevity

builder.Property(e => e.Id).IsUnicode(false).HasMaxLength(3).ValueGeneratedNever();
builder.Property(e => e.Name).IsRequired().HasMaxLength(50);

builder.HasMany(e => e.StateProvinces)
    .WithOne(e => e.CountryRegion)
    .HasForeignKey(e => e.CountryRegionId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Restrict);

builder.HasData(GetData())  

private static object[] GetData()
{   
    return new object[]
    {
        new { Id = "AF", Name = "Afghanistan", IsDeleted = false, LastModified = DateTimeOffset.UtcNow  },
        new { Id = "AL", Name = "Albania", IsDeleted = false, LastModified = DateTimeOffset.UtcNow  },
        new { Id = "DZ", Name = "Algeria", IsDeleted = false, LastModified = DateTimeOffset.UtcNow  },
        new { Id = "AS", Name = "American Samoa", IsDeleted = false, LastModified = DateTimeOffset.UtcNow  },

1 Ответ

5 голосов
/ 15 марта 2019

Связанный пост предназначен для EF6, а метод HasData указывает на EF Core. А в EF Core все гораздо проще и не нужно никаких хитростей в этом отношении.

  • EF Core не требует ICollection<T> для свойства навигации по коллекции. Любое открытое свойство, возвращающее IEnumerable<T> или производный интерфейс / класс, определяется соглашением как свойство навигации по коллекции. Следовательно, вы можете безопасно выставлять свои коллекции как IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T> и т. Д.

  • EF Core не требует установки свойств, поскольку его можно настроить на непосредственное использование вспомогательного поля .

Кроме того, нет необходимости в специальном «конструкторе EF», поскольку EF Core поддерживает конструкторы с параметрами .

С учетом вышесказанного вам не нужен пользовательский интерфейс / класс коллекции. Пример модели может быть таким:

public class CountryRegion
{
    public CountryRegion(string name) => Name = name;    
    public CountryRegion(string id, string name) : this(name) => Id = id;

    public string Id { get; protected set; }
    public string Name { get; protected set; }

    private readonly List<StateProvince> _stateProvinces = new List<StateProvince>(); // Private collection for DDD usage
    public IReadOnlyCollection<StateProvince> StateProvinces => _stateProvinces.AsReadOnly(); // Public like read only collection public immutable exposure
}

public class StateProvince
{
    public StateProvince(string name) => Name = name;
    public StateProvince(string id, string name) : this(name) => Id = id;

    public string Id { get; protected set; }
    public string Name { get; protected set; }

    public string CountryRegionId { get; protected set; }
    public virtual CountryRegion CountryRegion { get; protected set; }
}

и добавьте либо следующее (самое простое - для всех свойств всех сущностей)

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field);    

или для всех свойств CountryRegion

builder.UsePropertyAccessMode(PropertyAccessMode.Field);

или только для этого свойства навигации

builder.HasMany(e => e.StateProvinces)
    .WithOne(e => e.CountryRegion)
    .HasForeignKey(e => e.CountryRegionId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Restrict)
    .Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);

И это все. Вы сможете использовать все функциональные возможности EF Core, например Include / ThenInclude, «навигацию» внутри запросов LINQ to Entities и т. Д. (Включая HasData). Резервное копирование позволяет EF Core добавлять / удалять элементы при необходимости или даже заменять всю коллекцию (если поле не только для чтения).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...