Signum Documentation

The documentation comes from the Markdown files in the source code, so is always up-to-date but available only in English. Enjoy!

Paste your Framework commit SHA

MList<T> class

MList<T> is the class that you can use to model One-To-Many and Many-To-Many relationships on Signum Framework.

This class is a full featured collection with all the methods available in List<T>, but it supports data binding (like ObservableCollection<T>) and has change tracking embedded.

Database Mapping

We have already agreed that Signum Framework is all about writing the entities code and letting the engine have control over the database schema. MList<T> is not an exception, and in some senses it is the most radical decisions about database mapping.

Usually you make a One-To-Many relationship between Country and Continent by adding a foreign key in Country pointing to Continent. This is Databases 101, and is what will happen if you add a ContinentEntity field in CountryEntity.

However, if you add in ContinentEntity a field like this:

MList<CountryEntity> countries;

Then the behavior is different, a relational table with name ContinentDNCountries is created that looks like this:

CREATE TABLE ContinentDNCountries(
  Id INT IDENTITY NOT NULL PRIMARY KEY,
  idParent INT NOT NULL,
  idCountryEntity INT NULL
)

CREATE INDEX IX_idParent ON ContinentDNCountries(idParent)
CREATE INDEX IX_idCountryEntity ON ContinentDNCountries(idCountryEntity)

ALTER TABLE ContinentDNCountries ADD CONSTRAINT FK_ContinentDNCountries_idParent FOREIGN KEY (idParent) REFERENCES ComputerEntity(Id)
ALTER TABLE ContinentDNCountries ADD CONSTRAINT FK_ContinentDNCountries_idCountryEntity FOREIGN KEY (idCountryEntity) REFERENCES CountryEntity(Id)

This table has the following columns:

  • Id: This column is used internally by the Signum Framework to keep track of each particular element in the list.
  • idParent: Contains a reference to the entity that owns the collection, in this case the ContinentEntity.
  • idCountryEntity: The actual translation of T in the database. In this case, since CountryEntity is an entity, a foreign key with name idCountryEntity is enough.

This last point is very important. Tables generated by MList<T> will be relational tables just in the case that T is another entity, but it could be almost any other thing, so maybe MList Table is a better name.

Other examples could be:

  • Value: If storing Telephones just as MList<strings> in a PersonEntity entity, the string column will be included in he MList table. The result will looks like this:
CREATE TABLE PersonDNTelepones(
  Id INT IDENTITY NOT NULL PRIMARY KEY,
  idParent INT NOT NULL,
  Value NVARCHAR(200) NULL
)

CREATE INDEX IX_idParent ON PersonDNTelepones(idParent)
ALTER TABLE PersonDNTelepones ADD CONSTRAINT FK_PersonDNTelepones_idParent FOREIGN KEY (idParent) REFERENCES PersonEntity(Id)
  • EmbeddedEntity: The entity properties will just be embedded in the MList table. Very common pattern.
  • Lazy: Just like the reference example.
  • ImplementedBy and ImplementedByAll: The collection table will be the owner of the polymorphic foreign key.
  • Enums: ...you get the idea, don't you?

Is also impotant to note that, in order to reduce type-mismatch, by default the nullability of elements will be exactly the same in SQL than in C#. That means that a MList with entities of embedded entities could contain null values!. 99.9% of the time you don't want that, so avoid it using [NotNullableAttribute] in your MList<T> field.

Even better, always use fieldMlist snippet to create MList fields.

Change Tracking and RowId

In the previous version of Signum Framework, MList<T> only had two states: Clean and Modified. When an entity with a modified MList<T> was being saved, all their elements in the MList table where deleted and re-inserted.

Currently, MList<T> internally remembers the RowId of each element. This let's the engine be smarter, doing only the necessary INSERTS / DELETES / UPDATES, getting some performance improvements.

Even more important than the performance is that now the RowId are more stable, allowing scenarios like translate string fields in collections.

If you manipulate the entity with the typical methods (Add, AddRange, Remove, RemoveAt, RemoveAll, RemoveRange) or using the indexer, the MList will remember the RowId of all the unaffected elements, inserting the new ones when saved.

PersonEntity person = Database.Retrieve<PersonEntity>(1); 
person.Telephones.RemoveAt(0); //Ok
person.Telephones.Add("664 434 423"); //Ok
person.Telepgones.RemoveAll(t=>!t.StartsWith("6")); //Ok

On the other side, if you modify a retrieved entitiy by re-assign the MList field, typically using a LINQ query and ToMList extension method, all the previous elements will be removed and replaced by the new ones, even if they have the same values!.

PersonEntity person = Database.Retrieve<PersonEntity>(1); 
person.Telephones = person.Telephones.Where(t=>t.StartWith("6")).ToMList(); //All elements will be replaced!

In order to re-set all the elements of a retrieved entity, use ResetRange method in the MList instead. This way only the necessary changes will be made and the RowIds will be more stable.

PersonEntity person = Database.Retrieve<PersonEntity>(1); 
person.Telephones.ResetRange(person.Telephones.Where(t=>t.StartWith("6"))); //Ok

Remembering Order

Microsoft SQL Server (as many other RDBMS) does not guarantee any particular order if no ORDER BY is included in the query. That means that when an entity with an MList<T> field is retrieved the elements could be in a different order than they where when saved.

In many situations the order doesn't matter and this is not an issue, but if order matters, then write a [PreserveOrderAttribute] in your MList<T> field. This will have the following effect:

  • An Order column will be included in the MList table.
  • When the entity is saved, the elements order will be remembered in the Order column.
  • When the entity is retrieved, the MList<T> order will be sorted according to the Order column.
  • Manually sorting the MList<T> will mark the MList<T> as modified (it doesn't normally).

Additionally, when binding the MList<T> property to any user interface control, like EntityList, EntityStrip, etc... Move will be set to true, typically showing arrows to move elements up and down.

MList<T> vs Back references

Let's consider this two design alternatives:

  • MList of embeddeds:
  • OrderEntity is an Entity with a MList<OrderLineEntity>.
  • OrderLineEntity is an EmbeddedEntity
  • Back reference:
  • OrderEntity is an Entity.
  • OrderLineEntity is also an Entity with a reference to OrderEntity.

From a strictly relational point of view, this scenarios are very similar, in both cases there is a 1-to-N relationship from OrderEntity table to OrderLineEntity (OrderDNLines in the first example).

But there are other considerations:

  • The MList example requires to retrieve all the OrderLineEntity every time an OrderEntity is retrieved, so will usually be slower if there are many lines.
  • The MList example makes easier to validate the OrderLineEntity as part of the OrderEntity, for example to check that the TotalPrice of the OrderEntity is the sum of each SubTotalPrice of OrderLineEntity.
  • In the MList example you'll typically modify the OrderLineEntity by saving the OrderEntity, even if the user interface will promote this, while in the second example you'll typically navigate to the OrderLineEntity to make the changes, and will be logged independently.
  • In one MList example the dependency goes from OrderEntity to OrderLineEntity, while in the back reference example is the other way around. This could be a problem if the two entities are in different modules.

In this particular case, MList makes more sense, but for other cases, like Countries in a Continent, a back reference and expressionMethods are better options.