added document management page volosoft/volo#2245

pull/4392/head
Alper Ebicoglu 5 years ago
parent 84daf69ea3
commit 0e24004edf

@ -0,0 +1,40 @@
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
namespace Volo.Docs.Admin.Documents
{
[Serializable]
public class DocumentDto : EntityDto<Guid>
{
public virtual Guid ProjectId { get; protected set; }
public virtual string Name { get; protected set; }
public virtual string Version { get; protected set; }
public virtual string LanguageCode { get; protected set; }
public virtual string FileName { get; set; }
public virtual string Content { get; set; }
public virtual string Format { get; set; }
public virtual string EditLink { get; set; }
public virtual string RootUrl { get; set; }
public virtual string RawRootUrl { get; set; }
public virtual string LocalDirectory { get; set; }
public virtual DateTime CreationTime { get; set; }
public virtual DateTime LastUpdatedTime { get; set; }
public virtual DateTime? LastSignificantUpdateTime { get; set; }
public virtual DateTime LastCachedTime { get; set; }
}
}

@ -0,0 +1,21 @@
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
using Volo.Docs.Documents;
namespace Volo.Docs.Admin.Documents
{
public class GetAllInput : PagedAndSortedResultRequestDto
{
public Guid? ProjectId { get; set; }
[StringLength(DocumentConsts.MaxNameLength)]
public string Name { get; set; }
[StringLength(DocumentConsts.MaxLanguageCodeNameLength)]
public string LanguageCode { get; set; }
[StringLength(DocumentConsts.MaxVersionNameLength)]
public string Version { get; set; }
}
}

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace Volo.Docs.Admin.Documents
@ -11,5 +13,13 @@ namespace Volo.Docs.Admin.Documents
Task PullAllAsync(PullAllDocumentInput input);
Task PullAsync(PullDocumentInput input);
Task<PagedResultDto<DocumentDto>> GetAllAsync(GetAllInput input);
Task RemoveFromCacheAsync(Guid documentId);
Task ReindexAsync(Guid documentId);
Task DeleteFromDatabaseAsync(Guid documentId);
}
}

@ -7,6 +7,7 @@
"Permission:Delete": "Delete",
"Permission:Create": "Create",
"Permission:Documents": "Documents",
"Menu:Documents": "Documents",
"Menu:DocumentManagement": "Documents",
"Menu:ProjectManagement": "Projects",
"CreateANewProject": "Create new project",
@ -32,6 +33,16 @@
"DisplayName:GitHubUserAgent": "GitHub user agent",
"DisplayName:All": "Pull all",
"DisplayName:LanguageCode": "Language code",
"DisplayName:Version": "Version"
"DisplayName:Version": "Version",
"Documents": "Documents",
"RemoveFromCache": "Remove from cache",
"Reindex": "Reindex",
"ReindexCompleted": "Reindex completed",
"RemovedFromCache": "Removed from cache",
"RemoveFromCacheConfirmation": "Are you sure you want to remove this item from cache?",
"ReIndexDocumentConfirmation": "Are you sure you want to reindex this item?",
"DeleteDocumentFromDbConfirmation": "Are you sure you want to delete this item from database?",
"DeleteFromDatabase": "Delete from database",
"Deleted": "Deleted"
}
}

@ -1,5 +1,7 @@
using AutoMapper;
using Volo.Docs.Admin.Documents;
using Volo.Docs.Admin.Projects;
using Volo.Docs.Documents;
using Volo.Docs.Projects;
namespace Volo.Docs.Admin
@ -9,6 +11,7 @@ namespace Volo.Docs.Admin
public DocsAdminApplicationAutoMapperProfile()
{
CreateMap<Project, ProjectDto>();
CreateMap<Document, DocumentDto>();
}
}
}

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Newtonsoft.Json;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Docs.Caching;
@ -25,13 +25,15 @@ namespace Volo.Docs.Admin.Documents
private readonly IDistributedCache<DocumentUpdateInfo> _documentUpdateCache;
private readonly IDistributedCache<List<VersionInfo>> _versionCache;
private readonly IDistributedCache<LanguageConfig> _languageCache;
private readonly IDocumentFullSearch _documentFullSearch;
public DocumentAdminAppService(IProjectRepository projectRepository,
IDocumentRepository documentRepository,
IDocumentSourceFactory documentStoreFactory,
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
IDistributedCache<List<VersionInfo>> versionCache,
IDistributedCache<LanguageConfig> languageCache)
IDistributedCache<LanguageConfig> languageCache,
IDocumentFullSearch documentFullSearch)
{
_projectRepository = projectRepository;
_documentRepository = documentRepository;
@ -39,6 +41,7 @@ namespace Volo.Docs.Admin.Documents
_documentUpdateCache = documentUpdateCache;
_versionCache = versionCache;
_languageCache = languageCache;
_documentFullSearch = documentFullSearch;
LocalizationResource = typeof(DocsResource);
}
@ -57,7 +60,13 @@ namespace Volo.Docs.Admin.Documents
foreach (var document in documents)
{
var documentUpdateInfoCacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, document.Name, document.LanguageCode, document.LanguageCode);
var documentUpdateInfoCacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(
project: project,
documentName: document.Name,
languageCode: document.LanguageCode,
version: document.Version
);
await _documentUpdateCache.RemoveAsync(documentUpdateInfoCacheKey);
document.LastCachedTime = DateTime.MinValue;
@ -125,6 +134,67 @@ namespace Volo.Docs.Admin.Documents
await UpdateDocumentUpdateInfoCache(sourceDocument);
}
public async Task<PagedResultDto<DocumentDto>> GetAllAsync(GetAllInput input)
{
var totalCount = await _documentRepository.GetAllCountAsync(
projectId: input.ProjectId,
name: input.Name,
version: input.Version,
languageCode: input.LanguageCode,
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount
);
var docs = await _documentRepository.GetAllAsync(
projectId: input.ProjectId,
name: input.Name,
version: input.Version,
languageCode: input.LanguageCode,
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount
);
return new PagedResultDto<DocumentDto>
{
TotalCount = totalCount,
Items = ObjectMapper.Map<List<Document>, List<DocumentDto>>(docs)
};
}
public async Task RemoveFromCacheAsync(Guid documentId)
{
var document = await _documentRepository.GetAsync(documentId);
var project = await _projectRepository.GetAsync(document.ProjectId);
var documentUpdateInfoCacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(
project: project,
documentName: document.Name,
languageCode: document.LanguageCode,
version: document.Version
);
await _documentUpdateCache.RemoveAsync(documentUpdateInfoCacheKey);
document.LastCachedTime = DateTime.MinValue;
await _documentRepository.UpdateAsync(document);
}
public async Task ReindexAsync(Guid documentId)
{
await _documentFullSearch.DeleteAsync(documentId);
var document = await _documentRepository.GetAsync(documentId);
await _documentFullSearch.AddOrUpdateAsync(document);
}
public async Task DeleteFromDatabaseAsync(Guid documentId)
{
var document = await _documentRepository.GetAsync(documentId);
await _documentRepository.DeleteAsync(document);
}
private async Task UpdateDocumentUpdateInfoCache(Document document)
{
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}";

@ -1,6 +1,9 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Docs.Admin.Documents;
@ -37,7 +40,35 @@ namespace Volo.Docs.Admin
[Route("Pull")]
public Task PullAsync(PullDocumentInput input)
{
return _documentAdminAppService.PullAsync(input);
return _documentAdminAppService.PullAsync(input);
}
[HttpGet]
[Route("GetAll")]
public Task<PagedResultDto<DocumentDto>> GetAllAsync(GetAllInput input)
{
return _documentAdminAppService.GetAllAsync(input);
}
[HttpPut]
[Route("RemoveDocumentFromCache")]
public async Task RemoveFromCacheAsync(Guid documentId)
{
await _documentAdminAppService.RemoveFromCacheAsync(documentId);
}
[HttpPut]
[Route("ReindexDocument")]
public async Task ReindexAsync(Guid documentId)
{
await _documentAdminAppService.ReindexAsync(documentId);
}
[HttpDelete]
[Route("DeleteDocumentFromDatabase")]
public async Task DeleteFromDatabaseAsync(Guid documentId)
{
await _documentAdminAppService.DeleteFromDatabaseAsync(documentId);
}
}
}

