简体   繁体   中英

Linq query with multiple tables

Trying to type a Linq expression that will include information from two tables.

        var articles = db.Articles;
        var articlescores = db.ArticleScores;

articles table has the title field

articles.title

the articlescores table has the ActualCity field and ArticleID field

articlescores.ActualCity
artickescores.ArticleID (which links to articles table)

I need to .Count() the articlescores.ActualCity field, then store the highest counted city in a string along with articles.title

Basically articles viewed most by a city.

Sample Data:

ArticleScore table:

    ID articleID City  (Note: articleID is not dependent on City, I just wrote it this way)
    1    1       New York   
    2    2       Boston
    3    1       New York
    4    1       New York
    5    1       New York
    6    2       Boston

article table:

ID title
1  TitleOneOfArticles
2  TitleTwoOfArticles

Expected output (because New York has a count of 4, Boston only has 2):

TitleOneOfArticles, NewYork(4)
      ^^              ^^     ^
    title            City   Count

Only one line of output is expected.

Your question is self-contradictory and it's also missing some key information. So I'm going to take a guess.

  • You did not mention linq to what you are using. Are you using Entity Framework? Lightspeed? Something else? I'm assuming it's Entity Framework
  • You say that you need Basically articles viewed most by a city , but then you go ahead and give somewhat different example. I'm going to assume that it is the most viewed article by cities is what you are after
  • You say that articleID is not dependent on City but then proceed given an example where is one to one relationship between a City and articleID.

Here is how I understand a sample ArticleScore dataset:

    ID articleID  City  
     ?    1       New York   
     ?    1       New York
     ?    1       New York
     ?    1       Boston
     ?    2       New York
     ?    2       Boston
     ?    2       Boston

In this dataset there are 3 views for article 1 in NewYork and 1 view of article 2. Likewise, in Boston, there are 2 views of article 2 and 1 view of article 1.

Based on this data, I'm assuming you want to get something like this

TitleTwoOfArticles, Boston(2)
TitleOneOfArticles, NewYork(3)

This table above signifies that In Boston the most viewed article is article 2 with the total of 2 views. In NewYork, the most viewed article is 1 with the total of 3 view. Note that the total number of views for an article across all cities is not shown here.

A created a code sample to test the scenario above, for it I used:

  • Visual Studio 2013 / .net 4.5
  • Entity Framework 6

Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;

namespace SO22928136
{
    public interface IMyDbContext : IDisposable
    {
        IDbSet<Article> Articles { get; set; }
        IDbSet<ArticleScore> ArticleScores { get; set; }
        int SaveChanges();
    }

    public class MyDbContext : DbContext, IMyDbContext
    {
        public IDbSet<Article> Articles { get; set; }
        public IDbSet<ArticleScore> ArticleScores { get; set; }

        static MyDbContext()
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
        }

        public MyDbContext(string connectionString) : base(connectionString)
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Configurations.Add(new ArticleConfiguration());
            modelBuilder.Configurations.Add(new ArticleScoreConfiguration());
        }
    }

    public class Article
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public virtual ICollection<ArticleScore> ArticleScores { get; set; }
        public Article()
        {
            ArticleScores = new List<ArticleScore>();
        }
    }

    public class ArticleScore
    {
        public int Id { get; set; }
        public int ArticleId { get; set; }
        public string ActualCity { get; set; }
        public virtual Article Article { get; set; }
    }

    internal class ArticleConfiguration : EntityTypeConfiguration<Article>
    {
        public ArticleConfiguration()
        {
            ToTable("Article");
            HasKey(x => x.Id);
            Property(x => x.Id).HasColumnName("Id").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            Property(x => x.Title).HasColumnName("Title").IsOptional();
        }
    }

    internal class ArticleScoreConfiguration : EntityTypeConfiguration<ArticleScore>
    {
        public ArticleScoreConfiguration()
        {
            ToTable("ArticleScore");
            HasKey(x => x.Id);
            Property(x => x.Id).HasColumnName("Id").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            Property(x => x.ArticleId).HasColumnName("ArticleId").IsRequired();
            Property(x => x.ActualCity).HasColumnName("ActualCity").IsOptional().HasMaxLength(10);
            HasRequired(a => a.Article).WithMany(b => b.ArticleScores).HasForeignKey(c => c.ArticleId);
        }
    }


    class Program
    {
        static void Main()
        {
            MyDbContext context = new MyDbContext("Data Source=(local);Initial Catalog=SO22928136;Integrated Security=True;");
            CreateTestData(context);

            var countOfArticlesPerCity = context.ArticleScores.GroupBy(s => new {s.ArticleId, s.ActualCity}).Select(g => new {g.Key.ArticleId, g.Key.ActualCity, Count = g.Count()});
            var highestArticleCountPerCity = countOfArticlesPerCity.GroupBy(x => x.ActualCity).Select(g => g.OrderByDescending(x => x.Count).FirstOrDefault());
            var highestArticleCountPerCityWithArticleTitle = context.Articles.Join(highestArticleCountPerCity, x => x.Id, p => p.ArticleId, (x, p) => new { x.Title, p.ActualCity, p.Count });

            foreach (var line in highestArticleCountPerCityWithArticleTitle)
            {
                Console.WriteLine("{0}, {1}({2})",line.Title,line.ActualCity, line.Count);
            }

        }

        private static void CreateTestData(MyDbContext context)
        {
            Article articleOne = new Article { Title = "TitleOneOfArticles" };
            Article articleTwo = new Article { Title = "TitleTwoOfArticles" };

            articleOne.ArticleScores.Add(new ArticleScore { ActualCity = "NewYork" });
            articleOne.ArticleScores.Add(new ArticleScore { ActualCity = "NewYork" });
            articleOne.ArticleScores.Add(new ArticleScore { ActualCity = "NewYork" });
            articleOne.ArticleScores.Add(new ArticleScore { ActualCity = "Boston" });

            articleTwo.ArticleScores.Add(new ArticleScore { ActualCity = "NewYork" });
            articleTwo.ArticleScores.Add(new ArticleScore { ActualCity = "Boston" });
            articleTwo.ArticleScores.Add(new ArticleScore { ActualCity = "Boston" });

            context.Articles.Add(articleOne);
            context.Articles.Add(articleTwo);
            context.SaveChanges();
        }
    }
}

The linq queries that is of most interest to you are in the main method. Modify the connection string to point to your test server. Make sure that the database name you are using DOES NOT EXIST because database with this name WILL BE DROPPED. Be careful.

When the program runs it drops/creates the specified database and creates the sample schema in it. The it proceeds inserting its sample data, as described above. Then the actual query you are interested in follow.

I split the query to three parts. Firstly, we group by city name and article id and calculate counts for each line. Secondly, we select only those lines that have maximum count number for each city. Finally, we join with the Article table to get article title from the id.

After that we print out the results. It should be easy to modify the query to return just one line, you just need to add a condition to it.

try this linq:

db.Articles
               .Join(db.ArticleScores ,
                   art=> art.ID,
                   scr=> scr.ID,
                   (art, scr) => new { article= art, score= scr})
                   .Where(both => (both.art.ID == cityID))
                   .Select(both => both.score)

the cityId is parameter that you should send

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM