如何搜索来自另一个页面模型的串联名称列表?

Posted

技术标签:

【中文标题】如何搜索来自另一个页面模型的串联名称列表?【英文标题】:How do I search a list of concatenated names coming from another page model? 【发布时间】:2020-01-30 13:53:03 【问题描述】:

我的项目中有多个模型,但在下面的屏幕中,大部分字段/属性都位于 SecurityLog 模型中。

下面是我显示官员级联列表的地方。除了军官姓名之外,我的搜索和列标题排序对所有内容都正常工作。我在合并军官姓名时遇到困难,因为列表来自另一个页面模型。

这是我的数据库架构和示例结果

我已经根据 Microsoft 的 Contoso 大学的演示实现了排序、搜索和分页。

https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/sort-filter-page?view=aspnetcore-3.1

如何解决以下当前代码中的军官姓名搜索问题? 更具体地说,对于搜索...我如何读取(迭代)OfficerID 列表并搜索每个列表项的字符串值(连接的军官列表行)?

foreach (SecurityLog secLog in SecurityLog)
        
            secLogCopy = secLog;

            OfficerLists = officerList.GetOfficerList(_context, secLog, rowID, OfficerIDs);
            if (!String.IsNullOrEmpty(searchString))
            
                sort = sort.Where(s => OfficerIDs.ToString().Contains(searchString));
            
            rowID++;
        

页面模型:

namespace SecurityCore.Pages.SecurityLogs

    public class IndexModel : PageModel
    
        private readonly SecurityCore.Models.SecurityCoreContext _context;

        public IndexModel(SecurityCore.Models.SecurityCoreContext context)
        
            _context = context;
        

        public string EventDateSort  get; set;         
        public string CurrentSort  get; set; 


        [DataType(DataType.Date)]
        public Nullable<DateTime> DateEnd  get; set; 
        [DataType(DataType.Date)]
        public Nullable<DateTime> DateBegin  get; set; 
        public Entity Entity  get; set; 


        public PaginatedList<SecurityLog> SecurityLog  get; set; 
        public List<secLog> SecurityLogOfficers  get; set;  = new List<secLog>();
        public List<string> OfficerLists  get; set; 

        [BindProperty]
        public OfficerList officerList  get; set;  = new OfficerList();
        [BindProperty]
        public List<string> OfficerIDs  get; set;  = new List<string>();







    public async Task OnGetAsync(string sortOrder, string currentFilter, string searchString, int? pageIndex,
                                 string entitySelect, string entityFilter, DateTime dateBegin, DateTime dateBeginSelect, DateTime dateEnd, DateTime dateEndSelect)
    
        selectedEntity = new SelectList(_context.Entity.Where(a => a.Active == "Y"), "Name", "Name");

        CurrentSort = sortOrder;
        EventDateSort = sortOrder == "EventDate" ? "EventDate_Desc" : "EventDate";            
        OfficerNameSort = sortOrder == "OfficerName" ? "OfficerName_Desc" : "OfficerName";


        IQueryable<SecurityLog> sort = from s in _context.SecurityLog select s;


        switch (sortOrder)
        
            case "EventDate":
                sort = sort.OrderBy(s => s.EventDate);
                break;                                
            case "OfficerName":                    
                sort = sort.OrderBy(s => officerList.ToString()).ThenBy(s => s.EventDate);
                break;
            case "OfficerName_Desc":                    
                sort = sort.OrderByDescending(s => officerList.ToString()).ThenBy(s => s.EventDate);
                break;
            default:
                sort = sort.OrderByDescending(s => s.EventDate);
                break;
        

        int pageSize = 5;





        SecurityLog = await PaginatedList<SecurityLog>.CreateAsync(sort
        .Include(a => a.Entity)
        .Include(b => b.EventType)
        .Include(c => c.Location)
        .Include(d => d.ShiftRange)
        .Include(e => e.Officer)                                    
        .AsNoTracking(), pageIndex ?? 1, pageSize);



        int rowID;
        rowID = 0;


        foreach (SecurityLog secLog in SecurityLog)
        
            secLogCopy = secLog;
            OfficerLists = officerList.GetOfficerList(_context, secLog, rowID, OfficerIDs);
            if (!String.IsNullOrEmpty(searchString))
            
                sort = sort.Where(s => OfficerIDs.ToString().Contains(searchString));
            
            rowID++;
        



        if (!String.IsNullOrEmpty(searchString))
                                                        

            sort = sort.Where(s => s.Narrative.Contains(searchString)                                    
                                || s.ContactName.Contains(searchString)
                                || s.SubjectFirst.Contains(searchString)
                                || s.SubjectLast.Contains(searchString));                                    
        

    


