Group by in .net core 3.1 - entity-framework-core

I am trying to group by 2 fields and than count rows of each status.
I am using .NET core 3.1 and newest version of EF.
I am getting an error: The LINQ expression could not be translated. Either rewrite the query in a form that can be translated...
What I investigated so far is, when I get rid off predicate
y.Count(x=>x.Status == "New")
and just leave y.Count() it works fine.
Orders collection is just a mocked list of objects, in my real app it is a table in sql server.
//Rextester.Program.Main is the entry point for your code. Don't change it.
//Microsoft (R) Visual C# Compiler version 2.9.0.63208 (958f2354)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
var orders = new List<Order>();
orders.Add(new Order(){Year = 2020, Status = "New"});
orders.Add(new Order(){Year = 2020, Status = "New"});
orders.Add(new Order(){Year = 2020, Status = "Canceled"});
orders.Add(new Order(){Year = 2020, Status = "Shipped"});
var result = await orders
.GroupBy(x=> new {x.Year, x.Status})
.Select(y => new
{
Year = y.Key.Year,
NewCount = y.Count(x=>x.Status == "New"),
CanceledCount = y.Count(x=>x.Status == "Canceled"),
ShippedCount = y.Count(x=>x.Status == "Shipped")
}).ToListAsync();
}
}
public class Order
{
public int Year {get;set;}
public string Status {get;set;}
}
}
Expected result is:
Year: 2020, NewCount: 2
Year: 2020, CanceledCount : 1
Year: 2020, ShippedCount : 1
What am I doing wrong? How to correct this error to get desired output?
EDIT: This is my playground https://dotnetfiddle.net/v7IYmq

Can you try the following. You are already grouping by Year and Status. Status is part of your key so you can use that to your advantage. Then just count the records using y.Count()
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
var orders = new List<Order>();
orders.Add(new Order(){Year = 2020, Status = "New"});
orders.Add(new Order(){Year = 2020, Status = "New"});
orders.Add(new Order(){Year = 2020, Status = "Canceled"});
orders.Add(new Order(){Year = 2020, Status = "Shipped"});
var result = orders
.GroupBy(x=> new {x.Year, x.Status})
.Select(y => new
{
Year = y.Key.Year,
Status = y.Key.Status,
Count = y.Count()
}).ToList();
foreach (var item in result)
{
Console.WriteLine(string.Format("{0} {1} {2}",item.Year,item.Status, item.Count));
}
}
}
public class Order
{
public int Year {get;set;}
public string Status {get;set;}
}

Related

OData GetById is not working on Asp.net Core 2.2

I am trying to get a single item from my list of elements and it is not working. I am using FromODataUri correctly, I am not seeing what is wrong.
I am able to hit this resource:
https://localhost:44314/odata/Inventories
but I can't get a single item: https://localhost:44314/odata/Inventories(1)
This is how my Startup.cs looks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OData.Edm;
using OdataTest.Models;
using Swashbuckle.AspNetCore.Swagger;
namespace OdataTest
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddODataQueryFilter();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Inventory Api", Version = "v1" });
});
services.AddMvc(options => options.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
// force to add another /swagger to fix issue
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Physical Inventory API");
});
app.UseHttpsRedirection();
app.UseMvc(b =>
b.MapODataServiceRoute("odata", "odata", GetEdmModel(app.ApplicationServices))
);
}
private static IEdmModel GetEdmModel(IServiceProvider serviceProvider)
{
ODataModelBuilder builder = new ODataConventionModelBuilder(serviceProvider);
builder.Namespace = "PartsInventory";
builder.ContainerName = "PartsInventoryContainer";
builder.EntitySet<InventoryOutputDto>("Inventories").EntityType
.Filter()
.Count()
.Expand()
.OrderBy()
.Page()
.Select();
return builder.GetEdmModel();
}
}
}
And my Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using OdataTest.Models;
namespace OdataTest.Controllers
{
public class PartsInventoryController : ODataController
{
List<InventoryOutputDto> list = new List<InventoryOutputDto>() {
new InventoryOutputDto {
Description = "Description1",
UserName = "user1",
EndDate = DateTime.Now,
ErrorId = "Error",
InventoryId = 1,
LocationCode = 1,
StartDate = DateTime.Now,
StatusCode = 1
},
new InventoryOutputDto {
Description = "Description1",
UserName = "user1",
EndDate = DateTime.Now,
ErrorId = "Error2",
InventoryId = 2,
LocationCode = 2,
StartDate = DateTime.Now,
StatusCode = 2
} };
[HttpGet]
[ODataRoute("Inventories")]
/// public IActionResult Inventory(InventoryInputDto inputDto)
public IActionResult GetInventories(InventoryInputDto inputDto)
{
return StatusCode(StatusCodes.Status200OK, list);
}
[HttpGet]
[ODataRoute("Inventories({key})")]
public IActionResult GetInventoryById([FromODataUri] int key)
{
var invRecord = list.FirstOrDefault(i => i.InventoryId == key);
if(invRecord == null)
{
return NotFound();
}
return Ok(invRecord);
}
}
}

