با تشکر از مطلب مفید شما.آیا میشه مثلا اون قسمت Map گزارش گوگل رو گذاشت توی سایت؟
ممنون از شما
var container = new Container(); container.Configure(x => { x.For(typeof(IUnitOfWork)).Use(typeof(UnitOfWork)).SetLifecycleTo<HttpContextLifecycle>(); x.Scan(scan => { scan.WithDefaultConventions(); scan.Assembly("Test.Services"); }); x.Policies.SetAllProperties(y => { y.WithAnyTypeFromNamespace("Test.Services.Interfaces"); }); var dynamicProxy = new ProxyGenerator(); x.For<ITestService>() .DecorateAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, container.GetInstance<CacheInterceptor>())); });
var urlHelper = ViewContext.HttpContext.Items.Values.OfType<IUrlHelper>().FirstOrDefault();
// How to inject the ViewContext automatically [ViewContext, HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } // How to use the injected ViewContext IUrlHelper urlHelper = new UrlHelper(ViewContext); var actionUrl = urlHelper.Action(action: nameof(MyController.Xyz), controller: nameof(MyController).Replace("Controller", string.Empty), values: new { //..., area = "SomeName" });
namespace BlazorTreeView.ViewModels; public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
using BlazorTreeView.ViewModels; namespace BlazorTreeView.Pages; public partial class TreeView { private IReadOnlyDictionary<string, object> ChildrenHtmlAttributes { get; } = new Dictionary<string, object>(StringComparer.Ordinal) { { "style", "list-style: none;" }, }; private IList<Comment> Comments { get; } = new List<Comment> { new() { Text = "پاسخ یک", }, new() { Text = "پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخ اول به پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخی به پاسخ اول پاسخ دو", }, }, }, new() { Text = "پاسخ دوم به پاسخ دو", }, }, }, new() { Text = "پاسخ سوم", }, }; }
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeView<TRecord> {
/// <summary> /// The treeview's self-referencing items /// </summary> [Parameter] public IEnumerable<TRecord>? Items { set; get; }
/// <summary> /// The treeview item's template /// </summary> [Parameter] public RenderFragment<TRecord>? ItemTemplate { set; get; }
/// <summary> /// The content displayed if the list is empty /// </summary> [Parameter] public RenderFragment? EmptyContentTemplate { set; get; }
public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
/// <summary> /// The property which returns the children items /// </summary> [Parameter] public Expression<Func<TRecord, IEnumerable<TRecord>>>? ChildrenSelector { set; get; }
<DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments"
public partial class DntTreeView<TRecord> { private Expression? _lastCompiledExpression; internal Func<TRecord, IEnumerable<TRecord>>? CompiledChildrenSelector { private set; get; } // ... protected override void OnParametersSet() { if (_lastCompiledExpression != ChildrenSelector) { CompiledChildrenSelector = ChildrenSelector?.Compile(); _lastCompiledExpression = ChildrenSelector; } } }
@namespace BlazorTreeView.Pages.Components @typeparam TRecord @if (Items is null || !Items.Any()) { @EmptyContentTemplate } else { <CascadingValue Value="this"> <ul @attributes="AdditionalAttributes"> @foreach (var item in Items) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> </CascadingValue> }
@namespace BlazorTreeView.Pages.Components @typeparam TRecord <li @attributes="@SafeOwnerTreeView.ChildrenHtmlAttributes" @key="ParentItem?.GetHashCode()"> @if (SafeOwnerTreeView.ItemTemplate is not null && ParentItem is not null) { @SafeOwnerTreeView.ItemTemplate(ParentItem) } @if (Children is not null) { <ul> @foreach (var item in Children) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> } </li>
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeViewChildrenItem<TRecord> { /// <summary> /// Defines the owner of this component. /// </summary> [CascadingParameter] public DntTreeView<TRecord>? OwnerTreeView { get; set; } private DntTreeView<TRecord> SafeOwnerTreeView => OwnerTreeView ?? throw new InvalidOperationException("`DntTreeViewChildrenItem` should be placed inside of a `DntTreeView`."); /// <summary> /// Nested parent item to display /// </summary> [Parameter] public TRecord? ParentItem { set; get; } private IEnumerable<TRecord>? Children => ParentItem is null || SafeOwnerTreeView.CompiledChildrenSelector is null ? null : SafeOwnerTreeView.CompiledChildrenSelector(ParentItem); }
<div class="card" dir="rtl"> <div class="card-header"> DntTreeView </div> <div class="card-body"> <DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments" style="list-style: none;" ChildrenHtmlAttributes="ChildrenHtmlAttributes"> <ItemTemplate Context="record"> <div class="card mb-1"> <div class="card-body"> <span>@record.Text</span> </div> </div> </ItemTemplate> <EmptyContentTemplate> <div class="alert alert-warning"> There is no item to display! </div> </EmptyContentTemplate> </DntTreeView> </div> </div>
public class Customer { public int Id { get; set; } public string Name { get; set; } = null!; public CustomerType Type { get; set; } } public enum CustomerType { Individual, Institution, }
void ManyQueriesManyCalls() { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); var baseQuery = context.Customers.Select(customer => new { customer.Name, customer.Type, customer.Id, }); var total = baseQuery.Count(); var types = baseQuery.GroupBy(x => x.Type) .Select(x => x.Key).ToList(); var pageSize = 10; var pageIndex = 0; var results = baseQuery .OrderBy(x => x.Id) .Skip(pageSize * pageIndex) .Take(pageSize) .ToList(); Console.WriteLine($"Total:{total}, First Type: {types.First()}, First Item: {results.First().Name}"); }
SELECT COUNT(*) FROM [Customers] AS [c] SELECT [c].[Type] FROM [Customers] AS [c] GROUP BY [c].[Type] SELECT [c].[Name], [c].[Type], [c].[Id] FROM [Customers] AS [c] ORDER BY [c].[Id] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
void ManyQueriesOnCall() { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<CustomerContext>(); var baseQuery = context.Customers.Select(customer => new { customer.Name, customer.Type, customer.Id, }); var pageSize = 10; var pageIndex = 0; var allTogether = baseQuery .GroupBy(x => 1) .Select(bq => new { Total = baseQuery.Count(), Types = baseQuery.GroupBy(x => x.Type) .Select(x => x.Key) .ToList(), Results = baseQuery .OrderBy(x => x.Id) .Skip(pageSize * pageIndex) .Take(pageSize) .ToList(), }) .FirstOrDefault(); Console.WriteLine($"Total:{allTogether.Total}, First Type: {allTogether.Types.First()}, First Item: {allTogether.Results.First().Name}"); }
SELECT [t0].[Key], [t1].[Type], [t2].[Name], [t2].[Type], [t2].[Id] FROM ( SELECT TOP(1) [t].[Key] FROM ( SELECT 1 AS [Key] FROM [Customers] AS [c] ) AS [t] GROUP BY [t].[Key] ) AS [t0] OUTER APPLY ( SELECT [c0].[Type] FROM [Customers] AS [c0] GROUP BY [c0].[Type] ) AS [t1] OUTER APPLY ( SELECT [c1].[Name], [c1].[Type], [c1].[Id] FROM [Customers] AS [c1] ORDER BY [c1].[Id] OFFSET @__p_1 ROWS FETCH NEXT @__pageSize_2 ROWS ONLY ) AS [t2] ORDER BY [t0].[Key], [t1].[Type], [t2].[Id]
مدیریت تصاویر کالا
در این قسمت امکان آپلود همزمان چندین فایل به همراه پیش نمایش آنها وجود دارد. همچنین امکان کشیدن و رها کردن برای تغییر ترتیب چیدمان عکسها نیز مهیا است.( تصویر اول به عنوان کاور کالا در نظر گرفته میشود.)
قابلیتهای دیگر:
- مدیریت تصاویر اسلایدشو و تغییر ترتیب آنها از طریق کشیدن و رها کردن (drag & drop)
- تعریف برگه و تغییر ترتیب نمایش آنها از طریق کشیدن و رها کردن
- امکان ارسال پست
- تعریف دسته بندی
- مدیریت کاربران
- تعریف تنظیمات سایت
- نمایش کالا و پستهای مشابه
کارهایی که باید انجام شود:
- پیاده سازی سبد خرید و خرید آنلاین
تصویر پنل مدیریت
تصویر صفحهی اصلی:
فناوری یا کتابخانه | توضیحات | مقالات مرتبط |
Bootstrap 3.x | فریم ورک پایه ای css سایت | - Bootstrap 3 RTL Theme - Twitter Bootstrap -سازگارسازی کلاسهای اعتبارسنجی Twitter Bootstrap 3 با فرمهای ASP.NET MVC -ساخت قالبهای نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC -نمایش اخطارها و پیامهای بوت استرپ به کمک TempData در ASP.NET MVC |
AdminLTE | قالب مدیریت سایت | - نسخه راستچین شده AdminLTE 2.2.1 |
Animate.css | انیمیشنهای css3 سایت | |
Font Awesome | پک آیکونهای برداری | |
Awesome Bootstrap Checkbox | زیبا سازی چک باکس ها | |
فونت فارسی وزیر | قلم فارسی | |
<div id="first" class="content">Content1 with css class and id</div> <div class="content"> Content2 with css class <a class="links" href="#">Link1</a> <a href="#">Link2</a> </div> <div> Content3 without css class and id </div> <input type="button" onclick="myFunc()" value="Run myFunc" /> <input type="text" id="myInput" /> <script type="text/javascript"> function myFunc() { loop(1000); loop(50000); } function loop(number) { for (var i = 0; i < number; i++) { } } </script>
$$("div.content");
profile("myFunc Testing"); myFunc(); profileEnd();
$("first");
$$("div.content")
به تفاوت دو دستور توجه کنید . خروجی دستور اول ، یک المنت است و خروجی دستور دوم یک آرایه از المنت که بین [ و ] قرار گرفته اند .
برای آشنایی بیشتر با CSS Seletorها به این لینک مراجعه کنید : http://www.w3.org/TR/css3-selectors
var objects = $x("html/body/div[2]/a") for(var i = 0; i < objects.length; i++) { console.log(objects[i]); }
var objects = $x("html/body/div[2]/a") dir(objects);
var node = $("first"); dirxml(node);
var node = $$("#first")[0]; dirxml(node);
var node = $("first"); inspect(node); // inspect in html tab inspect(node,'dom'); // inspect in dom tab
var obj = $("first"); keys(obj)
var obj = $("first"); values(obj)
debug(myFunc); myFunc(); undebug(myFunc);
monitor(myFunc); // now click on "Run myFunc" button
unmonitor(myFunc); // now click on "Run myFunc" button
var obj = $("myInput"); monitorEvents(obj,'keypress');
نتیجه پس از فشردن چند دکمهی کیبورد در myInput :
توضیحات بیشتر : http://getfirebug.com/wiki/index.php/MonitorEvents
profile("myFunc Testing"); myFunc(); profileEnd();
ستونهای Profiler :
Function : نام تابع اجرا شده .
Calls : تعداد دفعات فراخوانی تابع .
Percent : زمان اجرای تابع در زمان کل ، به درصد .
Own Time : زمان اجرای تابع به تنهایی . برای مثال در کد ما ، زمان اجرای تابع myFunc به تنهایی تقریبا صفر است زیرا عمیاتی در خود انجام نمیدهد و زمان صرف شده در این تابع ، برای اجرای 2 با تابع loop است . این زمان ( Own Time ) زمان اجرای تابع ، منهای زمان صرف شده برای فراخوانی توابع دیگر است .
Time : زمان اجرای تابع از نقطهی آغاز تا پایان . مجموع زمان اجرای خود تابع به همراه زمان اجرای توابع فراخوانی شده . در کد ما ، این زمان ، مجموع زمان اجرای خود تابع به همراه دو بار فراخوانی تابع loop است .
Avg : میانگین زمان اجرای هربار تابع . فرمول : Avg = Time / Calls
Min & Max : حداقل و حداکثر زمان اجرای تابع .
File : نام فایل و شماره خطی که تابع در آن قرار دارد .
برای طراحی گزارش شما میتوانید به سه روش این کار را انجام دهید.
1- طراحی در برنامه طراح گزارش
2- طراحی از داخل ویژوال استودیو
3- طراحی گزارش در زمان اجرا
برای شروع شما میتوانید نسخه آزمایشی این گزارشساز را دریافت کنید. تنها محدودیت این نسخه نمایش عبارت Demo در چاپ میباشد.
برنامه Designer را اجرا کنید. در صورتی که برای اولین بار است این برنامه را اجرا میکنید ابتدا باید رابط کاربری خود را انتخاب نمایید. نوار ابزار سمت چپ تمامی ابزارهای پرکاربرد طراحی گزارش را در اختیارتان قرار میدهد. ابزارهایی که در این بخش درباره آنها توضیح داده خواهد شد عبارتند از:
Header, Footer, Data, Page Header, Page Footer, Report Title, Report Summery
*به ابزارهای بالا Band گفته میشود.
Header , Footer :
همانطور که از نامشان پیداست در قسمت بالا و پایین بخشی از گزارش قرار میگیرند که برای استفاده در بالا و پایین بند Data میباشد. به عنوان مثال بند Header مناسب طراحی سرستونهای یک جدول میباشد و بند Footer هم جهت نمایش اطلاعات انتهایی یک جدول. ولی شما میتوانید با تنظیم خصوصیات هر بند رفتار و نمایش آنها را به طور کل تغییر دهید. نکته مثبت این گزارشساز این است که شما میتوانید بیش از یک واحد از هر بند را بر روی صفحه طراح خود قرار دهید، به عنوان مثال شما میتوانید دو بند Header داشته باشید که یکی در صفحات زوج و دیگری در صفحات فرد نمایش داده شود.
Data :
این بند جهت نمایش اطلاعات از منبع دادهها میباشد. به این معنا که به ازای هر سطر از دادهها یک بار این بخش نمایش داده میشود. تعداد دفعات نمایش این بند محدود به تعداد سطرهای منبع داده و یا اندازه صفحه و همچنین خصوصیت محدوده نمایش سطرها در یک صفحه میباشد.
Page Header , Page Footer :
این دو بند با توجه به نامشان جهت نمایش در بالا و پایین هر صفحه از گزارش میباشد. البته باز هم یادآور میشوم که با تغییر در خصوصیاتشان میتوانید رفتار و نحوه نمایش آنها را تغییر دهید.
Report Title :
این بند فقط در ابتدای گزارش نمایش داده خواهد شد.
Report Summery :
این بند بلافاصله بعد از اتمام گزارش نمایش داده خواهد شد.
مثال :
برای شروع در Designer یک گزارش جدید از نوع Blank Report ایجاد نمایید. سپس در پنل Dictionary بر روی New Item کلیک کرده و گزینه XML Data را انتخاب نمایید. با توجه به محل نصب گزارشساز وارد مسیر …\Bin\Data شده و فایلهای Demo.xsd و Demo.xml را برای قسمتهای مربوطه انتخاب نمایید. یک بار دیگر بر رو New Item کلیک کرده و گزینه New Data Source را انتخاب نمایید، از لیست ظاهر شده کانکشنی را که ایجاد کردهاید را انتخاب نمایید؛ نتیجه کار تا اینجا باید به صورت زبر باشد.
جدول Product را دراگ کرده و بر روی صفحه طراحی گزارش رها کنید. فرم Data ظاهر میشود این فرم را مطابق تصویر زیر تنظیم نمایید.
حال بر روی صفحه طراحی گزارش بندهای Header, Data, Footer مشاهده میشود؛ حال شما میتوانید با کلیک بر روی سربرگ Preview خروجی گزارش را ببینید.
توابع :
این گزارشساز دارای توابع بسیاری است که اکثر نیازهای شما را برطرف میکند به عنوان مثال تابع تبدیل عدد به حروف به زبان فارسی. همچنین شما میتوانید توابع خاص خود را ساخته و به صورت رفرنس به گزارش اضافه نمایید.
در این بخش ما از توابع موجود در گزارش استفاده خواهیم کرد. برای شروع بر روی کامپوننت Text در بند Footer زیر ستون UnitPrice دابل کلیک کرده تا فرم TextEditor ظاهر شود. سربرگ Summery را انتخاب نمایید. مطابق اطلاعات زیر بخشها را تنظیم نمایید.
Summery Function: Sum
Data Band: DataProducts
Data Column: Products.UnitPrice
حال بر روی سربرگ Preview
کلیک نمایید تا خروجی گزارش را ببینید. جمع ستون UnitPrice
فقط در صفحه آخر نمایش داده خواهد شد. اگر بخواهید جمع ستون در پایین هر صفحه
نمایش داده شود ابتدا باید خصوصیت Print on All Pages بند Footer به True
ست شود. سپس بر روی کامپوننت Text در بند Footer،
دابل کلیک نمایید و در فرم TextEditor
سربرگ Summery
تیک Running Total
را به حالت انتخاب شده در بیاورید، حال خروجی گزارش را ببینید، جمع در انتهای هر
صفحه ظاهر میشود.
متغیرها :
در این گزارش ساز دو نوع متغیر وجود دارد؛ نرمال و سیستمی. نوع سیستمی شامل متغیرهایی میشود که کاربرد مشخصی در تهیه گزارش دارند، مثل شماره صفحه، شماره ردیف، عنوان گزارش و ...
برای مثال شما میتوانید متغیر سیستمی Line را برای روی صفحه طراحی دراگ کنید. دو کامپوننت Text بر روی صفحه ایجاد میشود. اولی با محتوای Line و دومی با محتوای {Line}. اولی را به بند Header و دومی را به بند Data منتقل کنید و سپس خروجی گزارش را مشاهده نمایید، حال گزارش شما دارای شماره ردیف است.
متغیرهای نرمال تقریبا همانند متغیرهایی هستند که همه روزه شما در برنامههای خود از آنها استفاده میکنید. با کلیک بر روی New Item گزینه New Variable را انتخاب نمایید و نوع متغیر را Decimal انتخاب نمایید، سپس متغیر ایجاد شده را دراگ کرده و بروی صفحه طراحی قرار دهید و مشابه متغیر Line عمل کرده و کامپوننتهای Text را در بندهای مناسب قرار دهید. سپس بند Data بر روی صفحه طراحی را انتخاب نمایید، در پنل Properties بر روی Eventes کلیک کرده سپس در رویداد Rendering کد زیر را وارد نمایید.
Variable1 += Products.UnitPrice
حال در خروجی گزارش میتوانید مقادیر محاسبه
شده را ببینید. توجه داشته باشید که شما میتوانید در رویدادهای این گزارشساز به
زبان VB
و C#
برنامه نویسی کنید و محدود به یک خط کد نمیباشید.
شما میتوانید گزارش ساخته شده را به صورتهای مختلف ذخیره کنید از جمله کد C# و یا یک اپلیکیشن قابل اجرا.
@model ProjectName.ViewModels.Identity.RegisterViewModel <label asp-for="PhoneNumber"></label>
@using System.Reflection @using System.Linq.Expressions @using System.ComponentModel.DataAnnotations @typeparam T @if (ChildContent is null) { <label>@Label</label> } else { <label> @Label @ChildContent </label> } @code { [Parameter, EditorRequired] public Expression<Func<T>> DisplayNameFor { get; set; } = default!; [Parameter] public RenderFragment? ChildContent { get; set; } private string Label => GetDisplayName(); private string GetDisplayName() { var expression = (MemberExpression)DisplayNameFor.Body; var value = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute; return value?.Name ?? expression.Member.Name; } }
private LoginDto Login { get; } = new();
<CustomDisplayName DisplayNameFor="@(() => Login.UserName)" />
<CustomDisplayName DisplayNameFor="@(() => Login.UserName)"> <span class="text-danger">(*)</span> </CustomDisplayName>
@using System.Reflection @using System.Linq.Expressions; @using System.ComponentModel.DataAnnotations; @typeparam T @if (ChildContent == null) { <label>@Label</label> } else { <label> @Label @ChildContent </label> } @code { [Parameter] public Expression<Func<T>> DisplayNameFor { get; set; } = default!; [Parameter] public RenderFragment? ChildContent { get; set; } [Inject] public IStringLocalizerFactory LocalizerFactory { get; set; } = default!; private string Label => GetDisplayName(); private string GetDisplayName() { var expression = (MemberExpression)DisplayNameFor.Body; var displayAttribute = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute; if (displayAttribute is {ResourceType: not null }) { // Try to dynamically create an instance of the specified resource type var resourceType = displayAttribute.ResourceType; var localizer = LocalizerFactory.Create(resourceType); return localizer[displayAttribute.Name ?? expression.Member.Name]; } return displayAttribute?.Name ?? expression.Member.Name; } }
public class LoginDto { [Display(Name = nameof(LoginDtoResource.UserName), ResourceType = typeof(LoginDtoResource))] [Required] [MaxLength(100)] public string UserName { get; set; } = default!; //... }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> InputAttributes { get; set; } = new();
@if (ChildContent is null) { <label @attributes="InputAttributes"> @Label </label> } else { <label @attributes="InputAttributes"> @Label @ChildContent </label> }
<CustomDisplayName DisplayNameFor="@(() => Login.UserName)" class="form-label" id="test" for="UserName" />
using System; namespace EFCoreSoftDelete.Entities { public abstract class BaseEntity { public int Id { get; set; } public bool IsDeleted { set; get; } public DateTime? DeletedAt { set; get; } } }
using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace EFCoreSoftDelete.Entities { public class Blog : BaseEntity { public string Name { set; get; } public virtual ICollection<Post> Posts { set; get; } } public class BlogConfiguration : IEntityTypeConfiguration<Blog> { public void Configure(EntityTypeBuilder<Blog> builder) { builder.Property(blog => blog.Name).HasMaxLength(450).IsRequired(); builder.HasIndex(blog => blog.Name).IsUnique(); builder.HasData(new Blog { Id = 1, Name = "Blog 1" }); builder.HasData(new Blog { Id = 2, Name = "Blog 2" }); builder.HasData(new Blog { Id = 3, Name = "Blog 3" }); } } }
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace EFCoreSoftDelete.Entities { public class Post : BaseEntity { public string Title { set; get; } public Blog Blog { set; get; } public int BlogId { set; get; } } public class PostConfiguration : IEntityTypeConfiguration<Post> { public void Configure(EntityTypeBuilder<Post> builder) { builder.Property(post => post.Title).HasMaxLength(450); builder.HasOne(post => post.Blog).WithMany(blog => blog.Posts).HasForeignKey(post => post.BlogId); builder.HasData(new Post { Id = 1, BlogId = 1, Title = "Post 1" }); builder.HasData(new Post { Id = 2, BlogId = 1, Title = "Post 2" }); builder.HasData(new Post { Id = 3, BlogId = 1, Title = "Post 3" }); builder.HasData(new Post { Id = 4, BlogId = 1, Title = "Post 4" }); builder.HasData(new Post { Id = 5, BlogId = 2, Title = "Post 5" }); } } }
public class BlogConfiguration : IEntityTypeConfiguration<Blog> { public void Configure(EntityTypeBuilder<Blog> builder) { // ... builder.HasQueryFilter(blog => !blog.IsDeleted); } }
SELECT [b].[Id], [b].[DeletedAt], [b].[IsDeleted], [b].[Name] FROM [Blogs] AS [b] WHERE [b].[IsDeleted] <> CAST(1 AS bit)
namespace EFCoreSoftDelete.DataLayer { public static class GlobalFiltersManager { public static void ApplySoftDeleteQueryFilters(this ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model .GetEntityTypes() .Where(eType => typeof(BaseEntity).IsAssignableFrom(eType.ClrType))) { entityType.addSoftDeleteQueryFilter(); } } private static void addSoftDeleteQueryFilter(this IMutableEntityType entityData) { var methodToCall = typeof(GlobalFiltersManager) .GetMethod(nameof(getSoftDeleteFilter), BindingFlags.NonPublic | BindingFlags.Static) .MakeGenericMethod(entityData.ClrType); var filter = methodToCall.Invoke(null, new object[] { }); entityData.SetQueryFilter((LambdaExpression)filter); } private static LambdaExpression getSoftDeleteFilter<TEntity>() where TEntity : BaseEntity { return (Expression<Func<TEntity, bool>>)(entity => !entity.IsDeleted); } } }
namespace EFCoreSoftDelete.DataLayer { public class ApplicationDbContext : DbContext { //... protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(typeof(BaseEntity).Assembly); modelBuilder.ApplySoftDeleteQueryFilters(); }
namespace EFCoreSoftDelete.DataLayer { public static class AuditableEntitiesManager { public static void SetAuditableEntityOnBeforeSaveChanges(this ApplicationDbContext context) { var now = DateTime.UtcNow; foreach (var entry in context.ChangeTracker.Entries<BaseEntity>()) { switch (entry.State) { case EntityState.Added: //TODO: ... break; case EntityState.Modified: //TODO: ... break; case EntityState.Deleted: entry.State = EntityState.Unchanged; //NOTE: For soft-deletes to work with the original `Remove` method. entry.Entity.IsDeleted = true; entry.Entity.DeletedAt = now; break; } } } } }
var post1 = context.Posts.Find(1); if (post1 != null) { context.Remove(post1); context.SaveChanges(); }
Executing DbCommand [Parameters=[@p2='1', @p0='2020-09-17T05:11:32' (Nullable = true), @p1='True'], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [Posts] SET [DeletedAt] = @p0, [IsDeleted] = @p1 WHERE [Id] = @p2; SELECT @@ROWCOUNT;
namespace EFCoreSoftDelete.DataLayer { public class ApplicationDbContext : DbContext { // ... public override int SaveChanges(bool acceptAllChangesOnSuccess) { ChangeTracker.DetectChanges(); beforeSaveTriggers(); ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again. var result = base.SaveChanges(acceptAllChangesOnSuccess); ChangeTracker.AutoDetectChangesEnabled = true; return result; } // ... private void beforeSaveTriggers() { setAuditProperties(); } private void setAuditProperties() { this.SetAuditableEntityOnBeforeSaveChanges(); } } }
ar blog1 = context.Blogs.FirstOrDefault(blog => blog.Id == 1); if (blog1 != null) { context.Remove(blog1); context.SaveChanges(); }
var blog1AndItsRelatedPosts = context.Blogs .Include(blog => blog.Posts) .FirstOrDefault(blog => blog.Id == 1); if (blog1AndItsRelatedPosts != null) { context.Remove(blog1AndItsRelatedPosts); context.SaveChanges(); }
SELECT [t].[Id], [t].[DeletedAt], [t].[IsDeleted], [t].[Name], [t0].[Id], [t0].[BlogId], [t0].[DeletedAt], [t0].[IsDeleted], [t0].[Title] FROM ( SELECT TOP(1) [b].[Id], [b].[DeletedAt], [b].[IsDeleted], [b].[Name] FROM [Blogs] AS [b] WHERE ([b].[IsDeleted] <> CAST(1 AS bit)) AND ([b].[Id] = 1) ) AS [t] LEFT JOIN ( SELECT [p].[Id], [p].[BlogId], [p].[DeletedAt], [p].[IsDeleted], [p].[Title] FROM [Posts] AS [p] WHERE [p].[IsDeleted] <> CAST(1 AS bit) ) AS [t0] ON [t].[Id] = [t0].[BlogId] ORDER BY [t].[Id], [t0].[Id] Executing DbCommand [Parameters=[@p2='1', @p0='2020-09-17T05:25:00' (Nullable = true), @p1='True', @p5='2', @p3='2020-09-17T05:25:00' (Nullable = true), @p4='True', @p8='3', @p6='2020-09-17T05:25:00' (Nullable = true), @p7='True', @p11='4', @p9='2020-09-17T05:25:00' (Nullable = true), @p10='True'], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [Blogs] SET [DeletedAt] = @p0, [IsDeleted] = @p1 WHERE [Id] = @p2; SELECT @@ROWCOUNT; UPDATE [Posts] SET [DeletedAt] = @p3, [IsDeleted] = @p4 WHERE [Id] = @p5; SELECT @@ROWCOUNT; UPDATE [Posts] SET [DeletedAt] = @p6, [IsDeleted] = @p7 WHERE [Id] = @p8; SELECT @@ROWCOUNT; UPDATE [Posts] SET [DeletedAt] = @p9, [IsDeleted] = @p10 WHERE [Id] = @p11; SELECT @@ROWCOUNT;