Перейти к основному содержимому

Visary Conventions

Entity Display Name (~Display Name~)

Each UML class can include a Display Name — a human-readable name of the entity shown in the UI (e.g., form headers, menus, and list titles).

It is declared directly in the class header using the ~Display Name~ syntax:

This display name is used during UI registration to generate localized captions automatically.

UI registration example (C#):

context.AddEntity<DemoObject>("Demo Object").AddDefaultMnemonic();

Files

  • file - Ссылка на файл
  • image - Ссылка на изображение
  • avatar - Ссылка на аватар

UML:

C#:

public class DemoObject : BaseObject, ILookup
{
[DetailView("Файлы"), ListView(DefaultVisibility = false)]
[FileStorageLink("demo", Multiple = true)]
public string Files { get; set; }
[DetailView("Картинка"), ListView]
[FileStorageImage("demo")]
public string Img { get; set; }
[DetailView("Аватарка"), ListView]
[Avatar]
public string Avatar { get; set; }
}

ILookup

The ILookup interface is used to represent entities that are referenced by other objects, typically as lookup or reference values in dropdowns, selectors, or relational links.

Rules:

  • Any object that includes a Title property is expected to implement the ILookup interface.

UML:

Interface Definition

namespace Visary.Abstractions
{
public interface ILookup
{
string Title { get; }
}
}

Example Implementation

public class DemoObject : BaseObject, ILookup
{
[DetailView("Наименование", Required = true), ListView]
[MaxLength(255)]
public string Title { get; set; }
}

IGeoObject

The IGeoObject interface is used for entities that can be displayed on a map.

Rules:

  • If an object contains a Location location field, it is considered to implement the IGeoObject interface.
  • If an object in the UML diagram is marked with the <<map>> stereotype, it must also implement IGeoObject.

UML:

Interface Definition

namespace Visary.Gis.Abstractions
{
public interface IGeoObject
{
int ID { get; }
string Title { get; }
string Description { get; }
Location Location { get; }
}
}

Example Implementation

public class MapObject : BaseObject, IGeoObject
{
[DetailView("Наименование", Required = true), ListView]
[MaxLength(255)]
public string Title { get; set; }
[DetailView("Описание"), ListView]
public string Description { get; set; }
[DetailView("Местоположение"), ListView]
public Location Location { get; set; }
}

ref

When generating C# classes, foreign keys must be explicitly declared for reference (ref) types.

public class DemoObject : BaseObject 
{
public int AuthorID { get; set; }
[DetailView("Автор", Required = true)]
public User Author { get; set; }

public int? AssigneeID { get; set; }
[DetailView("Исполнитель")]
public User Assignee { get; set; }
}

DAL registration:

config.Context<TContext, EFRepositoryFactoryProvider<TContext>>()
.Schema("Demo")
.Entity<DemoObject>(x => x
.AssociatedEntity(a => a.Author)
.AssociatedEntity(a => a.Assignee))

collection

Collections are used to represent one-to-many relationships between entities. In UML diagrams, they are declared as collection ~Type~, where Type is the item type.

There are two implementation strategies: aggregation (reference collection) and composition (embedded objects).

TypeDescription
collection ~Type~A collection of the specified type. Used in both aggregation and composition.

Aggregation (reference collection)

Collection items belong to another context and exist independently. The connection to the parent is implemented via helper classes.

  • In UML — denoted by o--
  • In C# — implemented via EasyCollectionEntry<Parent, Item>
  • Common use case — e.g., a list of authors for a publication

UML:

C#:

public class DemoObject : BaseObject {
[DetailView("Авторы")]
public ICollection<AuthorEasyEntry> Authors { get; set; } = new List<AuthorEasyEntry>();
}

public class AuthorEasyEntry : EasyCollectionEntry<DemoObject, User> { }

Composition (embedded objects)

Collection items are part of the parent and cannot exist independently — a “part-of” structural relationship.

  • In UML — denoted by 1 *-- n
  • In C# — implemented via SlaveObject<Parent>
  • Common use case — e.g., invoice lines, form elements

UML:

C#:

public class DemoObject : BaseObject {
[DetailView]
public ICollection<DemoObjectItem> Items { get; set; } = new List<DemoObjectItem>();
}

public class DemoObjectItem : SlaveObject<DemoObject> {
[DetailView, ListView]
public string Name { get; set; }
}

enumeration

Enumerations are used to define a limited set of allowed values. All enums must follow these rules:

Formatting rules:

  1. Each value must have the EnumDisplay attribute This attribute defines the label, icon, and optional color.

  2. Explicit numeric values are required Values must be assigned explicitly, starting from 0 with an increment of 10.

Usage example:

public enum DemoObjectStatus
{
[EnumDisplay("Открыто", Icon = "fas fa-star", Color = "#32a852")]
Open = 0,

[EnumDisplay("В работе", Icon = "fas fa-cogs", Color = "#dc9f43")]
Doing = 10,

[EnumDisplay("Закрыто", Icon = "fas fa-ban", Color = "#dc5242")]
Closed = 20
}

UML example:


OneToManyAssociation

Represents a one-to-many relationship in the UI, without a direct database-level link on the parent side.

public class Blog : BaseObject, ILookup
{
[DetailView(Required = true), ListView]
[MaxLength(255)]
public string Title { get; set; }

[DetailView]
[OneToManyAssociation(nameof(Post.Blog))]
public OneToManyAssociation<Post> Posts { get; set; }
}

public class Post : BaseObject
{
public int BlogId { get; set; }

[DetailView(Required = true), ListView]
public Blog Blog { get; set; }
}

DAL registration:

config.Context<TContext, EFRepositoryFactoryProvider<TContext>>()
.Schema("Blog")
.Entity<Blog>()
.Entity<Post>()

OneToOneAssociation

Displays a dependent object (1:1) on the main object’s side in the UI, without a direct DB link.

Important: If a UML diagram contains a 1 *-- 1 relationship but the parent object lacks a OneToOneAssociation<T> property, it must be auto-generated during code generation.

public class Blog : BaseObject, ILookup
{
[DetailView(Required = true), ListView]
[MaxLength(255)]
public string Title { get; set; }

[DetailView]
[OneToOneAssociation]
public OneToOneAssociation<BlogHeader> BlogHeader { get; set; }
}

public class BlogHeader : BaseObject
{
[DetailView, ListView]
public string Name { get; set; }
}

DAL registration:

config.Context<TContext, EFRepositoryFactoryProvider<TContext>>()
.Schema("Blog")
.EntityWithOne<BlogHeader, Blog>()

ManyToManyAssociation

Represents many-to-many relationships in the UI, without a direct database-level link.

public class Post : BaseObject
{
[DetailView]
public ManyToManyAssociation<Tag> Tags { get; set; }
}

public class Tag : BaseObject
{
[DetailView]
public ManyToManyAssociation<Post> Posts { get; set; }
}

public class PostAndTag : ManyToManyAssociation<Post, Tag> { }

DAL registration:

config.Context<TContext, EFRepositoryFactoryProvider<TContext>>()
.Schema("Blog")
.Entity<Post>()
.Entity<Tag>()
.Entity<PostAndTag>()

abstract

Declares an abstract base class. Must not be registered in DAL/UI.

UML:

C#:

public abstract class Document : BaseObject, ILookup
{
[DetailView("Заголовок", Required = true), ListView]
[MaxLength(255)]
public string Title { get; set; }

[DetailView("Дата", Required = true), ListView]
public DateOnly Date { get; set; }

[DetailView("Номер"), ListView]
[MaxLength(20)]
public string Number { get; set; }

[DetailView("Файл"), ListView(false)]
[FileStorageLink]
public string Attachment { get; set; }
}

public class IncomingDocument : Document
{
[DetailView("Рег. номер", Required = true), ListView]
[MaxLength(50)]
public string RegistrationNumber { get; set; }

[DetailView("Дата получения", Required = true), ListView]
public DateOnly ReceivedDate { get; set; }
}

public class OutgoingDocument : Document
{
[DetailView("Исходящий номер", Required = true), ListView]
[MaxLength(50)]
public string OutgoingNumber { get; set; }

[DetailView("Дата отправки", Required = true), ListView]
public DateOnly SentDate { get; set; }
}

DAL registration: Note: The abstract class Document must not be registered in DAL/UI.

config.Context<TContext, EFRepositoryFactoryProvider<TContext>>()
.Schema("Document")
.Entity<IncomingDocument>()
.Entity<OutgoingDocument>()

categorized

Declares a categorized entity referencing a tree-structured category.

public class Employee : BaseObject, ICategorized<Department>, ILookup
{
[DetailView("ФИО", Required = true), ListView]
[MaxLength(255)]
public string Title { get; set; }

public int DepartmentId { get; set; }

[DetailView("Отдел", Required = true), ListView]
public Department Department { get; set; }
}

public class Department : TreeNode<Department> { }

DAL registration:

config.Context<TContext, EFRepositoryFactoryProvider<TContext>>()
.Schema("OrgChart")
.Entity<Employee>(x => x.AssociatedEntity(a => a.Department))


complex

The <<complex>> stereotype is used to define composite (embedded) types that are not standalone entities, but are embedded as value objects inside other entities. These types are not registered in the UI or DAL individually.

Rules:

  • The class must be marked with the <<complex>> stereotype.
  • The complex type is embedded into other entities as a regular property.
  • It should not inherit from BaseObject.

UML Example:

C# Example:

public class ContactInfo
{
[DetailView("Email")]
[MaxLength(255)]
public string Email { get; set; }

[DetailView("Phone")]
[MaxLength(20)]
public string Phone { get; set; }
}

public class Customer : BaseObject
{
[DetailView("Name", Required = true), ListView]
[MaxLength(255)]
public string Name { get; set; }

[DetailView("Contact Info")]
public ContactInfo Contact { get; set; }
}

DAL registration

public static void Init<TContext>(EntityConfigurationBuilder config) where TContext : BaseDataContext
{
config.ComplexType<ContactInfo>();
}

Global Complex Types

namespace Visary.Gis.Abstractions
{
public class Location
{
[DetailView]
public string Address { get; set; }

[DetailView]
public Geometry Disposition { get; set; }

...
}
}
namespace Visary.Abstractions.ComplexTypes
{
public class Address
{
[DetailView, ListView]
public string AddressString { get; set; }

...
}
}
namespace Visary.Abstractions.ComplexTypes
{
public record DatePeriod
{
[DetailView]
public DateOnly Start { get; set; }

[DetailView]
public DateOnly End { get; set; }
}
}
namespace Visary.Abstractions.ComplexTypes
{
public class Period
{
[DetailView]
public DateTime Start { get; set; }

[DetailView]
public DateTime End { get; set; }
}
}