How to get months difference between two dates using datediff in c#

I have two date fields where i need to caluculate difference in months between those two dates how can i do this.Below is my formula
(start.Year * 12 + start.Month) - (end.Year * 12 + end.Month);
Expected Result
Start Date End Date Need to get output as
08/28/2019 09/02/2019 1
06/01/2019 09/02/2019 4
01/02/2019 03/02/2019 3
01/02/2019 03/05/2019 3
Although you haven't told us what the rule is for calculating the result you're after, it looks like you need to check the day-of-month and add one if the end one is the same or later:
using System;
using System.Globalization;
using System.Linq;
namespace ConsoleApp1
{
class Program
{
class DatePair
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
public DatePair(string s)
{
var ci = new CultureInfo("en-US");
var parts = s.Split(",".ToCharArray());
this.Start = DateTime.Parse(parts[0], ci);
this.End = DateTime.Parse(parts[1], ci);
}
}
static void Main(string[] args)
{
string dats = "08/28/2019,09/02/2019;06/01/2019,09/02/2019;01/02/2019,03/02/2019;01/02/2019,03/05/2019";
var dates = dats.Split(";".ToCharArray()).Select(p => new DatePair(p));
foreach (DatePair d in dates)
{
var x = d.End.Month - d.Start.Month;
if (d.End.Day >= d.Start.Day) { x += 1; }
Console.WriteLine(d.Start.ToString("yyyy-MM-dd") + " " + d.End.ToString("yyyy-MM-dd") + " " + x.ToString());
}
Console.ReadLine();
}
}
}
Outputs:
2019-08-28 2019-09-02 1
2019-06-01 2019-09-02 4
2019-01-02 2019-03-02 3
2019-01-02 2019-03-05 3
I did not include the years in the calculation as there was no example date for that.

Is it possible to rollback a changeset using Microsoft.TeamFoundation.SourceControl.WebApi?

Following code try to rollback a changeset using Microsoft.TeamFoundation.SourceControl.WebApi. But "The specified change type Rollback is not supported." is raised when calling CreateChangesetAsync. Is this a limitation of Microsoft.TeamFoundation.SourceControl.WebApi? Or I did something wrong?
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi;
namespace RestRollBackInTFS
{
class Program
{
static void Main(string[] args)
{
const String collectionUri = #"https://myTfs.visualstudio.com/";
VssConnection connection = new VssConnection(new Uri(collectionUri),
new VssClientCredentials()
);
TfvcHttpClient tfsClient = connection.GetClient<TfvcHttpClient>();
int changeSetIdToRollBack = 11651;
var changeSet = tfsClient.GetChangesetAsync(changeSetIdToRollBack, maxChangeCount: 10, includeDetails: true).Result;
foreach (var c in changeSet.Changes)
{
c.ChangeType = VersionControlChangeType.Rollback;
}
TfvcChangeset newC = new TfvcChangeset();
newC.Changes = new List<TfvcChange>(changeSet.Changes);
var ret = tfsClient.CreateChangesetAsync(newC).Result;
}
}
}

Npgsql Performance

