Skip to content

Models properties

Current Limitations

At present, only the DatabaseManager with a MySQL connector is supported. This section will focus on the available actions for these components. It’s essential to always include a parameterless constructor in your models; this is required to load models from the database correctly.

When starting your application, Aventus will automatically create the necessary tables in your database based on your models.

Tip: During development, you may frequently restart your server. To reset the database at each launch, you can call storage.ResetStorage(); in your Aventus.cs file. This will completely clear the database each time.

Basic Properties

You can define properties within your models that will automatically be mapped to corresponding fields in the database. Nullable properties (using ?) will automatically be marked as nullable in the database schema.

For dates, while DateTime can be used, it may present issues with queries. It is recommended to use Date or Datetime types instead.

User.cs
using AventusSharp.Data;
namespace Demo.Data
{
public enum UserType
{
User,
Admin
}
public class User : Storable<User>
{
public string Username { get; set; } = "";
public UserType Type { get; set; } // The enum will be stored as a string
public int Age { get; set; }
public double Price { get; set; }
public Date? Birthday { get; set; }
public DateTime? DeathDay { get; set; }
public Datetime? LastLogin { get; set; }
public bool Deleted { get; set; }
}
}

Table SQL Generated:

user.sql
CREATE TABLE `user` (
`Id` int NOT NULL AUTO_INCREMENT,
`Username` varchar(255) NOT NULL,
`Type` varchar(255) NOT NULL,
`Age` int NOT NULL,
`Price` float NOT NULL,
`Birthday` date DEFAULT NULL,
`DeathDay` datetime DEFAULT NULL,
`LastLogin` datetime DEFAULT NULL,
`Deleted` bit(1) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Using Attributes

For more control over the database schema, you can use attributes on your properties:

  • [Nullable]: Allows nullable values.
  • [NotNullable]: Denies nullable values.
  • [Primary]: Marks a property as a primary key (do not use manually).
  • [AutoIncrement]: Sets the property to auto-increment.
  • [NotInDB]: Excludes the field from being stored.
  • [Size()]: Specifies the length for a string field.
  • [SqlName()]: Sets a custom name for the field in storage.
  • [Unique]: Marks the property as unique.

To establish a link between two models, add the related model as a property in the first model. Both classes must inherit from Storable for the link to be effective.

Example C# Code for Link Creation:

Role.cs
using AventusSharp.Data;
namespace Demo.Data
{
public class Role : Storable<Role>
{
public string Name { get; set; }
}
}
User.cs
using AventusSharp.Data;
namespace Demo.Data
{
public class User : Storable<User>
{
...
public Role Role { get; set; }
}
}

Table SQL Generated:

user.sql
CREATE TABLE `user` (
`Id` int NOT NULL AUTO_INCREMENT,
`Username` varchar(255) NOT NULL,
`Type` varchar(255) NOT NULL,
`Age` int NOT NULL,
`Price` float NOT NULL,
`Birthday` date DEFAULT NULL,
`DeathDay` datetime DEFAULT NULL,
`LastLogin` datetime DEFAULT NULL,
`Deleted` bit(1) NOT NULL,
`Role` int NOT NULL,
PRIMARY KEY (`Id`),
KEY `FK_Role_User_Role` (`Role`),
CONSTRAINT `FK_Role_User_Role` FOREIGN KEY (`Role`) REFERENCES `role` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

This establishes a one-to-many relationship, where a user can have a single role, and a role can be assigned to multiple users. The related class will load automatically when read, but you can use an integer ID with the [ForeignKey<T>] attribute for a lighter connection.

User.cs
using AventusSharp.Data;
using AventusSharp.Data.Attributes;
namespace Demo.Data
{
public class User : Storable<User>
{
...
[ForeignKey<Role>]
public int RoleId { get; set; }
}
}

To enable reverse relationships, where a model can reference a collection of related models, use the [ReverseLink] attribute.

Example C# Code with Reverse Link:

Role.cs
public class Role : Storable<Role>
{
public string Name { get; set; }
[ReverseLink]
public List<User> Users { get; set; }
}

This setup allows access to all users associated with a role without altering the database schema. You can also combine [ReverseLink] with [Auto...] attributes to manage linked data actions.

To create multiple links, use a list to represent a many-to-many relationship. An intermediate table will be created automatically.

Example C# Code:

User.cs
using AventusSharp.Data;
namespace Demo.Data
{
public class User : Storable<User>
{
...
public List<Tag> Tags { get; set; }
}
}

Table SQL Generated:

intermediate_table.sql
CREATE TABLE `user_tag` (
`User_Id` int NOT NULL,
`Tag_Id` int NOT NULL,
PRIMARY KEY (`User_Id`,`Tag_Id`),
KEY `FK_User_Tag_Tag` (`Tag_Id`),
CONSTRAINT `FK_User_Tag_Tag` FOREIGN KEY (`Tag_Id`) REFERENCES `tag` (`Id`),
CONSTRAINT `FK_User_Tag_User` FOREIGN KEY (`User_Id`) REFERENCES `user` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

Using a List<int> with [ForeignKey<T>] can simplify linking without loading the full list of related objects.

User.cs
using AventusSharp.Data;
namespace Demo.Data
{
public class User : Storable<User>
{
...
[ForeignKey<Tag>]
public List<int> TagsId { get; set; }
}
}

Inheritance

AventusSharp supports storing classes with inheritance. To enable this, create an abstract parent class, which will serve as the base class for your entities. Since we need to provide the final class type to Storable<T>, our class should also be generic. Additionally, for Aventus to correctly interpret the structure, ensure that each abstract class implements a single interface.

Animal.cs
using AventusSharp.Data;
namespace Demo.Data
{
public interface IAnimal : IStorable
{
string Name { get; set; }
int Age { get; set; }
}
public abstract class Animal<T> : Storable<T>, IAnimal where T : IAnimal
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Dog : Animal<Dog>
{
public string Color { get; set; }
}
public class Cat : Animal<Cat>
{
public int SleepTime { get; set; }
}
}

The above code will create a primary Animal table that contains general information about each animal’s Id, Name, and Age. In addition, two child tables, Dog and Cat, will be generated to store specific data for each subclass.

Each child entry shares the same Id as its parent Animal entry, so there can be no duplicate Id between Dog and Cat entries. A __type field is added to the Animal table, enabling Aventus to identify whether the entry represents a Dog or Cat.

Table SQL Generated:

CREATE TABLE `animal` (
`__type` VARCHAR(255) NOT NULL,
`Id` INT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(255) NOT NULL,
`Age` INT NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `cat` (
`Id` INT NOT NULL,
`SleepTime` INT NOT NULL,
PRIMARY KEY (`Id`),
CONSTRAINT `FK_Id_Cat_Animal` FOREIGN KEY (`Id`) REFERENCES `animal` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `dog` (
`Id` INT NOT NULL,
`Color` VARCHAR(255) NOT NULL,
PRIMARY KEY (`Id`),
CONSTRAINT `FK_Id_Dog_Animal` FOREIGN KEY (`Id`) REFERENCES `animal` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Alternative Structure with ForceInherit

If you prefer each subclass (such as Dog and Cat) to have its own independent Id instead of sharing the Animal table’s Id, you can apply the [ForceInherit] attribute to the parent class. This will push all inherited fields into each child table, removing the need for a shared Animal table and creating a more self-contained structure for each subclass.

Animal.cs
...
[ForceInherit]
public abstract class Animal<T> : Storable<T>, IAnimal where T : IAnimal
{
...
}
...

Table SQL Generated:

CREATE TABLE `cat` (
`Id` INT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(255) NOT NULL,
`Age` INT NOT NULL,
`SleepTime` INT NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `dog` (
`Id` INT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(255) NOT NULL,
`Age` INT NOT NULL,
`Color` VARCHAR(255) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

With this configuration, the Animal table is no longer created, and each subclass (Dog and Cat) contains all necessary fields independently.

Creating Custom Field Types

AventusSharp allows you to create custom field types to handle the storage of complex fields. For instance, custom field types can simplify the management of complex data like file handling. To create a custom field type, follow these steps:

  1. Define the Base Class for the Field
    Start by defining a base class for the custom field, which will be used in your models.
CustomField.cs
public class CustomField
{
public string Name { get; set; }
}
  1. Define a Class for Database Handling
    Next, define a class that will manage this new field type and interact with the database. This class should inherit from CustomTableMember to leverage its database handling capabilities.
CustomField.cs
public class CustomFieldTableMember : CustomTableMember
{
public CustomFieldTableMember(MemberInfo? memberInfo, TableInfo tableInfo, bool isNullable)
: base(memberInfo, tableInfo, isNullable)
{
}
public override DbType? GetDbType()
{
return DbType.String;
}
public override object? GetSqlValue(object obj)
{
object? result = GetValue(obj);
if (result is CustomField customField)
{
return customField.Name;
}
return null;
}
protected override void SetSqlValue(object obj, string? value)
{
if (!string.IsNullOrEmpty(value))
{
CustomField customField = new CustomField() { Name = value };
SetValue(obj, customField);
}
}
}
  1. Annotate the Base Class with [CustomTableMemberType<T>]
    Finally, apply the [CustomTableMemberType<T>] attribute to your base class to indicate which CustomTableMember class will handle this field type. This binds the custom field type to its corresponding database handler.
CustomField.cs
[CustomTableMemberType<CustomFieldTableMember>]
public class CustomField
{
public string Name { get; set; }
}

With these steps, your custom field type will now be managed by AventusSharp, allowing complex fields to be easily stored and retrieved from the database.

Example: AventusFile

AventusSharp provides a built-in custom type, AventusFile, to manage file uploads seamlessly. This type simplifies file handling by directly associating file upload management with a class field. Here’s how to implement it:

  1. Add an AventusFile Field to Your Model
    To enable file uploads in your model, add an AventusFile property. For example, you might add a Picture property to a User class to manage user profile images.
User.cs
public class User : Storable<User>
{
public AventusFile? Picture { get; set; }
// Other properties...
}
  1. Save the Uploaded File to a Folder
    When a file is uploaded, use the SaveToFolderOnUpload method on the AventusFile instance. This will copy the file to the specified folder on your disk and update the file information within the AventusFile object. Upon saving the User object to the database, the file’s URI is automatically updated in the database.
Upload.cs
public class Upload
{
public void SaveFile(User user)
{
user.Picture?.SaveToFolderOnUpload(myPictureDir);
}
}

Extend AventusFile for Custom File Handling

One of the key advantages of AventusFile is that it’s fully extensible. You can create a subclass of AventusFile to apply custom logic. For example, if you want to restrict uploads to image files only, you could use a file type-checking library such as File.TypeChecker to validate the file type and trigger an error if the upload is not an image.

ImageFile.cs
public class ImageFile : AventusFile
{
public override ResultWithError<bool> SaveToFolderOnUpload(string filePath)
{
if (Upload == null)
{
return new ResultWithError<bool>();
}
// Use File.TypeChecker or similar library to verify file type
bool isValidImg = false;
FileStream fileStream = File.OpenRead(Upload.FilePath);
if (FileTypeValidator.IsTypeRecognizable(fileStream))
{
isValidImg = fileStream.IsImage();
}
if (!isValidImg)
{
ResultWithError<bool> result = new ResultWithError<bool>();
result.Errors.Add(new GenericError(405, "Not allowed"));
return result;
}
// Proceed with the upload if file type is valid
return base.SaveToFolderOnUpload(filePath);
}
}

By extending AventusFile this way, you gain fine-grained control over file uploads, ensuring data integrity and enforcing specific file types directly in your application logic.