@ -23,7 +23,7 @@ namespace Volo.Docs.Admin.Navigation
var l = context.GetLocalizer<DocsResource>();
var rootMenuItem = new ApplicationMenuItem(DocsMenuNames.GroupName, l["Menu:DocumentManagement"], icon: "fa fa-book");
var rootMenuItem = new ApplicationMenuItem(DocsMenuNames.GroupName, l["Menu:Documents"], icon: "fa fa-book");
administrationMenu.AddItem(rootMenuItem);
@ -32,6 +32,10 @@ namespace Volo.Docs.Admin.Navigation
rootMenuItem.AddItem(new ApplicationMenuItem(DocsMenuNames.Projects, l["Menu:ProjectManagement"], "~/Docs/Admin/Projects"));
}
if (await context.IsGrantedAsync(DocsAdminPermissions.Documents.Default))
{
rootMenuItem.AddItem(new ApplicationMenuItem(DocsMenuNames.Documents, l["Menu:DocumentManagement"], "~/Docs/Admin/Documents"));
}
}
}
}

@ -11,5 +11,6 @@ namespace Volo.Docs.Admin.Navigation
public const string Projects = GroupName + ".Projects";
public const string Documents = GroupName + ".Documents";
}
}

@ -0,0 +1,52 @@
@page
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@using Volo.Docs.Admin
@using Volo.Docs.Admin.Navigation
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Docs.Localization
@model Volo.Docs.Admin.Pages.Docs.Admin.Documents.IndexModel
@inject IHtmlLocalizer<DocsResource> L
@inject IAuthorizationService Authorization
@{
ViewBag.PageTitle = L["Menu:Documents"];
}
@inject IPageLayout PageLayout
@{
PageLayout.Content.Title = L["Documents"].Value;
PageLayout.Content.BreadCrumb.Add(L["Menu:DocumentManagement"].Value);
PageLayout.Content.MenuItemName = DocsMenuNames.Documents;
}
@section scripts {
<abp-script src="/Pages/Docs/Admin/Documents/index.js" />
}
<abp-card>
<abp-card-header>
<abp-row>
<h2>Filters ...</h2>
@*<abp-column size-md="_6" class="text-right">
<abp-button button-type="Primary" icon="plus" text="@L["ReIndexAllProjects"].Value" id="ReIndexAllProjects" />
</abp-column>*@
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="DocumentsTable" class="nowrap">
<thead>
<tr>
<th>@L["Actions"]</th>
<th>@L["Name"]</th>
<th>@L["Version"]</th>
<th>@L["LanguageCode"]</th>
<th>@L["FileName"]</th>
<th>@L["Format"]</th>
<th>@L["CreationTime"]</th>
<th>@L["LastUpdatedTime"]</th>
<th>@L["LastSignificantUpdateTime"]</th>
<th>@L["LastCachedTime"]</th>
</tr>
</thead>
</abp-table>
</abp-card-body>
</abp-card>

@ -0,0 +1,15 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Volo.Docs.Admin.Pages.Docs.Admin.Documents
{
[Authorize(DocsAdminPermissions.Projects.Default)]
public class IndexModel : DocsAdminPageModel
{
public virtual Task<IActionResult> OnGet()
{
return Task.FromResult<IActionResult>(Page());
}
}
}

