Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework read only collections

Consider a domain where a Customer, Company, Employee, etc, etc, have a ContactInfo property which in turn contains a set of Address(es), Phone(es), Email(s), etc, etc...

Here is my abbreviated ContactInfo:

public class ContactInfo : Entity<int>
{
    public ContactInfo()
    {
        Addresses = new HashSet<Address>();          
    }

    public virtual ISet<Address> Addresses { get ; private set; }

    public Address PrimaryAddress
    {
        get { return Addresses.FirstOrDefault(a => a.IsPrimary); }
    }

    public bool AddAddress(Address address)
    {
        // insure there is only one primary address in collection
        if (address.IsPrimary)
        {                  
            if (PrimaryAddress != null)
            {
                PrimaryAddress.IsPrimary = false;
            }
        }
        else
        {
            // make sure the only address in collection is primary
            if (!Addresses.Any())
            {
                address.IsPrimary = true;
            }
        }
        return Addresses.Add(address);
    }
}

Some notes (I am not 100% sure if these are EF "best practices"):

  • collection of Address(es) is virtual to allow for lazy loading
  • private setter on collection prohibits collection replacement
  • collection is an ISet to insure that there are no duplicate addresses per contact
  • using AddAddress method I can insure that there is always and at most 1 address which is primary....

I would like (if possible) to prevent adding Addresses via ContactInfo.Addresses.Add() method and to force using of ContactInfo.AddAddress(Address address)...

I am thinking exposing the set of addresses via ReadOnlyCollection but will this work with Entity Framework (v5)?

How would I go about this?

like image 496
zam6ak Avatar asked Jun 25 '12 14:06

zam6ak


People also ask

What is read-only collection?

ReadOnlyCollection makes an array or List read-only. With this type from System. Collections. ObjectModel, we provide a collection of elements that cannot be changed.

What is IReadOnlyList C#?

The IReadOnlyList<T> represents a list in which the number and order of list elements is read-only. The content of list elements is not guaranteed to be read-only.

Does Entity Framework cache data?

Entity Framework has the following forms of caching built-in: Object caching – the ObjectStateManager built into an ObjectContext instance keeps track in memory of the objects that have been retrieved using that instance. This is also known as first-level cache.


1 Answers

Another option suggested by Edo van Asseldonk is to create a custom collection that inherits its behavior from Collection.

You'd have to make your own implementation for ISet but the principle is the same.

By hiding any methods that modifies the list and marking them as obsolete you effectively get a ReadOnlyCollection but EF will still be able to modify it when it's unboxed as Collection. In my version I've added an implicit operator conversion for List so we don't have to unbox the collection when adding items:

var list = ListProperty.ToList();
list.Add(entity)
ListProperty = list;

Where

public virtual EntityCollection<MyEntity> ListProperty { get; protected set; }

and here's the EntityCollection:

public class EntityCollection<T> : Collection<T>
{
    [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)]
    public new void Add(T item) { }

    [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)]
    public new void Clear() { }

    [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)]
    public new void Insert(int index, T item) { }

    [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)]
    public new void Remove(T item) { }

    [Obsolete("Unboxing this collection is only allowed in the declarating class.", true)]
    public new void RemoveAt(int index) { }

    public static implicit operator EntityCollection<T>(List<T> source)
    {
        var target = new EntityCollection<T>();
        foreach (var item in source)
            ((Collection<T>) target).Add(item); // unbox

        return target;
    }
}

This way you can still run your Linq as usual but will get a proper warning of usage when trying to modify the Collection property. Un-boxing it to a Collection would be the only way:

((Collection<MyEntity>)ListProperty).Add(entity);
like image 59
Patrik Melander Avatar answered Sep 22 '22 10:09

Patrik Melander