0

So I want to display an output that is group by two fields: SubsidiaryCode and AssetCreatedDate. My problem is it displays the grouping values redundantly. I suspect it duplicates because of my Detail class.

What I want is:

OutputAimed

But it displays like this:

enter image description here

LINQ query:

        public DateTime FromDate { get; set; }
        public DateTime ToDate { get; set; }
        public IList<AssetListTemplate> List = new List<AssetListTemplate>();
        public IList<AssetListTemplate> GetList()
        {
            using (var ctx = LinqExtensions.GetDataContext<NXpert.FixedAsset.DataAccess.FixedAssetDataContext>("AccountingDB"))
            {
           
                var list = (from x in ctx.DataContext.AssetRegistryEntities
                            where x.SubsidiaryCode2 != "" && x.SubsidiaryCode2.ToUpper().Contains("y-") && x.AssetCreatedDate>=FromDate && x.AssetCreatedDate <= ToDate
                            group new { x.SubsidiaryCode2, x.AssetCreatedDate,x.AssetCategoryID } by x into groupedList
                            select new AssetListTemplate
                            {
                                IsSelected = false,
                                SubsidiaryCode = groupedList.Key.SubsidiaryCode2,
                                AssetCreatedDate = groupedList.Key.AssetCreatedDate,
                                AssetCategory = groupedList.Key.AssetCategoryID
                            }
                     
                            ).OrderBy(x => x.SubsidiaryCode).ThenBy(y => y.AssetCreatedDate).ToList();
 
                List = list;

                foreach (var item in List)
                {
                   var details =  (from x in ctx.DataContext.AssetRegistryEntities
                     join y in ctx.DataContext.AssetCategoryEntities on x.AssetCategoryID equals y.AssetCategoryID
                     join z in ctx.DataContext.FixedAssetOtherInfoEntities on x.AssetCode equals z.AssetCode
                                   where x.SubsidiaryCode2 == item.SubsidiaryCode
                     select new Details
                     {
                         AssetCode = x.AssetCode,
                         AssetCodeDesc = y.AssetCategoryDesc,
                         AssetDesc = x.AssetCodeDesc,
                         DepInCharge = z.DepartmentInCharge,
                         SerialNo = x.SerialNumber,
                         ModelNo = x.ModelNumber
                     }).ToList();

                   item.Details = details;

                }
                return List;
            }
        }

    }
    public class AssetListTemplate
    {
        public bool IsSelected { get; set; }
        public string SubsidiaryCode { get; set; }
        public DateTime? AssetCreatedDate { get; set; }
        public string AssetCategory { get; set; }
        public List<Details> Details { get; set; }
    }
    public class Details {
        public string AssetCode { get; set; }
        public string AssetCodeDesc { get; set; }
        public string AssetDesc { get; set; }
        public string DepInCharge { get; set; }
        public string SerialNo { get; set; }
        public string ModelNo { get; set; }
    }
SQL Query:


SELECT Are_SubsidiaryCode2[SubsidiaryCode],Are_AssetCreatedDate[AssetCreatedDate],Are_AssetCategoryID[AssetCategory]
FROM E_AssetRegistry
WHERE Are_SubsidiaryCode2<>''
AND Are_SubsidiaryCode2 LIKE '%Y-%'
GROUP BY Are_SubsidiaryCode2
        ,Are_AssetCreatedDate
        ,Are_AssetCategoryID
        ORDER BY AssetCreatedDate ASC
Its_Me
  • 55
  • 3
  • 7
  • 1
    What I suspect is that when you group by DateTime, it also takes count of the time. Either you should group by CreatedDate with `Date` only or group by the CreatedDate with it is converted to a Date string. – Yong Shun Feb 02 '22 at 06:17
  • I tried to convert AssetCreatedDate.Value.Date = groupedList.Key.AssetCreatedDate but it says cannot implicitly convert type System.Date.Time? to System.DateTime – Its_Me Feb 02 '22 at 06:25
  • 1
    Would be better if you can show the table schema in SQL and also the model class. Did you try `group new { x.SubsidiaryCode2, x.AssetCreatedDate?.ToString("YYYY-MM-DD") }`? – Yong Shun Feb 02 '22 at 06:29
  • try UPPER or LOWER conversion in string example x.SubsidiaryCode2.ToUpper().Contains("y-").ToUpper() – Pawan Lakhara Feb 02 '22 at 06:45
  • It doenst work, it says cannot convert to type system.collections.generic becuase it is not a delegate. I trtied to to make it a Date like this 'group new { x.SubsidiaryCode2, x.AssetCreatedDate.Value.Date }' but same output. I think it has no time value since my sql database data only has date. – Its_Me Feb 02 '22 at 06:47
  • I updated my post, included the sql query. It outputs the data correctly but I dnt know how to implement that to LINQ. – Its_Me Feb 02 '22 at 06:56