I am trying to implement Npgsql in our DAL and running into issues under heavy load. the following sample application is a decent representation of just a simple query that under heavy load, throws a 'A command is already in progress' exception. I am assuming this is due to the lack of MARS support so I also tried creating a connection each time with a using statement around each command only to have the performance become unusable. I checked that the username is indexed so that shouldn't be an issue.
Not sure what I am doing wrong here but I need some advice on how to get this performing well.
OS: Docker Container: microsoft/dotnet:2.1.301-sdk
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
namespace npgsqlTest
{
class Program
{
static async Task Main(string[] args)
{
DAL dal = new DAL();
dal.Prepare();
var tasks = dal.Users.Select(async user =>
{
Console.WriteLine(await dal.RunTest(user));
});
await Task.WhenAll(tasks);
}
}
public class DAL
{
private static string _ConnectionString;
private NpgsqlConnection _Connection;
public List<string> Users { get; set; } = new List<string>();
public DAL()
{
_ConnectionString = $"Host=192.168.1.1;Username=admin;Port=5432;Password=password;Database=BigDB;";
_Connection = new NpgsqlConnection(_ConnectionString);
_Connection.Open();
}
public void Prepare()
{
string query = "SELECT username FROM usertable;";
using (var cmd = new NpgsqlCommand(query, _Connection))
{
var reader = cmd.ExecuteReader();
using (reader)
{
while (reader.Read())
{
Users.Add(reader[0].ToString());
}
}
}
}
public async Task<string> RunTest(string user)
{
var parameters = new Dictionary<string, Object> { { "username", user } };
var query = $"SELECT name FROM usertable WHERE username = (#username);";
var reader = await QueryAsync(query, parameters);
using (reader)
{
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var name = reader["name"];
if (!(hash is DBNull))
return (string)name;
}
}
}
return String.Empty;
}
public async Task<DbDataReader> QueryAsync(string query, Dictionary<string, Object> parameters)
{
using (var cmd = new NpgsqlCommand(query, _Connection))
{
foreach (var parameter in parameters)
{
cmd.Parameters.AddWithValue(parameter.Key, parameter.Value == null ? DBNull.Value : parameter.Value);
}
cmd.Prepare();
return await cmd.ExecuteReaderAsync();
}
}
}
}

How to find top 10 files changed most frequently in a period from TFS 2015 Version Control?