@ -0,0 +1,153 @@
$(function () {
var l = abp.localization.getResource('Docs');
var _dataTable = $('#DocumentsTable').DataTable(abp.libs.datatables.normalizeConfiguration({
processing: true,
serverSide: true,
scrollX: true,
paging: true,
searching: false,
autoWidth: false,
scrollCollapse: true,
ajax: abp.libs.datatables.createAjax(volo.docs.admin.documentsAdmin.getAll),
columnDefs: [
{
rowAction: {
items:
[
{
text: l('RemoveFromCache'),
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('RemoveFromCacheConfirmation'); },
action: function (data) {
volo.docs.admin.documentsAdmin
.removeFromCache(data.record.id)
.then(function () {
abp.message.success(l('RemovedFromCache'));
_dataTable.ajax.reload();
});
}
},
{
text: l('ReIndex'),
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('ReIndexDocumentConfirmation'); },
action: function (data) {
volo.docs.admin.documentsAdmin
.reindex(data.record.id)
.then(function () {
abp.message.success(l('ReindexCompleted'));
_dataTable.ajax.reload();
});
}
},
{
text: l('DeleteFromDatabase'),
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('DeleteDocumentFromDbConfirmation'); },
action: function (data) {
volo.docs.admin.documentsAdmin
.deleteFromDatabase(data.record.id)
.then(function () {
abp.message.success(l('Deleted'));
_dataTable.ajax.reload();
});
}
}
]
}
},
{
target: 1,
data: "name"
},
{
target: 2,
data: "version"
},
{
target: 3,
data: "languageCode"
},
{
target: 4,
data: "fileName"
},
{
target: 5,
data: "format",
render: function (data) {
if (data === 'md') {
return 'markdown';
}
return data;
}
},
{
target: 6,
data: "creationTime",
render: function (creationTime) {
if (!creationTime) {
return "";
}
var date = Date.parse(creationTime);
return (new Date(date)).toLocaleDateString(abp.localization.currentCulture.name);
}
},
{
target: 7,
data: "lastUpdatedTime",
render: function (lastUpdatedTime) {
if (!lastUpdatedTime) {
return "";
}
var date = Date.parse(lastUpdatedTime);
return (new Date(date)).toLocaleDateString(abp.localization.currentCulture.name);
}
},
{
target: 8,
data: "lastSignificantUpdateTime",
render: function (lastSignificantUpdateTime) {
if (!lastSignificantUpdateTime) {
return "";
}
var date = Date.parse(lastSignificantUpdateTime);
return (new Date(date)).toLocaleDateString(abp.localization.currentCulture.name);
}
},
{
target: 9,
data: "lastCachedTime",
render: function (lastCachedTime) {
if (!lastCachedTime) {
return "";
}
var date = Date.parse(lastCachedTime);
return (new Date(date)).toLocaleDateString(abp.localization.currentCulture.name);
}
}
]
}));
$("#ReIndexAllProjects").click(function (event) {
abp.message.confirm(l('ReIndexAllProjectConfirmationMessage'))
.done(function (accepted) {
if (accepted) {
volo.docs.admin.projectsAdmin
.reindexAll()
.then(function () {
abp.message.success(l('SuccessfullyReIndexAllProject'));
});
}
});
});
});

@ -30,6 +30,7 @@
<Content Remove="Properties\launchSettings.json" />
<Content Remove="compilerconfig.json" />
<Content Remove="compilerconfig.json.defaults" />
<None Remove="Pages\Docs\Admin\Documents\index.js" />
<None Include="compilerconfig.json" />
<None Include="Properties\launchSettings.json" />
</ItemGroup>

