Conditional filter retrieve partial object - c#

how to retrieve partial object?
{
Id:123,
Name:"david",
Languages:[{b:"en"},{b:"ru"}]
}
public async Task<myObj> Get(long id, string lang=null)
{
FilterDefinition<myObj> filter = Builders<myObj>.Filter.Eq(s => s.Id, id)
& Builders<myObj>.Filter.ElemMatch(l => l.Languages, s => s.b== lang);
ProjectionDefinition<myObj> projection = Builders<Symptom>.Projection
.Include(d => d.Id)
.Include(d => d.Name)
.Include(d => d.Languages[-1]);
FindOptions<myObj> options = new FindOptions<myObj> { Projection = projection };
using (IAsyncCursor<myObj> cursor = await db.Collection.FindAsync(filter, options))
{
return cursor.SingleOrDefault();
}
}
if i call function get(123,"cn") i expect to get:
{
Id:123,
Name:"david",
Languages:null
}
instead of null.
how to fix the query to achieve my demand?

i think this will get the job done:
public async Task<myObj> Get(long id, string lang = null)
{
var res = await db.Collection.AsQueryable()
.Where(m =>
m.Id == id &&
m.Languages.Any(l => l.b == lang))
.SingleOrDefaultAsync();
return res ?? new myObj { _Id = id, Languages = null };
}

If you want to display the languages only when they match (and null if none match up), then try the following
public async Task<myObj> Get(long id, string lang = null)
{
FilterDefinition<myObj> filter = Builders<myObj>.Filter.Eq(s => s.Id, id)
var result = await collection.Find(filter).SingleOrDefaultAsync();
if (result != null)
result.Languages = result.Languages?.Where(lng => lng.b.Equals(lang)).ToList();
return result;
}
You will get your object that you want based on the ID.. then further it will return only those languages that match up with language that you are passing (null or otherwise).

It's working. I don't know what you mean by "instead of null".
One minor thing, that you would like to not include Languges, instead you projected the Languages to an array range with the [-1]. So it's just return last element of the array. The final code is:
> db.ItemWithLanguages.find()
{ "_id" : ObjectId("5dfb57c9692d22eefa6e0cfe"), "Id" : 123, "Name" : "david", "Languages" : [ { "B" : "en" }, { "B" : "cn" } ] }
internal class MyObj
{
public long Id { get; set; }
[BsonId]
[BsonElement("_id")]
public ObjectId MyId { get; set; }
public string Name { get; set; }
public List<Language> Languages { get; set; }
}
internal class Language
{
public string B { get; set; }
}
public static async Task<MyObj> Get(IMongoCollection<MyObj> collection, long id, string lang = null)
{
FilterDefinition<MyObj> filter = Builders<MyObj>.Filter.Eq(s => s.Id, id)
& Builders<MyObj>.Filter.ElemMatch(l => l.Languages, s => s.B == lang);
// excluding d.Languages by not including it.
// it makes Languages = null.
ProjectionDefinition<MyObj> projection = Builders<MyObj>.Projection
.Include(d => d.Id)
.Include(d => d.Name);
FindOptions<MyObj> options = new FindOptions<MyObj> { Projection = projection };
using (IAsyncCursor<MyObj> cursor = await collection.FindAsync(filter, options))
{
return cursor.SingleOrDefault();
}
}
...
string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var db = client.GetDatabase("test");
var myObjs = db.GetCollection<MyObj>("ItemWithLanguages");
MyObj ret;
Task.Run(async () => { ret = await Get(myObjs, 123, "cn"); }).ConfigureAwait(false).GetAwaiter()
.GetResult();

Related

I am trying to get all employee positions (relationship many-to-many)

I'm a beginner. I try to get all the positions of the employee, but only the position of the first employee is returned. Employees and positions relationship is many-to-many. Whatever you do, the result is the same :(
View Model
public class DivisionEmployeeViewModel
{
public DivisionEmployee DivisionEmployees { get; set; }
public Division Division { get; set; }
public IEnumerable<DivisionEmployee> DivisionEmployeeList { get; set; }
public IEnumerable<EmployeePosition> EmployeePositionList { get; set; } // get 1 obj
public IEnumerable<SelectListItem> DivisionEmployeeListDropDown { get; set; }
}
Action Details
[HttpGet]
public async Task<IActionResult> Details(int id)
{
var model = new DivisionEmployeeViewModel
{
DivisionEmployeeList = await _db.DivisionEmployeesModel.Include(x => x.Employee)
.Include(x => x.Division).Where(x => x.Division_Id == id).ToListAsync(),
// Get only 1 obj
EmployeePositionList = await _db.EmployeePositions.Include(x => x.Position)
.Include(x => x.Employee).Where(x => x.Employee_Id == id).ToListAsync(),
//
DivisionEmployees = new DivisionEmployee()
{
Division_Id = id
},
Division = await _db.Divisions.FirstOrDefaultAsync(x => x.Id == id)
};
List<int> tempAssignedList = model.DivisionEmployeeList.Select(x => x.Employee_Id).ToList();
List<int> tempAssignedList2 = model.EmployeePositionList.Select(x => x.Position_Id).ToList(); // ? Get only 1 obj
// Get all items who's Id isn't in tempAuthorsAssignedList and tempCitiesAssignedList
var tempList = await _db.Employees.Where(x => !tempAssignedList.Contains(x.Id)).Where(x => !tempAssignedList2.Contains(x.Id)).ToListAsync();
model.DivisionEmployeeListDropDown = tempList.Select(x => new SelectListItem
{
Text = x.FullName,
Value = x.Id.ToString()
});
return View(model);
}
Project GitHub https://github.com/ValencyJacob/DepartmentManagementApp-Many-to-Many
Your Division_Id and Employee_Id are both id in Details(int id)?And tempAssignedList2 is a list of positionId,why you use .Where(x => !tempAssignedList2.Contains(x.Id)) to compare employeeId with positionId? – Yiyi You

trying to add different types inside if statement

I have a below method where I am loop through the list of id's and getting the data from db based on id and then creating the material and then adding to material list
public Construction AddToOsm(Model model, APIDbContext dbContext)
{
var construction = new Construction(model);
var surfaceType = dbContext.IntendedSurfaceTypes.SingleOrDefault(s => s.Id == this.SurfaceTypeId);
construction.setName(surfaceType?.Name);
using var materials = new MaterialVector();
var fenestrationMaterialById = new Dictionary<Guid, FenestrationMaterial>();
var opaqueMaterialById = new Dictionary<Guid, StandardOpaqueMaterial>();
foreach (var materialId in this.LayerIds.Where(i => i != default))
{
var opaqueMaterial = dbContext.OpaqueMaterials.SingleOrDefault(o => o.Id == materialId);
if (opaqueMaterial != default)
{
materials.Add(opaqueMaterialById.GetOrCreate(opaqueMaterial.Id, () => opaqueMaterial.AddToOsm(model)));
}
else
{
var glazingMaterial = dbContext.GlazingMaterials.SingleOrDefault(o => o.Id == materialId);
if (glazingMaterial != default)
{
materials.Add(fenestrationMaterialById.GetOrCreate(glazingMaterial.Id, () => glazingMaterial.AddToOsm(model)));
}
else
{
var glazingSimpleMaterial = dbContext.SimpleGlazingMaterials.SingleOrDefault(s => s.Id == materialId);
if(glazingSimpleMaterial != default)
{
materials.Add(fenestrationMaterialById.GetOrCreate(glazingSimpleMaterial.Id, () => glazingSimpleMaterial.AddToOsm(model)));
}
else
{
var gasGlazingMaterials = dbContext.GasGlazingMaterials.SingleOrDefault(a => a.Id == materialId);
if(gasGlazingMaterials != default)
{
materials.Add(fenestrationMaterialById.GetOrCreate(gasGlazingMaterials.Id, () => gasGlazingMaterials.AddToOsm(model)));
}
}
}
}
}
construction.setLayers(materials);
return construction;
}
I am looking a way to avoid this much of if-else statements mainly refactoring this but could not find a way to do. Could any one please suggest any idea on how to achieve the same.
Thanks in advance.
update: sample entity structure
public class GasGlazingMaterial : ISourceOfData, IIdentity<Guid>
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
......
.....
}
A simple fix would be to "continue" after each materials.add. This would mean you dont need to embed the rest in an else

MongoDb C# Driver - DeleteMany doesn't work with In Filter