OfficerList.cs

public class OfficerList

    public List<string> GetOfficerList(SecurityCoreContext _context, SecurityLog secLog, int rowID, List<string> OfficerIDs)
                

        int CurrentID = secLog.ID;

        var SecLogOfficer = _context.SecurityLogOfficer.ToList();
        var Officer = _context.Officer.ToList();


        int count = SecLogOfficer.Where(slo => slo.SecurityLogID == CurrentID).Count();

        if (count >= 0)
        
            OfficerIDs.Add("");
        
        foreach (secLog slo in SecLogOfficer.Where(slo => slo.SecurityLogID == CurrentID))
        
            OfficerIDs[rowID] = OfficerIDs[rowID] + slo.Officer.FullName + ", ";
        
        if (count > 0)
        
            OfficerIDs[rowID] = OfficerIDs[rowID].Substring(0, OfficerIDs[rowID].Length - 2);
        


        return OfficerIDs;

    


页面:

@page
@model WebApplication_core_razorpage.Pages.HomeModel
@
    ViewData["Title"] = "Home";
    Layout = "~/Pages/Shared/_Layout.cshtml";
    var i = 0;


<h1>Home</h1>

<table>
    @foreach (var item in Model.SecurityLog)
    
        <tr>
            <td style="width:4% !important">
                @Html.DisplayFor(modelItem => item.ID)
            </td>
            <td style="width:5% !important">
                @Html.DisplayFor(modelItem => item.EventDate)
            </td>

            <td style="width:5% !important">
                @Model.OfficerLists[i]
            </td>
        </tr>
        i++;
    

</table>

分页列表.cs

public class PaginatedList<T> : List<T>

    public int PageIndex  get; private set; 
    public int TotalPages  get; private set;         

    public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
    
        PageIndex = pageIndex;
        TotalPages = (int)Math.Ceiling(count / (double)pageSize);

        this.AddRange(items);
    

    public bool HasPreviousPage
    
        get
        
            return (PageIndex > 1);
        
    


    public bool HasNextPage => PageIndex < TotalPages;

    public bool ShowFirst
    
        get
        
            return (PageIndex != 1);
        
    

    public bool ShowLast
    
        get
        
            return (PageIndex != TotalPages);
        
    

    public static async Task<PaginatedList<T>> CreateAsync(
        IQueryable<T> source, int pageIndex, int pageSize)
    
        var count = await source.CountAsync();
        var items = await source.Skip(
            (pageIndex - 1) * pageSize)
            .Take(pageSize).ToListAsync();
        return new PaginatedList<T>(items, count, pageIndex, pageSize);
    

SecurityLog.cs

namespace SecurityCore.Models

public class SecurityLog
                

    [BindProperty(SupportsGet = true)]
    public int ID  get; set; 

    [DataType(DataType.Date)]
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "0:MM/dd/yyyy")]
    [Display(Name = "Event Date")]
    public System.DateTime EventDate  get; set; 

    public virtual Officer Officer  get; set;         
    public virtual List<secLog> SecurityLogOfficers  get; set;        


  

关系

public class SecurityCoreContext : DbContext

    public SecurityCoreContext (DbContextOptions<SecurityCoreContext> options)
        : base(options)
    
    

    public DbSet<SecurityCore.Models.SecurityLog> SecurityLog  get; set;         

    public DbSet<SecurityCore.Models.secLog> SecurityLogOfficer  get; set; 

    public DbSet<SecurityCore.Models.Officer> Officer  get; set; 

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    
        modelBuilder.Entity<secLog>()
            .HasKey(t => new  t.SecurityLogID, t.OfficerID );

        modelBuilder.Entity<secLog>()
            .HasOne(pt => pt.SecurityLog)
            .WithMany(p => p.SecurityLogOfficers)
            .HasForeignKey(pt => pt.SecurityLogID);

        modelBuilder.Entity<secLog>()
            .HasOne(pt => pt.Officer)
            .WithMany(t => t.SecurityLogOfficers)
            .HasForeignKey(pt => pt.OfficerID);
    


【问题讨论】:

_context.SecurityLog.Include(x=&gt;x.SecurityLogOfficer).ThenInclude(x=&gt;x.Officer).Where(....).Select(x=&gt; new PageViewModel OfficersNames=string.Join(",",x.SecurityLogOfficers.Select(o=&gt;o.Officer.Name)) ).ToPagedList(page),修改你的模型和视图模型,直到你可以使用这个查询。 这篇文章将帮助您映射多对多关系:thereformedprogrammer.net/… 这是一个很好的分页:docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/… 感谢您的建议和链接...这似乎是 MVC 的解决方案,而不是我正在使用的 asp.net 核心剃须刀页面。这对剃须刀有用吗?我还将我的多对多关系添加到我原来的问题中。 请将代码缩减为所需部分。即使是2-3个属性也应该没问题,不需要View的代码,不确定DBContext是否有很大帮助。问题是什么也不是很清楚。 【参考方案1】:

基于多对多关系的搜索

谈到文章和作者,当每篇文章可能有很多作者时,假设您要根据term 进行搜索,然后找到文章名称或文章摘要包含该术语或其中一个的文章文章的作者在他们的名字或姓氏中有这个词。

EF 6 - Many-To-May without entity class for Relationship

您可以使用 Any 在 Linq 查询中处理这些情况,就像使用 EXISTS 在 SQL 查询中处理这些情况一样:

Where(article=> article.Title.Contains(term) || 
                article.Abstract.Contains(term) || 
                article.Authors.Any(author => 
                    author.FirstName.Contains(term) ||
                    author.LastName.Contains(searchTerm)))

它并不完全生成以下 SQL Query,但其逻辑与 SQL 中的以下内容非常相似:

FROM Articles
WHERE (Articles.Title LIKE '%' + @Term + '%') OR 
      (Articles.Abstract LIKE '%' + @Term + '%') OR 
      EXISTS (SELECT * FROM Authors 
              WHERE (Authors.FirstName LIKE '%' + @Term + '%') OR
                    (Authors.LastName LIKE '%' + @Term + '%'))

EF CORE - Many-To-May with entity class for Relationship

目前,尚不支持没有实体类来表示连接表的多对多关系。

您可以在 Linq 查询中使用 Any 处理这些情况,就像在 SQL 查询中使用 EXISTS + Join 处理这些情况一样:

.Where(article => article.Title.Contains(model.SearchTerm) ||
                  article.Abstract.Contains(model.SearchTerm) ||
                  article.ArticlesAuthors.Any(au =>
                      (au.Author.FirstName).Contains(model.SearchTerm) ||
                      (au.Author.LastName).Contains(model.SearchTerm)))

它并不完全生成以下 SQL Query,但其逻辑与 SQL 中的以下内容非常相似:

FROM Articles
WHERE (Articles.Title LIKE '%' + @Term + '%') OR 
      (Articles.Abstract LIKE '%' + @Term + '%') OR 
      EXISTS (SELECT * FROM ArticlesAuthors 
              INNER JOIN Authors 
              ON ArticlesAuthors.AuthorId = Authors.Id
              WHERE ((Authors.FirstName LIKE '%' + @Term + '%') OR
                     (Authors.LastName LIKE '%'+ @Term + '%')) AND 
                     (Articles.Id = ArticlesAuthors.ArticleId))

EF 6 - 示例

这个问题有点混乱,包括搜索排序和大量代码,需要更多关注。为了使其对您和功能读者更有用且更易于理解,我将使用属性更少且更易于理解的更简单模型。

正如您在 EF 图中看到的那样,ArticlesAuthors 表未在图中显示,因为它是一个多对多关系,仅包含其他实体的 Id 列,没有任何额外字段

搜索逻辑

我们要查找基于SerachTermPublishDateFromPublishDateTo 的文章:

如果文章的标题或摘要包含术语,则文章应该是结果的一部分。 如果文章作者的名字和姓氏的组合包含术语,文章应该是结果的一部分。 如果发布日期大于或等于PublishDateFrom,文章应该是结果的一部分,同样如果发布日期小于或等于PublishDateTo,文章应该是结果的一部分。李>

这是一个搜索模型:

public class ArticlesSearchModel

    public string SearchTerm  get; set; 
    public DateTime? PublishDateFrom  get; set; 
    public DateTime? PublishDateTo  get; set; 

搜索代码如下:

请注意:Inculde 与搜索和搜索无关 它只是为了在输出结果中包含相关实体。

