The documentation comes from the Markdown files in the source code, so is always up-to-date but available only in English. Enjoy!
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.
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
However, if you add in
ContinentEntity a field like this:
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
Tin the database. In this case, since
CountryEntityis an entity, a foreign key with name
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:
PersonEntityentity, 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)
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
Even better, always use
fieldMlist snippet to create MList fields.
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.
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 (
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
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:
Ordercolumn will be included in the MList table.
MList<T>order will be sorted according to 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
Move will be set to true, typically showing arrows to move elements up and down.
Let's consider this two design alternatives:
OrderLineEntityis also an
Entitywith a reference to
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
OrderDNLines in the first example).
But there are other considerations:
OrderLineEntityevery time an
OrderEntityis retrieved, so will usually be slower if there are many lines.
OrderLineEntityas part of the
OrderEntity, for example to check that the
OrderEntityis the sum of each
OrderLineEntityby saving the
OrderEntity, even if the user interface will promote this, while in the second example you'll typically navigate to the
OrderLineEntityto make the changes, and will be logged independently.
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.