Codebasis als Dateien ins Arbeitsrepo statt als Gitlink

QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
This commit is contained in:
Christoph Schwörer
2026-08-26 07:43:51 +02:00
parent 18edae75b6
commit f045b99a25
24664 changed files with 5846716 additions and 1 deletions
@@ -0,0 +1,154 @@
# On DTOs and Entities
In our c-entron<span>.NET solution we use three different types of objects that hold actual data:
type | layer | usage
---|---|---
Entity | BL/WebServiceBL | interaction with the database via NHibernate
DTO | WebService/Logics |transfer from entity to viewmodel via webservice
ViewModels | ViewModel/UI|display data to the user and allow user to edit it
*Because ViewModels are pretty straight forward and quite boring we're just going to ignore them here.*
We're going to use the `Thingy` class for examples here again. For a real implementation check out MyDayWorkItem and associated classes.
## Entities
On the most basic level entities are just a row in a database table managed by our ORM [NHibernate](https://nhibernate.info/). If we have a `Thingy` there is a corresponding row in the `Thingies` table, where the **primary key equals** the **I3D**. Entities can never leave the BL-Layer as they cannot be used in the webservice and need a connection to NHibernate and the DB itself. CRUD-Operations are done via `Session.GetGenericDAO\<T>().SaveOrUpdate/Get/Delete` and can only be executed with a valid entity.`
As an entity is simply for holding data it should never include any logic, no overrides and not even a ctor. To avoid confusion all properties present in an entity should also be mapped to a column in the database.
An entitiy needs 2 classes:
The actual entity that holds all data and is used for all operations. It must inherit the `BaseEntity` abstract base class that adds the unique identifier (**I3D**) for us. The correct project for entities is `Centron.Entities` and then the `Entities` directory.
All properties must be declared **virtual** and have to have both a **setter** and a **getter**. Both things are required for the interaction with NHibernate.
``` csharp
public class Thingy : BaseEntity
{
public virtual string SomeProperty { get; set; }
public virtual int SomeOtherProperty { get; set; }
}
```
Aside from the actual entity we also need a mapping class from [Fluent NHibernate](https://github.com/FluentNHibernate/fluent-nhibernate). This class maps the properties to the database column and must inherit `ClassMap<T>`. Even tough NHibernate can figure a lot of the properties out itself, you should always set the table, the id and ALL properties. Mapped properties need to be described with `.Not`, `.Nullable()`, `.Lenght()` etc. as closely as possible. Check out ['Fluent NHibernate in a Nutshell'](https://github.com/FluentNHibernate/fluent-nhibernate/wiki/Getting-started) for more infos. The correct project for entities is `Centron.DAO` and then the `Mappings` directory.
``` csharp
public class ThingyMaps : ClassMap<Thingy>
{
public ThingyMaps
{
this.Table("Thingies");
this.Id(f => f.I3D);
this.Map(f => f.SomeProperty).Nullable();
this.Map(f => f.SomeOtherProperty).Not.Nullable();
}
}
```
## DTOs
>In the field of programming a data transfer object (DTO) is an object that carries data between processes. (https://en.wikipedia.org/wiki/Data_transfer_object)
For us DTOs do exactly that. They transfer the data from the entity on the bl-layer to the viewmodel in the UI over WebService or direct database connection. Our webservice exclusivly uses DTOs, so all other applications and the c-entron<span>.NET ILogics use them. The project for DTOs is `Centron.WebServices.Core` and then the wrongly named `Entities` directory.
Like `entities DTOs` should never hold any kind of logic, no overrides and no ctors. It can include properties not present in the entity but this should be only be done in very specific circumstances.
Constructing DTOs is very easy: just create a class `<yourclass>DTO` (`ThingyDTO` for us), inherit `BaseDTO` and copy all properties from the entity. Then add the `[DataContract]` attribute to your class and the `[DataMember]` attribute to each property.
There are certain things that need to be avoided.
Properties of type DateTime can be dangerous. The default value of DateTime in .NET is 01.01.0001 which cannot be parsed to json and *will* throw an exception. Especially if the DateTime property gets filled by the database, is used in multiple locations or by other applications. Using a nullable `DateTime?` is the easiest way around that.
List properties **must** use `List<T>`. Using `IList<T>` or other types of list (`IEnumerable<T>`, `ICollection<T>`) can lead to problems with serializing it into json. As this is not widely done in our DTOs please consider updating existing DTOs.
### KnownTypes
If your DTO inherits from a different class (NOT `BaseDTO`) you must add the type to `GetKnownTypes()` in `KnownTypes.cs`. Additionally each webservice method that uses that DTO also needs the `[ServiceKnownType(nameof(KnownTypes.GetKnownTypes), typeof(KnownTypes))]` attribute in `ICentronRestService`. Otherwise the webservice *will* throw an exception while parsing your DTO to json.
## Converting DTOs to Entities and vice versa
### DTO -> Entity
Converting from a DTO to an entity can be somewhat annoying and complicated.
**NEVER use the `ObjectMapper` for this.** *(Yes, I know, there are a lot of places that do)*
What we need to do is as follows:
1. check if the I3D of the `ThingyDTO` is 0.
\> yes: `new Thingy()`;
\> no: load the entity from the database (via the appopriate BL method)
2. take over all properties from the DTO to the entity by hand.
This is done to ensure that all entities that are already saved to the DB (I3D != 0) are unique and properly managed by NHibernate.
``` csharp
public Thingy ConvertThingyDTOToEntity(ThingyDTO dto)
{
Guard.NotNull(dto, nameof(dto));
Thingy entity;
if(dto.I3D != 0)
entity = new ThingyBL(this.Session).LoadThingy(dto.I3D);
else
entity = new Thingy();
entity.I3D = dto.I3D;
entity.SomeProperty = dto.SomeProperty;
[..]
return entity;
}
```
### Entity -> DTO
Converting an entity to a DTO is very simple as we can just use the `ObjectMapper.Map<TEntity, TDTO>()`.
To use the Objectmapper you should create a configuration file.
These files go under `Centron.Bl` -> `Webservice` -> `ObjectMapperConfiguration`.
If there is a fitting class already just add `this.CreateMap<Thingy, ThingyDTO>();`, else we need to create a new file.
``` csharp
public class ThingyConfiguration : Profile
{
protected ovveride void Configure()
{
this.CreateMap<Thingy, ThingyDTO>();
}
}
```
### Generating mapping and entities
The following SQL statements generate your mappings and entities for you.
**There could be errors inside, you need to manually verify them.**
``` SQL
SELECT 'Map(m => m.' + c.name + ').Column("' + c.name + '")' +
CASE WHEN ty.name = 'text' THEN '.Length(int.MaxValue)' ELSE '' END +
CASE WHEN ty.name = 'varchar' THEN '.Length(' + CONVERT(varchar(100), c.max_length) + ')' ELSE '' END +
CASE WHEN ty.name = 'nvarchar' THEN '.Length(' + CONVERT(varchar(100),c.max_length / 2) + ')' ELSE '' END +
CASE WHEN c.is_nullable = 1 THEN '.Nullable()' ELSE '' END + ';', c.max_length
FROM sys.all_columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = 'TableName' AND ty.name <> 'sysname' AND c.Name <> 'I3D'
SELECT 'public virutal ' +
CASE WHEN ty.name IN ('text', 'nvarchar', 'varchar') THEN 'string' ELSE '' END +
CASE WHEN ty.name = 'bit' THEN 'bool' ELSE '' END +
CASE WHEN ty.name = 'datetime' THEN 'DateTime' ELSE '' END +
CASE WHEN c.is_nullable = 1 AND ty.name = 'datetime' THEN 'DateTime' ELSE '' END +
CASE WHEN ty.name = 'float' THEN 'double' ELSE '' END +
CASE WHEN ty.name = 'int' THEN 'int' ELSE '' END +
CASE WHEN ty.name = 'uniqueidentifier' THEN 'Guid' ELSE '' END +
CASE WHEN ty.name = 'char' THEN 'char' ELSE '' END +
CASE WHEN c.is_nullable = 1 AND ty.name NOT IN ('text', 'nvarchar', 'varchar', 'datetime') THEN '?' ELSE '' END +
' ' + c.name + ' { get; set; }'
FROM sys.all_columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = 'TableName' AND ty.name <> 'sysname' AND c.Name <> 'I3D'
```
**In the mapping Length(0) should be .Length(int.MaxValue)**
**For the entities ? should be DateTime?**