CodeWarrior
CodeWarrior

Reputation: 7468

Constrain the number of child entities in Entity Framework

Bottom Line Up Front

Is there a succinct way that I can constrain the number of child entities that can belong to a parent in Entity Framework. I am using 4.3.1 at the moment.

The Problem

I am developing an ASP.NET MVC3 site which accesses data via a data access layer that uses Entity Framework. I have a SearchList entity which has a many to many relationship to a Search entity. A SearchList may have many Searches, and a Search may belong to many SearchLists.

At one point in the workflow of the site, a user needs to select the searches and other items to use in a batch search. We want the page to load the entire search list.

SearchLists can get quite large, and as a test we created one with 21,000 searches. It took a few seconds, and the data returned was about 9.5 MB, which we were expecting, but jQueryUI choked when trying to table-ify that much.

What we would like

So we want to impose a limit on the number of searches any search list can have. I can go through the application and put a bunch of rules in that checks the size of the collection and if the searches that are trying to be added plus the size of the current... yada yada yada.

If however there was a better way (especially one that could easily output an error message that MVC would pick up) I would totally take that instead.

I have googled, and perused a number of EF blogs to no avail. Constrain children and max number of children in collection and similar searches have returned results that are about Linq queries and the Count and Max methods.

Any help would be appreciated.

Upvotes: 1

Views: 508

Answers (2)

CodeWarrior
CodeWarrior

Reputation: 7468

I ended up going with CustomValidationAttribute, and implemented it with a great deal of success. See below for my implementation info:

In the SearchList entity

    [NotMapped]
    public String ValidationMessage { get; set; }
    [CustomValidation(typeof(EntityValidation.EntityValidators), "ValidateSearchCount")]
    public virtual List<Search> Searches { get; set; }

    public static bool Create(ProjectContext db, SearchList searchList)
    {
        try
        {
            db.SearchLists.Add(searchList);
            db.SaveChanges();
            return true;
        }
        catch (DbEntityValidationException dbEx)
        {
            foreach (var validationErrors in dbEx.EntityValidationErrors)
            {
                foreach (var validationError in validationErrors.ValidationErrors)
                {
                    searchList.ValidationMessage += validationError.ErrorMessage;
                }
            }
            return false;
        }
        catch (Exception)
        {
            return false;
        }
    }

EntityValidators Class

    public static ValidationResult ValidateSearchCount(List<Search> Searches)
    {
        bool isValid;
        int count = Searches.Count();
        isValid = (count <= 5000 ? true : false);

        if (isValid)
        {
            return ValidationResult.Success;
        }
        else
        {
            return new ValidationResult("A maximum of 5000 searches may be added to a SearchList.");
        }
    }

A similar exception block is on the update method. In this way, when SaveChanges gets called it attempts to validate the entity and its child collections, and when the collection count is greater than 5000 the validator will return an error message which gets caught in the exception handler and stored in a local property for my controller to check when things go wrong.

Upvotes: 0

Ladislav Mrnka
Ladislav Mrnka

Reputation: 364409

There is no built-in way so you will have to code such validation yourselves. Some quick ideas:

  • You can for example use custom collection for the navigation property which will fire exception when you try to add additional search exceeding the threshold. It is simple but it demands you to have all searches loaded, it will have concurrency problems and moreover it can fire during loading search list and searches from database.
  • You can handle it in overriden SaveChanges. You will at least have to check how many searches are already related to search list but you will still have concurrency problem (what if other request tries to add search to the same list but only one place is remaining - both can succeed the check and insert related search)
  • You can handle it in database trigger - again it will have concurrency problems

Avoiding concurrency problems completely requires hand written queries with locking hints to ensure that only one request can check number of searches per search list and insert a new search in atomic transaction.

Upvotes: 1

Related Questions