I tried to delete dublicate records using MongoDb C# Driver. The script below neither raise an error nor delete any record. Any advice ?
[BsonIgnoreExtraElements]
public class ApiUsers
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public ObjectId Id { get; set; }
public string UserEMail { get; set; }
}
var users = _mongoDbContext.ApiUsers.Find(f => f.UserEMail == email).ToList();
var oneUser = users[0];
var idsToDelete = users.Where(x => !x.Id.Equals(oneUser.Id)).Select(x => x.Id);
if (idsToDelete.Any())
{ //Delete Dublicates
var idsToDeleteFilter = Builders<ApiUsers>.Filter.In(t => t.Id, idsToDelete);
var result = _mongoDbContext.ApiUsers.DeleteMany(idsToDeleteFilter);
}
I tried also DeleteOne method like below, but it didn't work either,
foreach (var idToDelete in idsToDelete)
_mongoDbContext.ApiChildUsers.DeleteOne(Builders<ApiChildUsers>.Filter.Eq(u => u.Id, idToDelete));
you could try the following approach where you get back an Id to keep and delete the rest like so:
var filter = Builders<ApiUsers>.Filter.Where(u => u.UserEMail == "test#test.com");
var projection = Builders<ApiUsers>.Projection.Expression(u=>u.Id);
var options = new FindOptions<ApiUsers, string> { Projection = projection, Limit = 1 };
var idToKeep = collection.FindSync(filter, options).ToList()[0];
collection.DeleteMany(
u => u.UserEMail == "test#test.com" &&
u.Id != idToKeep);
as a sidenote, i'd like to suggest you store the IDs in the server as ObjectId instead of string because it's less efficient as ObjectId only takes 12 bytes to store which would be less than the hexadecimal string representation of it.
here's a less verbose test program using mongodb.entities:
using MongoDB.Entities;
using System.Threading.Tasks;
namespace TestApp
{
public class ApiUsers : Entity
{
public string UserEMail { get; set; }
}
internal static class Program
{
private static async Task Main()
{
await DB.InitAsync("test", "localhost");
await new[] {
new ApiUsers { UserEMail = "test#test.com"},
new ApiUsers { UserEMail = "test#test.com"},
new ApiUsers { UserEMail = "test#test.com"},
new ApiUsers { UserEMail = "teX#teX.com"},
}.SaveAsync();
var idToKeep = (await DB.Find<ApiUsers, string>()
.Match(u => u.UserEMail == "test#test.com")
.Project(u => u.ID)
.Limit(1)
.ExecuteAsync())[0];
await DB.DeleteAsync<ApiUsers>(
u => u.UserEMail == "test#test.com" &&
u.ID != idToKeep);
}
}
}

How to get specific reports by Id in C#

The code generates all events of a specific producer, so that works.
But the problem is that when I return new ApiListModel< EventPerYearReport >() it goes to the class ApiListModel and then it says "status_code": 200, "data": null.
How do I generate, by executing the following URL: /api/dashboard/1, a value instead of "status_code": 200, "data": null?
DashboardController.cs
[HttpGet("{id}")]
public async Task<ApiListModel<EventPerYearReport>> GetEventPerYearReport(int id)
{
return await _dashboardService.GetEventPerYearReport(id);
}
DashboardService.cs
public async Task<ApiListModel<EventPerYearReport>> GetEventPerYearReport(int id)
{
var results = _dbContext.Events
.Where(p => p.ProducerId == id)
.GroupBy(e => e.DateStartEvent.ToString("yyyy"), (year, values) => new EventPerYear
{
year = year,
total = values.Count(),
}).ToList();
var report = new EventPerYearReport()
{
eventsPerYear = results
};
return new ApiListModel<EventPerYearReport>();
}
ApiListModel.cs
public class ApiListModel<T>
{
[JsonProperty(PropertyName = "status_code")]
public int StatusCode = 200;
[JsonProperty(PropertyName = "data")]
public List<T> Data;
}
EventPerYearReport.cs
public class EventPerYearReport
{
public List<EventPerYear> eventsPerYear { get; set; }
}
Your DashboardService.cs is returning new ApiListModel<EventPerYearReport>(); this will always have null data.
To fix this you need to use the following
public async Task<ApiListModel<EventPerYearReport>> GetEventPerYearReport(int id)
{
var results = _dbContext.Events
.Where(p => p.ProducerId == id)
.GroupBy(e => e.DateStartEvent.ToString("yyyy"), (year, values)
=> new EventPerYear
{
year = year,
total = values.Count(),
}).ToList();
return new ApiListModel<EventPerYearReport>()
{
Data = new EventPerYearRaport()
{
eventsPerYear = results
}
};
}

Conditionally Populate Class Field in LINQ To Entities