@ -245,8 +245,13 @@ namespace Volo.Docs.Documents
return await GetDocumentAsync(documentName, project, languageCode, version, document);
}
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, document.Name,
document.LanguageCode, document.Version);
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(
project: project,
documentName: document.Name,
languageCode: document.LanguageCode,
version: document.Version
);
await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo
{
Name = document.Name,
@ -282,8 +287,13 @@ namespace Volo.Docs.Documents
Logger.LogInformation($"Document retrieved: {documentName}");
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(project, sourceDocument.Name,
sourceDocument.LanguageCode, sourceDocument.Version);
var cacheKey = CacheKeyGenerator.GenerateDocumentUpdateInfoCacheKey(
project: project,
documentName: sourceDocument.Name,
languageCode: sourceDocument.LanguageCode,
version: sourceDocument.Version
);
await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo
{
Name = sourceDocument.Name,

@ -8,14 +8,40 @@ namespace Volo.Docs.Documents
{
public interface IDocumentRepository : IBasicRepository<Document>
{
Task<List<Document>> GetListByProjectId(Guid projectId,
CancellationToken cancellationToken = default);
Task<List<Document>> GetListByProjectId(Guid projectId, CancellationToken cancellationToken = default);
Task<Document> FindAsync(Guid projectId, string name, string languageCode, string version,
Task<Document> FindAsync(Guid projectId,
string name,
string languageCode,
string version,
bool includeDetails = true,
CancellationToken cancellationToken = default);
Task DeleteAsync(Guid projectId, string name, string languageCode, string version,
Task DeleteAsync(Guid projectId,
string name,
string languageCode,
string version,
CancellationToken cancellationToken = default);
Task<List<Document>> GetAllAsync(Guid? projectId,
string name,
string version,
string languageCode,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default);
Task<long> GetAllCountAsync(
Guid? projectId,
string name,
string version,
string languageCode,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default);
Task<Document> GetAsync(Guid id, CancellationToken cancellationToken = default);
}
}

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@ -23,6 +24,34 @@ namespace Volo.Docs.Documents
return await DbSet.Where(d => d.ProjectId == projectId).ToListAsync(cancellationToken: cancellationToken);
}
public async Task<List<Document>> GetAllAsync(Guid? projectId,
string name,
string version,
string languageCode,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var query = ApplyFilterForGetAll(DbSet, projectId, name, version, languageCode);
query = query.OrderBy(string.IsNullOrWhiteSpace(sorting) ? nameof(Document.Name) : sorting);
return await query.PageBy(skipCount, maxResultCount).ToListAsync(cancellationToken);
}
public async Task<long> GetAllCountAsync(
Guid? projectId,
string name,
string version,
string languageCode,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var query = ApplyFilterForGetAll(DbSet, projectId, name, version, languageCode);
return await query.LongCountAsync(GetCancellationToken(cancellationToken));
}
public async Task<Document> FindAsync(Guid projectId, string name, string languageCode, string version,
bool includeDetails = true,
CancellationToken cancellationToken = default)
@ -40,5 +69,25 @@ namespace Volo.Docs.Documents
x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode &&
x.Version == version, cancellationToken: cancellationToken);
}
public async Task<Document> GetAsync(Guid id, CancellationToken cancellationToken = default)
{
return await DbSet.Where(x => x.Id == id).SingleAsync(cancellationToken: cancellationToken);
}
protected virtual IQueryable<Document> ApplyFilterForGetAll(
IQueryable<Document> query,
Guid? projectId,
string name,
string version,
string languageCode,
CancellationToken cancellationToken = default)
{
return DbSet
.WhereIf(projectId.HasValue, d => d.ProjectId == projectId.Value)
.WhereIf(name != null, d => d.Name != null && d.Name.Contains(name))
.WhereIf(version != null, d => d.Version != null && d.Version == version)
.WhereIf(languageCode != null, d => d.LanguageCode != null && d.LanguageCode == languageCode);
}
}
}

@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
using MongoDB.Driver.Linq;
using MongoDB.Driver;
using Volo.Docs.MongoDB;
namespace Volo.Docs.Documents
{
public class MongoDocumentRepository : MongoDbRepository<IDocsMongoDbContext, Document, Guid>, IDocumentRepository
@ -39,5 +42,73 @@ namespace Volo.Docs.Documents
x.ProjectId == projectId && x.Name == name && x.LanguageCode == languageCode &&
x.Version == version, cancellationToken: cancellationToken);
}
public async Task<List<Document>> GetAllAsync(Guid? projectId,
string name,
string version,
string languageCode,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
return await
ApplyFilterForGetAll(GetMongoQueryable(), projectId, name, version, languageCode)
.OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As<IMongoQueryable<Document>>()
.PageBy<Document, IMongoQueryable<Document>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<long> GetAllCountAsync(Guid? projectId,
string name,
string version,
string languageCode,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
return await
ApplyFilterForGetAll(GetMongoQueryable(), projectId, name, version, languageCode)
.OrderBy(string.IsNullOrWhiteSpace(sorting) ? "name asc" : sorting).As<IMongoQueryable<Document>>()
.PageBy<Document, IMongoQueryable<Document>>(skipCount, maxResultCount)
.LongCountAsync(GetCancellationToken(cancellationToken));
}
public async Task<Document> GetAsync(Guid id, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().Where(x => x.Id == id).SingleAsync(cancellationToken);
}
protected virtual IMongoQueryable<Document> ApplyFilterForGetAll(
IMongoQueryable<Document> query,
Guid? projectId,
string name,
string version,
string languageCode,
CancellationToken cancellationToken = default)
{
if (projectId.HasValue)
{
query = query.Where(d => d.ProjectId == projectId.Value);
}
if (name != null)
{
query = query.Where(d => d.Name != null && d.Name.Contains(name));
}
if (version != null)
{
query = query.Where(d => d.Version != null && d.Version == version);
}
if (languageCode != null)
{
query = query.Where(d => d.LanguageCode != null && d.LanguageCode == languageCode);
}
return query;
}
}
}
Loading…
Cancel
Save