1 Answers1

1

You don't seem to be using the grouping for any aggregate function , so you could make life simpler by just using distinct:

                from x in ctx.DataContext.AssetRegistryEntities
                where x.SubsidiaryCode2.Contains("y-") && x.AssetCreatedDate>=FromDate && x.AssetCreatedDate <= ToDate
                select new AssetListTemplate
                {
                    IsSelected = false,
                    SubsidiaryCode = groupedList.Key.SubsidiaryCode2,
                    AssetCreatedDate = groupedList.Key.AssetCreatedDate.Value.Date,
                    AssetCategory = groupedList.Key.AssetCategoryID
                }
         
                ).Distinct().OrderBy(x => x.SubsidiaryCode).ThenBy(y => y.AssetCreatedDate).ToList();

Side note, you don't need to assign a list to a clas variable and also return it; I'd recommend just to return it. If you're looking to cache the results, make the class level var private, assign it and return it first time and just return it the second time (use the null-ness of the class level var to determine if the query has been run)

Expanding on the comment:

You don't need to store your data in a public property and also return it. Don't do this:

public class Whatever{

  public string Name {get;set;}

  public string GetName(){
    var name = "John";
    Name = name;
    return name;
  }

Typically we would either return it:

public class Whatever{

  public string GetName(){
    var name = MySlowDatabaseCallToCalculateAName();
    return name;
  }

//use it like:

var w = new Whatever();
var name = w.GetName();

Or we would store it:

public class Whatever{

  public string Name {get;set;}

  public void PopulateName(){
    Name = MySlowDatabaseCallToCalculateAName();
  }

//use it like
var w = new Whatever();
w.PopulateName();

var name = w.Name;

We might have something like a mix of the two if we were providing some sort of cache, like if the query is really slow and the data doesn't change often, but it is used a lot:

public class Whatever{

  private string _name;
  private DateTime _nameGeneratedAt = DateTime.MinValue;

  public string GetName(){
    //if it was more than a day since we generated the name, generate a new one
    if(_nameGeneratedAt < DateTime.UtcNow.AddDays(-1)){
      _name = MySlowDatabaseCallToCalculateAName();
      _nameGeneratedAt = DateTime.UtcNow;           //and don't do it again for at least a day 
    }

    return _name;
  }

This would mean that we only have to do the slow thing once a day, but generally in a method like "Get me a name/asset list/whatever" we wouldn't set a public property as well as return the thing; it's confusing for callers which one to use - they want the name; should they access the Name property or call GetName? If the property was called "MostRecentlyGeneratedName" and the method called "GenerateLatestName" it would make more sense - the caller can know they might call Generate..( first, and then they could use MostRecently.. - it's like a caching; the calling class can decide whether to get the latest, or reuse a recently generated one (but it does introduce the small headache of what happens if some other operation does a generate in the middle of the first operation using the property..)..

..but we probably wouldn't do this; instead we'd just provide the Generate..( method and if the caller wants to cache it and reuse the result, it can

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • @Caicus Jard, thank you. What do you mean by assign it and return it first time and just return it the 2nd time and also the null-ness of the class level. can you show me the code how to do it ? – Its_Me Feb 02 '22 at 07:41
  • I added some more words to the answer – Caius Jard Feb 02 '22 at 09:15
  • Thank you so much and for the concise explanation, you made my code cleaner.@ Caicus Jard. – Its_Me Feb 02 '22 at 09:49
  • I have a question about caching is it possible to set it to minutes/ or hours(Minimize the timeframe) instead of day to see the impact? Base on my understanding basically caching is when the data is already loaded u can just return the value (recent ones) otherwise generate new data , so in short this is benificial if u only want to use the recent data faster loading times and the caller have an option to return or to get new ones. – Its_Me Feb 02 '22 at 14:27
  • Sure. In that code just change the `DateTime.UtcNow.AddDays(-1)` to e.g. `DateTime.UtcNow.AddMinutes(-1)`, if more than one minute passes between calling the method then a new download will occur. `AddMinutes(-5)` is five minutes, `AddSeconds(-27)` is 27 seconds etc.. But remember you'll need a new context each time you query; the context also has a cache that remembers forever. Contexts aren't supposed to be long lived – Caius Jard Feb 02 '22 at 17:20
  • Okay, Thanks. Learned a new thing today. – Its_Me Feb 03 '22 at 00:02