I have an EF class that looks like this:
public class Item
public string ItemId{ get; set; }
public string NormalDescription { get; set; }
public string LongDescription { get; set; }
public string ShortDescription { get; set; }
.. <snip>
In addition, I have a DTO that looks like this:
public class ItemDTO
public string Id { get; set; }
public string DisplayName { get; set; }
.. <snip>
Upon loading the data from the 'Item' class into the DTO, I need to conditionally set 'DisplayName' based on a configuration setting. In other words, I'm looking for something similar to:
return _repo.GetAsQueryable<Item>()
.Select(i=> new ItemDTO
{
Id = i.ItemId,
DisplayName = (setting == 1) ? i.NormalDescription :
(setting == 2) ? i.LongDescription :
(setting == 3) ? i.ShortDescription :
String.Empty
}
Of course, this results in some very inefficient SQL (using 'CASE' to evaluate each possible value) being sent to the database. This is a performance issue as there's a TON of description fields on the Item.
That being said, is there a way to select ONLY the field that's required to populate the 'DisplayName' value?
In other words, instead of a query filled with 'CASE WHEN' logic, I'd like to ONLY retrieve one of the Description values based on my application configuration setting.
You should create lambda Expression dynamically:
var typeOfItem = typeof(Item);
var argParam = Expression.Parameter(typeOfItem, "x");
var itemIdProperty = Expression.Property(argParam, "ItemId");
var properties = typeOfItem.GetProperties();
Expression descriptionProperty;
if (setting < properties.Count())
descriptionProperty = Expression.Property(argParam, properties[setting].Name);
else
descriptionProperty = Expression.Constant(string.Empty);
var ItemDTOType = typeof(ItemDTO);
var newInstance = Expression.MemberInit(
Expression.New(ItemDTOType),
new List<MemberBinding>()
{
Expression.Bind(ItemDTOType.GetMember("Id")[0], itemIdProperty),
Expression.Bind(ItemDTOType.GetMember("DisplayName")[0], descriptionProperty),
}
);
var lambda = Expression.Lambda<Func<Item, ItemDTO>>(newInstance, argParam);
return _repo.GetAsQueryable<Item>().Select(lambda);
Something like this?
var repo = _repo.GetAsQueryable<Item>();
if (setting == 1)
{
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.NormalDescription
});
}
if (setting == 2)
{
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.LongDescription
});
}
if (setting == 3)
{
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.ShortDescription
});
}
return repo.Select(i => new ItemDTO
{
Id = i.ItemId,
DisplayName = String.Empty
});
EDIT
You can create the expression dynamically as Slava Utesinov showed. If you do not want to build the whole expression, you can replace just the parts you want:
public class UniRebinder : ExpressionVisitor
{
readonly Func<Expression, Expression> replacement;
UniRebinder(Func<Expression, Expression> replacement)
{
this.replacement = replacement;
}
public static Expression Replace(Expression exp, Func<Expression, Expression> replacement)
{
return new UniRebinder(replacement).Visit(exp);
}
public override Expression Visit(Expression p)
{
return base.Visit(replacement(p));
}
}
Expression<Func<Item, ItemDTO>> ReplaceProperty(
int setting, Expression<Func<Item, ItemDTO>> value)
{
Func<MemberExpression, Expression> SettingSelector(int ss)
{
switch (ss)
{
case 1: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.NormalDescription)));
case 2: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.LongDescription)));
case 3: return x => Expression.MakeMemberAccess(x.Expression, typeof(Item).GetProperty(nameof(Item.ShortDescription)));
default: return x => Expression.Constant(String.Empty);
}
}
return (Expression<Func<Item, ItemDTO>>)UniRebinder.Replace(
value,
x =>
{
if (x is MemberExpression memberExpr
&& memberExpr.Member.Name == nameof(Item.NormalDescription))
{
return SettingSelector(setting)(memberExpr);
}
return x;
});
}
private void Test()
{
var repo = (new List<Item>() {
new Item() {
ItemId ="1",
LongDescription = "longd1",
NormalDescription = "normald1",
ShortDescription = "shortd1" },
new Item() {
ItemId ="2",
LongDescription = "longd2",
NormalDescription = "normald2",
ShortDescription = "shortd2" }
}).AsQueryable();
for (int selector = 1; selector < 5; ++selector)
{
var tst = repo.Select(ReplaceProperty(selector,
i => new ItemDTO
{
Id = i.ItemId,
DisplayName = i.NormalDescription
})).ToList();
Console.WriteLine(selector + ": " + string.Join(", ", tst.Select(x => x.DisplayName)));
//Output:
//1: normald1, normald2
//2: longd1, longd2
//3: shortd1, shortd2
//4: ,
}
}

Categories

Resources