public class ArticlesBusinessLogic

    public IEnumerable<Article> Search(ArticlesSearchModel model)
    
        using (var db = new ArticlesDBEntities())
        
            var result = db.Articles.Include(x => x.Authors).AsQueryable();

            if (model == null)
                return result.ToList();

            if (!string.IsNullOrEmpty(model.SearchTerm))
                result = result.Where(article => (
                    article.Title.Contains(model.SearchTerm) ||
                    article.Abstract.Contains(model.SearchTerm) ||
                    article.Authors.Any(author =>
                    (author.FirstName + " " + author.LastName).Contains(model.SearchTerm))
                ));

            if (model.PublishDateFrom.HasValue)
                result = result.Where(x => x.PublishDate >= model.PublishDateFrom);

            if (model.PublishDateFrom.HasValue)
                result = result.Where(x => x.PublishDate <= model.PublishDateTo);

            return result.ToList();
        
    

EF CORE - 示例

如上所述,目前尚不支持没有实体类来表示连接表的多对多关系,因此使用 EF CORE 的模型将是:

搜索代码如下:

请注意:Inculde 与搜索无关 它只是为了在输出结果中包含相关实体。

public IEnumerable<Article> Search(ArticlesSearchModel model)

    using (var db = new ArticlesDbContext())
    
        var result = db.Articles.Include(x=>x.ArticleAuthor)
                                .ThenInclude(x=>x.Author)
                                .AsQueryable();

        if (model == null)
            return result;

        if (!string.IsNullOrEmpty(model.SearchTerm))
        
            result = result.Where(article => (
                article.Title.Contains(model.SearchTerm) ||
                article.Abstract.Contains(model.SearchTerm) ||
                article.ArticleAuthor.Any(au =>
                    (au.Author.FirstName + " " + au.Author.LastName)
                        .Contains(model.SearchTerm))
            ));
        
        if (model.PublishDateFrom.HasValue)
        
            result = result.Where(x => x.PublishDate >= model.PublishDateFrom);
        
        if (model.PublishDateFrom.HasValue)
        
            result = result.Where(x => x.PublishDate <= model.PublishDateTo);
        

        return result.ToList();
    

【讨论】:

不要与ArticlesSearchModelArticlesBusinessLogic 混淆。他们只是为了拥有更好的代码结构,但答案的重点在于第一部分。此处的代码示例旨在帮助您更好地了解Any 及其工作原理。它还向您展示了创建多对多关系的更好方法。 谢谢。我会审核你的答案。 不客气。希望它有所帮助:) 我使用这种模式的原因是因为它通过分离关注点非常清晰易懂。你可以在this answer 中看到我在这里使用过。 我能够发布一个可行的解决方案,尽管我的搜索过滤器在尝试搜索安全日志模型中的官员列表时不起作用。有任何想法吗?错误 --> “序列包含多个元素” 您需要在模型中修复的第一件事(这将有很大帮助)是将SecurityLogOfficer 作为实体删除,并在SecurityLog 和@987654352 之间建立真正的多对多关系@。那么每个SecurityLog 都会有一个Officers 的集合不是更干净吗?【参考方案2】:

你做错了很多事情:

    您不能在类或列表上使用.ToString()。所以首先你必须删除或更改这些行。例如:
  sort = sort.Where(s => OfficerIDs.ToString().Contains(searchString));

  sort = sort.OrderBy(s => officerList.ToString()).ThenBy(s => s.EventDate);

  sort = sort.OrderByDescending(s => officerList.ToString()).ThenBy(s => s.EventDate);

    每次加载页面或更改搜索或排序时,您几乎都会从数据库表中加载全部数据。当然,分页让这个问题在这里有点模糊

    您没有使用实体框架来加载关系数据,因此您无法编写一个查询来加载您需要的内容或用户搜索的内容。 (您正在从不同部分的数据库中获取数据)


我知道这不是你要找的,但老实说,我试图回答你的问题并帮助你解决问题,但我最终又重写了整个事情...... 你应该把你的问题分解成更小的部分,并提出一个更具概念性的问题。

【讨论】:

感谢您的建议。如何将我的关系数据与实体框架关联? 您应该使用.Include() 来加载您的相关数据。 docs.microsoft.com/en-us/ef/core/querying/related-data 知道了。谢谢。

以上是关于如何搜索来自另一个页面模型的串联名称列表?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用循环将带有来自 googlesearch 的链接的列表附加到另一个列表?

Xamarin Forms:如何通知我以前的视图模型而不是需要修改可观察集合?

姜戈。如何连接下拉列表(来自html)和表单?

列出来自2个不同表/模型的数据

如何在查询器节点js中调用另一个页面函数名称

如何在 SwiftUI 中从一个详情页跳转到另一个详情页并返回列表页?