Skip to main content

Repository

nicyeRepositoryAbout 3 minAbout 1005 words

FreeSql.DbContext references the abp vnext interface specification and implements a generic repository layer functionality (CURD), which can be understood as an enhanced version of traditional Data Access Layer (DAL).

.NET CLI
 dotnet add package FreeSql.DbContext
  • Select/Attach snapshot objects, Update only changes modified fields;
  • Insert data, optimized execution with ExecuteAffrows/ExecuteIdentity/ExecuteInserted across various databases;
  • Cascade save and cascade delete (one-to-one, one-to-many, many-to-many);
  • Repository + Unit of Work design pattern, simple and unified style;
public class Song
{
    [Column(IsIdentity = true)]
    public int Id { get; set; }
    public string Title { get; set; }
}

Note: Repository objects are not thread-safe, so they should not be used concurrently across multiple threads.

Temporary Usage

var curd = fsql.GetRepository<Song>();

Suitable for creating repositories temporarily in local code and disposing of them when done.

Generic Repository (Dependency Injection)

Method 2: Generic Repository + Dependency Injection (.NET Core);

// First, refer to the entry documentation to inject IFreeSql
services.AddFreeRepository();

// Use generic repository in the controller
public SongsController(IBaseRepository<Song> songRepository)
{
}

Inherited Repository (Dependency Injection)

// First, refer to the entry documentation to inject IFreeSql
services.AddFreeRepository(typeof(SongRepository).Assembly); // No need to pass the second parameter if no inherited repositories

// Use inherited repositories
public SongsController(SongRepository repo1, TopicRepository repo2)
{
}

public class SongRepository : BaseRepository<Song>
{
    public SongRepository(IFreeSql fsql) : base(fsql) {}

    // Add additional methods beyond CURD here
}

Update Comparison

Only update changed properties:

var repo = fsql.GetRepository<Topic>();
var item = repo.Where(a => a.Id == 1).First();  // Snapshot item at this point
item.Title = "newtitle";
repo.Update(item); // Compare changes from snapshot
// UPDATE `tb_topic` SET `Title` = ?p_0
// WHERE (`Id` = 1)

Does it seem cumbersome to query first and then update?

var repo = fsql.GetRepository<Topic>();
var item = new Topic { Id = 1 };
repo.Attach(item); // Snapshot item at this point
item.Title = "newtitle";
repo.Update(item); // Compare changes from snapshot
// UPDATE `tb_topic` SET `Title` = ?p_0
// WHERE (`Id` = 1)

repo.CompareState(item) can retrieve the status change information of item.

/// <summary>
/// Compare entities and calculate properties that have changed values, as well as the old and new values of these properties.
/// </summary>
/// <param name="newdata">The latest entity object, which will be compared with the attached entity's state.</param>
/// <returns>key: property name, value: [old value, new value]</returns>
Dictionary<string, object[]> CompareState(TEntity newdata);

It should be noted that when using Repository updates, ServerTime should not be specified in ColumnAttribute.

var repo = fsql.GetRepository<Dictionaries>();
var item = await repo.Where(a => a.DictId == "1").FirstAsync();

//If the ServerTime property exists in the Column attribute, it may result in the inability to modify it
item.UpdateTime =  DateTime.Now;
await repo.UpdateAsync(item);

public class Dictionaries
{
    [Column(Name = "id", IsPrimary = true)]
    public string Id { get; set; }

    [Column(Name = "name")]
    public string Name { get; set; }

    [Column(Name = "update_time", ServerTime = DateTimeKind.Local)]
    public DateTime? UpdateTime { get; set; }
}

Login Information (Dependency Injection)

repo.DbContextOptions.AuditValue is suitable for integration with AddScoped (Dependency Injection) to uniformly set login information.

Example: Automatically use login information when inserting/updating with repository

services.AddSingleton(fsql);
services.AddScoped(typeof(IBaseRepository<>), typeof(MyRepository<>));
services.AddScoped(typeof(IBaseRepository<,>), typeof(MyRepository<,>));
services.AddScoped(r => new MyRepositoryOptions
{
    AuditValue = e => {
        var user = r.GetService<User>();
        if (user == null) return;
        if (e.AuditValueType == AuditValueType.Insert &&
            e.Object is IEntityCreated obj1 && obj1 != null) {
            obj1.CreatedUserId = user.Id;
            obj1.CreatedUserName = user.Username;
        }
        if (e.AuditValueType == AuditValueType.Update &&
            e.Object is IEntityModified obj2 && obj2 != null) {
            obj2.ModifiedUserId = user.Id;
            obj2.ModifiedUserName = user.Username;
        }
    }
});

class MyRepository<TEntity, TKey> : BaseRepository<TEntity, TKey> where TEntity : class
{
    public MyRepository(IFreeSql fsql, MyRepositoryOptions options) : base(fsql)
    {
        if (options?.AuditValue != null) DbContextOptions.AuditValue += (_, e) => options.AuditValue(e);
    }
}
class MyRepository<TEntity> : MyRepository<TEntity, long> where TEntity : class
{
    public MyRepository(IFreeSql fsql, MyRepositoryOptions options) : base(fsql, options) { }
}
class MyRepositoryOptions
{
    public Action<DbContextAuditValueEventArgs> AuditValue { get; set; }
}

Compatibility Issues

The output inserted feature provided by SqlServer allows quick retrieval of inserted data when tables use auto-increment or default values defined in the database. PostgreSQL also has similar functionality, which is convenient but not supported by every database.

When using databases that do not support this feature (Sqlite/MySql/Oracle/Dameng/Nandasoft/MsAccess), and entities use auto-increment properties, batch inserts in the repository will be executed one by one. Consider the following improvements:

  • Use UUID as the primary key (i.e., Guid);
  • Avoid using default value functionality in the database;

Cascade Save

Please refer to the document 《Cascade Save》

API

PropertyReturn ValueDescription
EntityTypeTypeThe entity type the repository is currently operating on, note that it may not be TEntity
UnitOfWorkIUnitOfWorkThe unit of work currently in use
OrmIFreeSqlThe ORM currently in use
DbContextOptionsDbContextOptionsThe DbContext settings currently in use, changes to these settings do not affect others
UpdateDiyIUpdate<TEntity>Preparing to update data, in the same transaction as the repository
SelectISelect<TEntity>Preparing to query data
MethodReturn ValueParametersDescription
AsTypevoidTypeChange the entity type the repository is currently operating on
GetTEntityTKeyQuery data by primary key
FindTEntityTKeyQuery data by primary key
DeleteintTKeyDelete data by primary key
DeleteintLambdaDelete data based on lambda conditions
DeleteintTEntityDelete data
DeleteintIEnumerable<TEntity>Batch delete data
DeleteCascadeByDatabaseList<object>LambdaRecursively delete data by navigation properties
Insert-TEntityInsert data, if the entity has auto-increment columns, the auto-increment value will be filled into the entity after insertion
Insert-IEnumerable<TEntity>Batch insert data
Update-TEntityUpdate data
Update-IEnumerable<TEntity>Batch update data
InsertOrUpdate-TEntityInsert or update data
FlushState-NoneClear state management data
Attach-TEntityAttach entity to state management, used for updating or deleting without querying
Attach-IEnumerable<TEntity>Batch attach entities to state management
AttachOnlyPrimary-TEntityAttach only primary key data of entity to state management
BeginEdit-List<TEntity>Prepare to edit a list of entities
EndEditintNoneComplete editing data and perform save actions

State management allows Update to only update changed fields (not all fields), and using Attach and Update is very comfortable.