My team uses TFS 2015 as an ALM and Version Control system,I want to analyse which files change most frequently.
I found TFS didn't have this functionality out of the box, but TFS2015 has a REST API to query Changesets for files, like this below:
http://{instance}/tfs/DefaultCollection/_apis/tfvc/changesets?searchCriteria.itemPath={filePath}&api-version=1.0
There are thousands of files in my Project Repository, querying it one by one is not a good idea, are there any better solution to this question?
I don't think there's a defacto out of the box solution for your question, I've tried two separate approaches to solve your question, I initially focused on the REST API but later switched to the SOAP API to see what features are supported in it.
In all options below the following api should suffice:
Install the client API link
#NuGet
Install-Package Microsoft.TeamFoundationServer.ExtendedClient -Version 14.89.0 or later
In all options the following extension method is required ref
public static class StringExtensions
{
public static bool ContainsAny(this string source, List<string> lookFor)
{
if (!string.IsNullOrEmpty(source) && lookFor.Count > 0)
{
return lookFor.Any(source.Contains);
}
return false;
}
}
OPTION 1: SOAP API
With the SOAP API one is not explicitly required to limit the number of query results using the maxCount parameter as described in this excerpt of QueryHistory method's IntelliSense documentation:
maxCount: This parameter allows the caller to limit the number of
results returned. QueryHistory pages results back from the server on
demand, so limiting your own consumption of the returned IEnumerable
is almost as effective (from a performance perspective) as providing a
fixed value here. The most common value to provide for this parameter
is Int32.MaxValue.
Based on the maxCount documentation I made a decision to extract statistics for each of the products in my source control system since it may be of great value to see how much code flux there is for each system in the codebase independent of each other instead of limiting to 10 files across the entire codebase which could contain hundreds of systems.
C# REST and SOAP (ExtendedClient) api reference
Install the SOAP API Client link
#NuGet
Install-Package Microsoft.TeamFoundationServer.ExtendedClient -Version 14.95.2
limiting criteria are: Only scan specific paths in source
control since some systems in source control are older and possibly only there for historic purposes.
only certain file extensions included e.g. .cs, .js
certain filenames excluded e.g. AssemblyInfo.cs.
items extracted for each path: 10
from date: 120 days ago
to date: today
exclude specific paths e.g. folders containing release branches or
archived branches
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
public void GetTopChangedFilesSoapApi()
{
var tfsUrl = "https://<SERVERNAME>/tfs/<COLLECTION>";
var domain = "<DOMAIN>";
var password = "<PASSWORD>";
var userName = "<USERNAME>";
//Only interested in specific systems so will scan only these
var directoriesToScan = new List<string> {
"$/projectdir/subdir/subdir/subdirA/systemnameA",
"$/projectdir/subdir/subdir/subdirB/systemnameB",
"$/projectdir/subdir/subdir/subdirC/systemnameC",
"$/projectdir/subdir/subdir/subdirD/systemnameD"
};
var maxResultsPerPath = 10;
var fromDate = DateTime.Now.AddDays(-120);
var toDate = DateTime.Now;
var fileExtensionToInclude = new List<string> { ".cs", ".js" };
var extensionExclusions = new List<string> { ".csproj", ".json", ".css" };
var fileExclusions = new List<string> { "AssemblyInfo.cs", "jquery-1.12.3.min.js", "config.js" };
var pathExclusions = new List<string> {
"/subdirToForceExclude1/",
"/subdirToForceExclude2/",
"/subdirToForceExclude3/",
};
using (var collection = new TfsTeamProjectCollection(new Uri(tfsUrl),
new NetworkCredential(userName: userName, password: password, domain: domain)))
{
collection.EnsureAuthenticated();
var tfvc = collection.GetService(typeof(VersionControlServer)) as VersionControlServer;
foreach (var rootDirectory in directoriesToScan)
{
//Get changesets
//Note: maxcount set to maxvalue since impact to server is minimized by linq query below
var changeSets = tfvc.QueryHistory(path: rootDirectory, version: VersionSpec.Latest,
deletionId: 0, recursion: RecursionType.Full, user: null,
versionFrom: new DateVersionSpec(fromDate), versionTo: new DateVersionSpec(toDate),
maxCount: int.MaxValue, includeChanges: true,
includeDownloadInfo: false, slotMode: true)
as IEnumerable<Changeset>;
//Filter changes contained in changesets
var changes = changeSets.SelectMany(a => a.Changes)
.Where(a => a.ChangeType != ChangeType.Lock || a.ChangeType != ChangeType.Delete || a.ChangeType != ChangeType.Property)
.Where(e => !e.Item.ServerItem.ContainsAny(pathExclusions))
.Where(e => !e.Item.ServerItem.Substring(e.Item.ServerItem.LastIndexOf('/') + 1).ContainsAny(fileExclusions))
.Where(e => !e.Item.ServerItem.Substring(e.Item.ServerItem.LastIndexOf('.')).ContainsAny(extensionExclusions))
.Where(e => e.Item.ServerItem.Substring(e.Item.ServerItem.LastIndexOf('.')).ContainsAny(fileExtensionToInclude))
.GroupBy(g => g.Item.ServerItem)
.Select(d => new { File=d.Key, Count=d.Count()})
.OrderByDescending(o => o.Count)
.Take(maxResultsPerPath);
//Write top items for each path to the console
Console.WriteLine(rootDirectory); Console.WriteLine("->");
foreach (var change in changes)
{
Console.WriteLine("ChangeCount: {0} : File: {1}", change.Count, change.File);
}
Console.WriteLine(Environment.NewLine);
}
}
}
OPTION 2A: REST API
(!! problem identified by OP led to finding a critical defect in v.xxx-14.95.4 of api) - OPTION 2B is the workaround
defect discovered in v.xxx to 14.95.4 of api: The TfvcChangesetSearchCriteria type contains an ItemPath property
which is supposed to limit the search to a specified directory. The
default value of this property is $/, unfortunately when used
GetChangesetsAsync will always use the root path of the tfvc source repository irrespective of the value set.
That said, this will still be a reasonable approach if the defect were to be fixed.
One way to limit the impact to your scm system would be to specify limiting criteria for the query/s using the TfvcChangesetSearchCriteria Type parameter of the GetChangesetsAsync member in the TfvcHttpClient type.
You do not particularly need to check each file in your scm system/project individually, checking the changesets for the specified period may be enough. Not all of the limiting values I used below are properties of the TfvcChangesetSearchCriteria type though so I've written a short example to show how I would do it i.e.
you can specify the maximum number of changesets to consider initially and the specific project you want to look at.
Note: The TheTfvcChangesetSearchCriteria type contains some additional properties that you may want to consider using.
In the example below I've used the REST API in a C# client and getting results from tfvc.
If your intention is to use a different client language and invoke the REST services directly e.g. JavaScript; the logic below should still give you some pointers.
//targeted framework for example: 4.5.2
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
public async Task GetTopChangedFilesUsingRestApi()
{
var tfsUrl = "https://<SERVERNAME>/tfs/<COLLECTION>";
var domain = "<DOMAIN>";
var password = "<PASSWORD>";
var userName = "<USERNAME>";
//Criteria used to limit results
var directoriesToScan = new List<string> {
"$/projectdir/subdir/subdir/subdirA/systemnameA",
"$/projectdir/subdir/subdir/subdirB/systemnameB",
"$/projectdir/subdir/subdir/subdirC/systemnameC",
"$/projectdir/subdir/subdir/subdirD/systemnameD"
};
var maxResultsPerPath = 10;
var fromDate = DateTime.Now.AddDays(-120);
var toDate = DateTime.Now;
var fileExtensionToInclude = new List<string> { ".cs", ".js" };
var folderPathsToInclude = new List<string> { "/subdirToForceInclude/" };
var extensionExclusions = new List<string> { ".csproj", ".json", ".css" };
var fileExclusions = new List<string> { "AssemblyInfo.cs", "jquery-1.12.3.min.js", "config.js" };
var pathExclusions = new List<string> {
"/subdirToForceExclude1/",
"/subdirToForceExclude2/",
"/subdirToForceExclude3/",
};
//Establish connection
VssConnection connection = new VssConnection(new Uri(tfsUrl),
new VssCredentials(new Microsoft.VisualStudio.Services.Common.WindowsCredential(new NetworkCredential(userName, password, domain))));
//Get tfvc client
var tfvcClient = await connection.GetClientAsync<TfvcHttpClient>();
foreach (var rootDirectory in directoriesToScan)
{
//Set up date-range criteria for query
var criteria = new TfvcChangesetSearchCriteria();
criteria.FromDate = fromDate.ToShortDateString();
criteria.ToDate = toDate.ToShortDateString();
criteria.ItemPath = rootDirectory;
//get change sets
var changeSets = await tfvcClient.GetChangesetsAsync(
maxChangeCount: int.MaxValue,
includeDetails: false,
includeWorkItems: false,
searchCriteria: criteria);
if (changeSets.Any())
{
var sample = new List<TfvcChange>();
Parallel.ForEach(changeSets, changeSet =>
{
sample.AddRange(tfvcClient.GetChangesetChangesAsync(changeSet.ChangesetId).Result);
});
//Filter changes contained in changesets
var changes = sample.Where(a => a.ChangeType != VersionControlChangeType.Lock || a.ChangeType != VersionControlChangeType.Delete || a.ChangeType != VersionControlChangeType.Property)
.Where(e => e.Item.Path.ContainsAny(folderPathsToInclude))
.Where(e => !e.Item.Path.ContainsAny(pathExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('/') + 1).ContainsAny(fileExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(extensionExclusions))
.Where(e => e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(fileExtensionToInclude))
.GroupBy(g => g.Item.Path)
.Select(d => new { File = d.Key, Count = d.Count() })
.OrderByDescending(o => o.Count)
.Take(maxResultsPerPath);
//Write top items for each path to the console
Console.WriteLine(rootDirectory); Console.WriteLine("->");
foreach (var change in changes)
{
Console.WriteLine("ChangeCount: {0} : File: {1}", change.Count, change.File);
}
Console.WriteLine(Environment.NewLine);
}
}
}
OPTION 2B
Note: This solution is very similar to OPTION 2A with the exception of a workaround implemented to fix a limitation in the REST client API library at time of writing.
Brief summary - instead of invoking client api library to get changesets this example uses a web request direct to the REST API to fetch changesets, thus additional types were needed to be defined to handle the response from the service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
using System.Text;
using System.IO;
using Newtonsoft.Json;
public async Task GetTopChangedFilesUsingDirectWebRestApiSO()
{
var tfsUrl = "https://<SERVERNAME>/tfs/<COLLECTION>";
var domain = "<DOMAIN>";
var password = "<PASSWORD>";
var userName = "<USERNAME>";
var changesetsUrl = "{0}/_apis/tfvc/changesets?searchCriteria.itemPath={1}&searchCriteria.fromDate={2}&searchCriteria.toDate={3}&$top={4}&api-version=1.0";
//Criteria used to limit results
var directoriesToScan = new List<string> {
"$/projectdir/subdir/subdir/subdirA/systemnameA",
"$/projectdir/subdir/subdir/subdirB/systemnameB",
"$/projectdir/subdir/subdir/subdirC/systemnameC",
"$/projectdir/subdir/subdir/subdirD/systemnameD"
};
var maxResultsPerPath = 10;
var fromDate = DateTime.Now.AddDays(-120);
var toDate = DateTime.Now;
var fileExtensionToInclude = new List<string> { ".cs", ".js" };
var folderPathsToInclude = new List<string> { "/subdirToForceInclude/" };
var extensionExclusions = new List<string> { ".csproj", ".json", ".css" };
var fileExclusions = new List<string> { "AssemblyInfo.cs", "jquery-1.12.3.min.js", "config.js" };
var pathExclusions = new List<string> {
"/subdirToForceExclude1/",
"/subdirToForceExclude2/",
"/subdirToForceExclude3/",
};
//Get tfvc client
//Establish connection
VssConnection connection = new VssConnection(new Uri(tfsUrl),
new VssCredentials(new Microsoft.VisualStudio.Services.Common.WindowsCredential(new NetworkCredential(userName, password, domain))));
//Get tfvc client
var tfvcClient = await connection.GetClientAsync<TfvcHttpClient>();
foreach (var rootDirectory in directoriesToScan)
{
var changeSets = Invoke<GetChangeSetsResponse>("GET", string.Format(changesetsUrl, tfsUrl, rootDirectory,fromDate,toDate,maxResultsPerPath), userName, password, domain).value;
if (changeSets.Any())
{
//Get changes
var sample = new List<TfvcChange>();
foreach (var changeSet in changeSets)
{
sample.AddRange(tfvcClient.GetChangesetChangesAsync(changeSet.changesetId).Result);
}
//Filter changes
var changes = sample.Where(a => a.ChangeType != VersionControlChangeType.Lock || a.ChangeType != VersionControlChangeType.Delete || a.ChangeType != VersionControlChangeType.Property)
.Where(e => e.Item.Path.ContainsAny(folderPathsToInclude))
.Where(e => !e.Item.Path.ContainsAny(pathExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('/') + 1).ContainsAny(fileExclusions))
.Where(e => !e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(extensionExclusions))
.Where(e => e.Item.Path.Substring(e.Item.Path.LastIndexOf('.')).ContainsAny(fileExtensionToInclude))
.GroupBy(g => g.Item.Path)
.Select(d => new { File = d.Key, Count = d.Count() })
.OrderByDescending(o => o.Count)
.Take(maxResultsPerPath);
//Write top items for each path to the console
Console.WriteLine(rootDirectory); Console.WriteLine("->");
foreach (var change in changes)
{
Console.WriteLine("ChangeCount: {0} : File: {1}", change.Count, change.File);
}
Console.WriteLine(Environment.NewLine);
}
}
}
private T Invoke<T>(string method, string url, string userName, string password, string domain)
{
var request = WebRequest.Create(url);
var httpRequest = request as HttpWebRequest;
if (httpRequest != null) httpRequest.UserAgent = "versionhistoryApp";
request.ContentType = "application/json";
request.Method = method;
request.Credentials = new NetworkCredential(userName, password, domain); //ntlm 401 challenge support
request.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(domain+"\\"+userName + ":" + password)); //basic auth support if enabled on tfs instance
try
{
using (var response = request.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
string s = reader.ReadToEnd();
return Deserialize<T>(s);
}
}
catch (WebException ex)
{
if (ex.Response == null)
throw;
using (var responseStream = ex.Response.GetResponseStream())
{
string message;
try
{
message = new StreamReader(responseStream).ReadToEnd();
}
catch
{
throw ex;
}
throw new Exception(message, ex);
}
}
}
public class GetChangeSetsResponse
{
public IEnumerable<Changeset> value { get; set; }
public class Changeset
{
public int changesetId { get; set; }
public string url { get; set; }
public DateTime createdDate { get; set; }
public string comment { get; set; }
}
}
public static T Deserialize<T>(string json)
{
T data = JsonConvert.DeserializeObject<T>(json);
return data;
}
}
Additional References:
C# REST and SOAP (ExtendedClient) api reference
REST API: tfvc Changesets
TfvcChangesetSearchCriteria type #MSDN