Commit 2d9f0252 authored by 阿星Plus's avatar 阿星Plus

entity framework

parent dbf60efb
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace Plus.EntityFramework
{
/// <summary>
/// DatabaseFacadeExtensions
/// </summary>
public static class DatabaseFacadeExtensions
{
public static bool IsRelational(this DatabaseFacade database)
{
return ServiceProviderServiceExtensions.GetService<IRelationalConnection>(AccessorExtensions.GetInfrastructure(database)) != null;
}
}
}
\ No newline at end of file
using Plus.Domain.Uow;
namespace Plus.EntityFramework
{
/// <summary>
/// DbContextTypeMatcher
/// </summary>
public class DbContextTypeMatcher : DbContextTypeMatcher<PlusDbContext>
{
public DbContextTypeMatcher(ICurrentUnitOfWorkProvider currentUnitOfWorkProvider)
: base(currentUnitOfWorkProvider)
{
}
}
}
\ No newline at end of file
using Plus.Dependency;
using Plus.Domain.Repositories;
using Plus.Domain.Uow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Plus.EntityFramework
{
/// <summary>
/// DbContextTypeMatcher
/// </summary>
/// <typeparam name="TBaseDbContext"></typeparam>
public abstract class DbContextTypeMatcher<TBaseDbContext> : IDbContextTypeMatcher, ISingletonDependency
{
private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
private readonly Dictionary<Type, List<Type>> _dbContextTypes;
protected DbContextTypeMatcher(ICurrentUnitOfWorkProvider currentUnitOfWorkProvider)
{
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
_dbContextTypes = new Dictionary<Type, List<Type>>();
}
public void Populate(Type[] dbContextTypes)
{
foreach (Type type in dbContextTypes)
{
List<Type> list = new List<Type>();
AddWithBaseTypes(type, list);
foreach (Type item in list)
{
Add(item, type);
}
}
}
public virtual Type GetConcreteType(Type sourceDbContextType)
{
if (!sourceDbContextType.GetTypeInfo().IsAbstract)
{
return sourceDbContextType;
}
List<Type> orDefault = _dbContextTypes.GetOrDefault(sourceDbContextType);
if (orDefault.IsNullOrEmpty())
{
throw new PlusException("Could not find a concrete implementation of given DbContext type: " + sourceDbContextType.AssemblyQualifiedName);
}
if (orDefault.Count == 1)
{
return orDefault[0];
}
CheckCurrentUow();
return GetDefaultDbContextType(orDefault, sourceDbContextType);
}
private void CheckCurrentUow()
{
if (_currentUnitOfWorkProvider.Current == null)
{
throw new PlusException("GetConcreteType method should be called in a UOW.");
}
}
private static Type GetDefaultDbContextType(List<Type> dbContextTypes, Type sourceDbContextType)
{
List<Type> list = (from type in dbContextTypes
where !type.GetTypeInfo().IsDefined(typeof(AutoRepositoryTypesAttribute), inherit: true)
select type).ToList();
if (list.Count == 1)
{
return list[0];
}
list = (from type in list
where type.GetTypeInfo().IsDefined(typeof(DefaultDbContextAttribute), inherit: true)
select type).ToList();
if (list.Count == 1)
{
return list[0];
}
throw new PlusException($"Found more than one concrete type for given DbContext Type ({sourceDbContextType}). Found types: {(from c in dbContextTypes select c.AssemblyQualifiedName).JoinAsString(", ")}.");
}
private static void AddWithBaseTypes(Type dbContextType, List<Type> types)
{
types.Add(dbContextType);
if (dbContextType != typeof(TBaseDbContext))
{
AddWithBaseTypes(dbContextType.GetTypeInfo().BaseType, types);
}
}
private void Add(Type sourceDbContextType, Type targetDbContextType)
{
if (!_dbContextTypes.ContainsKey(sourceDbContextType))
{
_dbContextTypes[sourceDbContextType] = new List<Type>();
}
_dbContextTypes[sourceDbContextType].Add(targetDbContextType);
}
}
}
\ No newline at end of file
using System;
namespace Plus.EntityFramework
{
/// <summary>
/// DefaultDbContextAttribute
/// </summary>
public class DefaultDbContextAttribute : Attribute
{
}
}
\ No newline at end of file
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Linq;
namespace Plus.EntityFramework.Extensions
{
public static class EntityEntryExtensions
{
/// <summary>
/// 检查实体及其关联的所属实体是否已更改
/// </summary>
/// <param name="entry"></param>
/// <returns></returns>
public static bool CheckOwnedEntityChange(this EntityEntry entry)
{
return entry.State == EntityState.Modified ||
entry.References.Any(r =>
r.TargetEntry != null && r.TargetEntry.Metadata.IsOwned() && CheckOwnedEntityChange(r.TargetEntry));
}
}
}
\ No newline at end of file
using System;
namespace Plus.EntityFramework
{
/// <summary>
/// IDbContextTypeMatcher
/// </summary>
public interface IDbContextTypeMatcher
{
void Populate(Type[] dbContextTypes);
Type GetConcreteType(Type sourceDbContextType);
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plus\Plus.csproj" />
</ItemGroup>
</Project>
using Castle.Core.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Plus.Dependency;
using Plus.Domain.Entities;
using Plus.Domain.Entities.Auditing;
using Plus.Domain.Repositories;
using Plus.Domain.Uow;
using Plus.EntityFramework.Extensions;
using Plus.Event.Bus;
using Plus.Event.Bus.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace Plus.EntityFramework
{
/// <summary>
/// PlusDbContext
/// </summary>
public abstract class PlusDbContext : DbContext, ITransientDependency
{
private class ReplaceExpressionVisitor : ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
{
return _newValue;
}
return base.Visit(node);
}
}
public IEntityChangeEventHelper EntityChangeEventHelper { get; set; }
public ILogger Logger { get; set; }
public IEventBus EventBus { get; set; }
public IGuidGenerator GuidGenerator { get; set; }
public ICurrentUnitOfWorkProvider CurrentUnitOfWorkProvider { get; set; }
protected virtual bool IsSoftDeleteFilterEnabled => CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(PlusDataFilters.SoftDelete) == true;
protected PlusDbContext(DbContextOptions options)
: base(options)
{
InitializeDbContext();
}
private void InitializeDbContext()
{
SetNullsForInjectedProperties();
}
private void SetNullsForInjectedProperties()
{
Logger = NullLogger.Instance;
EntityChangeEventHelper = NullEntityChangeEventHelper.Instance;
GuidGenerator = SequentialGuidGenerator.Instance;
EventBus = NullEventBus.Instance;
}
protected void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType entityType) where TEntity : class
{
if (entityType.BaseType != null || !ShouldFilterEntity<TEntity>(entityType))
{
return;
}
Expression<Func<TEntity, bool>> expression = CreateFilterExpression<TEntity>();
if (expression != null)
{
if (entityType.IsQueryType)
{
modelBuilder.Query<TEntity>().HasQueryFilter(expression);
}
else
{
modelBuilder.Entity<TEntity>().HasQueryFilter(expression);
}
}
}
protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) where TEntity : class
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return false;
}
protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() where TEntity : class
{
Expression<Func<TEntity, bool>> expression = null;
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> softDeleteFilter = e => !((ISoftDelete)e).IsDeleted || ((ISoftDelete)e).IsDeleted != IsSoftDeleteFilterEnabled;
expression = expression == null ? softDeleteFilter : CombineExpressions(expression, softDeleteFilter);
}
return expression;
}
public override int SaveChanges()
{
try
{
var changeReport = ApplyAbpConcepts();
var result = base.SaveChanges();
EntityChangeEventHelper.TriggerEvents(changeReport);
return result;
}
catch (DbUpdateConcurrencyException ex)
{
throw new PlusDbConcurrencyException(ex.Message, ex);
}
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
try
{
var changeReport = ApplyAbpConcepts();
var result = await base.SaveChangesAsync(cancellationToken);
await EntityChangeEventHelper.TriggerEventsAsync(changeReport);
return result;
}
catch (DbUpdateConcurrencyException ex)
{
throw new PlusDbConcurrencyException(ex.Message, ex);
}
}
protected virtual EntityChangeReport ApplyAbpConcepts()
{
var changeReport = new EntityChangeReport();
var userId = GetAuditUserId();
foreach (var entry in ChangeTracker.Entries().ToList())
{
if (entry.State != EntityState.Modified && entry.CheckOwnedEntityChange())
{
Entry(entry.Entity).State = EntityState.Modified;
}
ApplyAbpConcepts(entry, userId, changeReport);
}
return changeReport;
}
protected virtual void ApplyAbpConcepts(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
switch (entry.State)
{
case EntityState.Added:
ApplyPlusConceptsForAddedEntity(entry, userId, changeReport);
break;
case EntityState.Modified:
ApplyPlusConceptsForAddedEntity(entry, userId, changeReport);
break;
case EntityState.Deleted:
ApplyPlusConceptsForAddedEntity(entry, userId, changeReport);
break;
}
AddDomainEvents(changeReport.DomainEvents, entry.Entity);
}
protected virtual void ApplyPlusConceptsForAddedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
CheckAndSetId(entry);
SetCreationAuditProperties(entry.Entity, userId);
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Created));
}
protected virtual void ApplyPlusConceptsForModifiedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
SetModificationAuditProperties(entry.Entity, userId);
if (entry.Entity is ISoftDelete && entry.Entity.As<ISoftDelete>().IsDeleted)
{
SetDeletionAuditProperties(entry.Entity, userId);
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
}
else
{
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated));
}
}
protected virtual void ApplyPlusConceptsForDeletedEntity(EntityEntry entry, long? userId, EntityChangeReport changeReport)
{
if (IsHardDeleteEntity(entry))
{
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
return;
}
CancelDeletionForSoftDelete(entry);
SetDeletionAuditProperties(entry.Entity, userId);
changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted));
}
protected virtual bool IsHardDeleteEntity(EntityEntry entry)
{
if (CurrentUnitOfWorkProvider?.Current?.Items == null)
{
return false;
}
if (!CurrentUnitOfWorkProvider.Current.Items.ContainsKey(UnitOfWorkExtensionDataTypes.HardDelete))
{
return false;
}
var hardDeleteItems = CurrentUnitOfWorkProvider.Current.Items[UnitOfWorkExtensionDataTypes.HardDelete];
if (!(hardDeleteItems is HashSet<string> objects))
{
return false;
}
var hardDeleteKey = EntityHelper.GetHardDeleteKey(entry.Entity);
return objects.Contains(hardDeleteKey);
}
protected virtual void AddDomainEvents(List<DomainEventEntry> domainEvents, object entityAsObj)
{
var generatesDomainEventsEntity = entityAsObj as IGeneratesDomainEvents;
if (generatesDomainEventsEntity == null)
{
return;
}
if (generatesDomainEventsEntity.DomainEvents.IsNullOrEmpty())
{
return;
}
domainEvents.AddRange(generatesDomainEventsEntity.DomainEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData)));
generatesDomainEventsEntity.DomainEvents.Clear();
}
protected virtual long? GetAuditUserId()
{
return null;
}
protected virtual void CheckAndSetId(EntityEntry entry)
{
var entity = entry.Entity as IEntity<Guid>;
if (entity != null && entity.Id == Guid.Empty)
{
var idPropertyEntry = entry.Property("Id");
if (idPropertyEntry != null && idPropertyEntry.Metadata.ValueGenerated == ValueGenerated.Never)
{
entity.Id = GuidGenerator.Create();
}
}
}
protected virtual void SetCreationAuditProperties(object entityAsObj, long? userId)
{
EntityAuditingHelper.SetCreationAuditProperties(entityAsObj, userId);
}
protected virtual void SetModificationAuditProperties(object entityAsObj, long? userId)
{
EntityAuditingHelper.SetModificationAuditProperties(entityAsObj, userId);
}
protected virtual void CancelDeletionForSoftDelete(EntityEntry entry)
{
if (!(entry.Entity is ISoftDelete))
{
return;
}
entry.Reload();
entry.State = EntityState.Modified;
entry.Entity.As<ISoftDelete>().IsDeleted = true;
}
protected virtual void SetDeletionAuditProperties(object entityAsObj, long? userId)
{
if (entityAsObj is IHasDeletionTime)
{
var entity = entityAsObj.As<IHasDeletionTime>();
if (entity.DeletionTime == null)
{
entity.DeletionTime = DateTime.Now;
}
}
if (entityAsObj is IHasDeletionTime)
{
var entity = entityAsObj.As<IDeletionAudited>();
if (entity.DeleterUserId != null)
{
return;
}
if (userId == null)
{
entity.DeleterUserId = null;
return;
}
}
}
protected virtual Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T));
ReplaceExpressionVisitor replaceExpressionVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameterExpression);
Expression left = replaceExpressionVisitor.Visit(expression1.Body);
ReplaceExpressionVisitor replaceExpressionVisitor2 = new ReplaceExpressionVisitor(expression2.Parameters[0], parameterExpression);
Expression right = replaceExpressionVisitor2.Visit(expression2.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), new ParameterExpression[1]
{
parameterExpression
});
}
}
}
\ No newline at end of file
using System;
namespace Plus.Domain.Entities.Auditing
{
public static class EntityAuditingHelper
{
public static void SetCreationAuditProperties(object entityAsObj, long? userId)
{
IHasCreationTime hasCreationTime = entityAsObj as IHasCreationTime;
if (hasCreationTime == null)
{
return;
}
if (hasCreationTime.CreationTime == default(DateTime))
{
hasCreationTime.CreationTime = DateTime.Now;
}
if (entityAsObj is ICreationAudited && userId.HasValue)
{
ICreationAudited creationAudited = entityAsObj as ICreationAudited;
if (!creationAudited.CreatorUserId.HasValue)
{
creationAudited.CreatorUserId = userId;
}
}
}
public static void SetModificationAuditProperties(object entityAsObj, long? userId)
{
if (entityAsObj is IHasModificationTime)
{
Extensions.As<IHasModificationTime>(entityAsObj).LastModificationTime = DateTime.Now;
}
if (entityAsObj is IModificationAudited)
{
IModificationAudited modificationAudited = Extensions.As<IModificationAudited>(entityAsObj);
if (!userId.HasValue)
{
modificationAudited.LastModifierUserId = null;
}
else
{
modificationAudited.LastModifierUserId = userId;
}
}
}
}
}
\ No newline at end of file
namespace Plus.Domain.Entities.Auditing
{
public interface ICreationAudited : IHasCreationTime
{
long? CreatorUserId
{
get;
set;
}
}
public interface ICreationAudited<TUser> : ICreationAudited, IHasCreationTime where TUser : IEntity<long>
{
TUser CreatorUser
{
get;
set;
}
}
}
\ No newline at end of file
namespace Plus.Domain.Entities.Auditing
{
public interface IDeletionAudited : IHasDeletionTime, ISoftDelete
{
long? DeleterUserId
{
get;
set;
}
}
public interface IDeletionAudited<TUser> : IDeletionAudited, IHasDeletionTime, ISoftDelete where TUser : IEntity<long>
{
TUser DeleterUser
{
get;
set;
}
}
}
\ No newline at end of file
using System;
namespace Plus.Domain.Entities.Auditing
{
public interface IHasCreationTime
{
DateTime CreationTime
{
get;
set;
}
}
}
using System;
namespace Plus.Domain.Entities.Auditing
{
public interface IHasDeletionTime : ISoftDelete
{
DateTime? DeletionTime
{
get;
set;
}
}
}
\ No newline at end of file
using System;
namespace Plus.Domain.Entities.Auditing
{
public interface IHasModificationTime
{
DateTime? LastModificationTime
{
get;
set;
}
}
}
\ No newline at end of file
namespace Plus.Domain.Entities.Auditing
{
public interface IModificationAudited : IHasModificationTime
{
long? LastModifierUserId
{
get;
set;
}
}
public interface IModificationAudited<TUser> : IModificationAudited, IHasModificationTime where TUser : IEntity<long>
{
TUser LastModifierUser
{
get;
set;
}
}
}
\ No newline at end of file
using Plus.Reflection;
using System;
namespace Plus.Domain.Entities
{
/// <summary>
/// EntityHelper
/// </summary>
public static class EntityHelper
{
public static bool IsEntity(Type type)
{
return ReflectionHelper.IsAssignableToGenericType(type, typeof(IEntity<>));
}
public static Type GetPrimaryKeyType<TEntity>()
{
return GetPrimaryKeyType(typeof(TEntity));
}
public static Type GetPrimaryKeyType(Type entityType)
{
Type[] interfaces = entityType.GetInterfaces();
foreach (Type type in interfaces)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEntity<>))
{
return type.GenericTypeArguments[0];
}
}
throw new PlusException("Can not find primary key type of given entity type: " + entityType + ". Be sure that this entity type implements IEntity<TPrimaryKey> interface");
}
public static object GetEntityId(object entity)
{
if (!ReflectionHelper.IsAssignableToGenericType(entity.GetType(), typeof(IEntity<>)))
{
throw new PlusException(entity.GetType() + " is not an Entity !");
}
return ReflectionHelper.GetValueByPath(entity, entity.GetType(), "Id");
}
public static string GetHardDeleteKey(object entity)
{
return entity.GetType().FullName + ";Id=" + GetEntityId(entity);
}
}
}
\ No newline at end of file
using Plus.Event.Bus;
using System.Collections.Generic;
namespace Plus.Domain.Entities
{
/// <summary>
/// IGeneratesDomainEvents
/// </summary>
public interface IGeneratesDomainEvents
{
ICollection<IEventData> DomainEvents
{
get;
}
}
}
\ No newline at end of file
namespace Plus.Domain.Entities
{
public interface ISoftDelete
{
bool IsDeleted
{
get;
set;
}
}
}
\ No newline at end of file
......@@ -3,7 +3,7 @@
/// <summary>
/// UnitOfWorkExtensionDataTypes
/// </summary>
internal class UnitOfWorkExtensionDataTypes
public class UnitOfWorkExtensionDataTypes
{
public static string HardDelete { get; } = "HardDelete";
}
......
using System;
using System.Runtime.Serialization;
namespace Plus.Domain.Uow
{
/// <summary>
/// PlusDbConcurrencyException
/// </summary>
[Serializable]
public class PlusDbConcurrencyException : PlusException
{
public PlusDbConcurrencyException()
{
}
public PlusDbConcurrencyException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
public PlusDbConcurrencyException(string message)
: base(message)
{
}
public PlusDbConcurrencyException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
\ No newline at end of file
using System;
namespace Plus.Event.Bus.Entities
{
[Serializable]
public class DomainEventEntry
{
public object SourceEntity
{
get;
}
public IEventData EventData
{
get;
}
public DomainEventEntry(object sourceEntity, IEventData eventData)
{
SourceEntity = sourceEntity;
EventData = eventData;
}
}
}
\ No newline at end of file
using System;
namespace Plus.Event.Bus.Entities
{
[Serializable]
public class EntityChangeEntry
{
public object Entity
{
get;
set;
}
public EntityChangeType ChangeType
{
get;
set;
}
public EntityChangeEntry(object entity, EntityChangeType changeType)
{
Entity = entity;
ChangeType = changeType;
}
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace Plus.Event.Bus.Entities
{
public class EntityChangeReport
{
public List<EntityChangeEntry> ChangedEntities
{
get;
}
public List<DomainEventEntry> DomainEvents
{
get;
}
public EntityChangeReport()
{
ChangedEntities = new List<EntityChangeEntry>();
DomainEvents = new List<DomainEventEntry>();
}
public bool IsEmpty()
{
return ChangedEntities.Count <= 0 && DomainEvents.Count <= 0;
}
public override string ToString()
{
return $"[EntityChangeReport] ChangedEntities: {ChangedEntities.Count}, DomainEvents: {DomainEvents.Count}";
}
}
}
\ No newline at end of file
namespace Plus.Event.Bus.Entities
{
public enum EntityChangeType
{
Created,
Updated,
Deleted
}
}
\ No newline at end of file
using System.Threading.Tasks;
namespace Plus.Event.Bus.Entities
{
public interface IEntityChangeEventHelper
{
void TriggerEvents(EntityChangeReport changeReport);
Task TriggerEventsAsync(EntityChangeReport changeReport);
void TriggerEntityCreatingEvent(object entity);
void TriggerEntityCreatedEventOnUowCompleted(object entity);
void TriggerEntityUpdatingEvent(object entity);
void TriggerEntityUpdatedEventOnUowCompleted(object entity);
void TriggerEntityDeletingEvent(object entity);
void TriggerEntityDeletedEventOnUowCompleted(object entity);
}
}
\ No newline at end of file
using System.Threading.Tasks;
namespace Plus.Event.Bus.Entities
{
public class NullEntityChangeEventHelper : IEntityChangeEventHelper
{
public static NullEntityChangeEventHelper Instance
{
get;
} = new NullEntityChangeEventHelper();
private NullEntityChangeEventHelper()
{
}
public void TriggerEntityCreatingEvent(object entity)
{
}
public void TriggerEntityCreatedEventOnUowCompleted(object entity)
{
}
public void TriggerEntityUpdatingEvent(object entity)
{
}
public void TriggerEntityUpdatedEventOnUowCompleted(object entity)
{
}
public void TriggerEntityDeletingEvent(object entity)
{
}
public void TriggerEntityDeletedEventOnUowCompleted(object entity)
{
}
public void TriggerEvents(EntityChangeReport changeReport)
{
}
public Task TriggerEventsAsync(EntityChangeReport changeReport)
{
return Task.FromResult(0);
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment