I have these entities in my database
For a given Job, I want to basically get the results in the following form
<Translation>,<ExternalUnit.Text>,<ExternalTranslation.Text>
where the joining condition is that Translation.Unit.Text == ExternalUnit.Text
This is what I have so far, working fine:
var props = session.QueryOver<Translation>(() => translation)
.Select(c => translation.Id, c => externalUnit.Text, c => externalTranslation.Text)
.JoinAlias(() => translation.TranslationUnit, () => unit)
.JoinAlias(() => unit.Job, () => job)
.Where(() => unit.Job == job)
.JoinAlias(() => job.ExternalUnits, () => externalUnit)
.JoinAlias(() => externalUnit.ExternalTranslations, () => externalTranslation)
.Where(() => externalUnit.Text == unit.Text)
.List<object[]>();
var translations = session.QueryOver<Translation>(() => translation)
.JoinAlias(() => translation.TranslationUnit, () => unit)
.JoinAlias(() => unit.Job, () => job)
.Where(() => unit.Job == job)
.JoinAlias(() => job.ExternalUnits, () => externalUnit)
.JoinAlias(() => externalUnit.ExternalTranslations, () => externalTranslation)
.Where(() => externalUnit.Text == unit.Text)
.List<Translation>()
.ToList();
Then I loop through translations, referring to props. However, I don't like this approach since I unnecessarily perform two (almost identical) queries to the database instead of just one.
But I can't get the desired projection working. I was thinking about something like this:
var data = session.QueryOver<Translation>(() => translationAlias)
.JoinAlias(() => translation.TranslationUnit, () => unit)
.JoinAlias(() => unit.Job, () => job)
.Where(() => unit.Job == job)
.JoinAlias(() => job.ExternalUnits, () => externalUnit)
.JoinAlias(() => externalUnit.ExternalTranslations, () => externalTranslation)
.Where(() => externalUnit.Text == unit.Text)
.Select(() => translation, () => externalUnit.Text, () => externalTranslation.Text)
.List()
but, obviously, NHibernate does not like the Select(() => translation...) bit (it does not allow me to project the whole entity).
Ideally I would like to select into anonymous types, like
var data = session.QueryOver<Translation>()
...
.Select(() => new { A = translation, B = externalTranslation })
but I guess NHibernate is not there so far...
Thank you very much for any suggestion.
I've got it! NHibernate's LINQ provider saved me. What's great about it is that it can select into anonymous types, which makes joining so much easier. Just in case someone is curious, here it is for my particular case:
var q =
(from c in
(from b in
(from translation in session.Query<Translation>()
join unit in units on translation.Unit equals unit
where unit.Job == job
select new { Translation = translation, Original = unit.Text })
join extUnit in externalUnits on job equals extUnit.Job
where extUnit.Text == b.Original
select new { Translation = b.Translation, ExternalUnit = extUnit })
join extTranslation in extTranslations on c.ExternalUnit equals extTranslation.Unit
select new { Translation = c.Translation, Suggestion = extTranslation })
.ToList();
The SQL it generates is very reasonable, so I am pretty happy :)
Related
This is my current code:
useEffect(() => {
profile.familyCode.forEach((code) => {
console.log(code._id)
onSnapshot(query(collection(db, "group-posts", code._id, "posts"), orderBy("timestamp", "desc")
),
(querySnapshot) => {
const posts = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMessages([...messages, posts])
}
)
})
There are TWO code._id's and currently it is only setting my messages from one of them. What am I missing here?
Ive tried using some of firestores logical expressions to do the same thing with no success. This way I can at least pull some of them, but I would like to pull ALL of the posts from BOTH code._id's
You are missing the fact that setMessages is not updating messages itself immediately. So messages are closure-captured here with the old (or initial value) and calling setMessages will just overwrite what was previously set by previous onSnapshot.
Next issue - onSnapshot returns the unsubscribe function which should be called to stop the listener. Or you will get some bugs and memory leaks.
Here is a fast-written (and not really tested) example of possible solution, custom hook.
export function useProfileFamilyGroupPosts(profile) {
const [codeIds, setCodeIds] = useState([]);
const [messagesMap, setMessagesMap] = useState(new Map());
const messages = useMemo(() => {
if (!messagesMap || messagesMap.size === 0) return [];
// Note: might need some tweaks/fixes. Apply .flatMap if needed.
return Array.from(messagesMap).map(([k, v]) => v);
}, [messagesMap])
// extract codeIds only, some kind of optimization
useEffect(() => {
if (!profile?.familyCode) {
setCodeIds([]);
return;
}
const codes = profile.familyCode.map(x => x._id);
setCodeIds(curr => {
// primitive arrays comparison, replace if needed.
// if curr is same as codes array - return curr to prevent any future dependent useEffects executions
return curr.sort().toString() === codes.sort().toString() ? curr : codes;
})
}, [profile])
useEffect(() => {
if (!codeIds || codeIds.length === 0) {
setMessagesMap(new Map());
return;
}
const queries = codeIds.map(x => query(collection(db, "group-posts", x, "posts"), orderBy("timestamp", "desc")));
const unsubscribeFns = queries.map(x => {
return onSnapshot(x, (querySnapshot) => {
const posts = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
// update and re-set the Map object.
setMessagesMap(curr => {
curr.set(x, posts);
return new Map(curr)
})
});
});
// we need to unsubscribe to prevent memory leaks, etc
return () => {
unsubscribeFns.forEach(x => x());
// not sure if really needed
setMessagesMap(new Map());
}
}, [codeIds]);
return messages;
}
The idea is to have a Map (or just {} key-value object) to store data from snapshot listeners and then to flat that key-value to the resulting messages array. And to return those messages from hook.
Usage will be
const messages = useProfileFamilyGroupPosts(profile);
Is there a way to refactor/reduce this code please. I have the impresssion yes but I don't know how to do. I'm getting my data from my API.
export default function Plant() {
const [plant, setPlant] = useState([])
const [value1, setValue1] = useState(null);
const [value2, setValue2] = useState(null)
const [value3, setValue3] = useState(null)
...
useEffect(() => {
axios.post(url, { plantId })
.then(res => {
console.log(res)
setPlant(res.data.plants[0])
})
.catch(err => {
console.log(err)
})
}, [plantId]);
useEffect(() => {
if (plant?.water) {
setValue1(WATER.find((t) => t.label === plant.water));
}
}, [plant]);
useEffect(() => {
if (plant?.sun) {
setValue2(SUN.find((t) => t.label === plant.sun));
}
}, [plant]);
useEffect(() => {
if (plant?.date) {
setValue3(DATE.find((t) => t.label === plant.date));
}
}, [plant]);
return (
<div>
<MyDrop options={SUN} value={value2} setValue={setValue2} />
</div>
)
Please check MyDrop and Label code here here:
The last three all depend on the same plant, so there shouldn't be an issue putting them together.
useEffect(() => {
if (plant?.water) {
setValue1(OPTION1.find((t) => t.label === plant.water));
}
if (plant?.sun) {
setValue2(OPTION2.find((t) => t.label === plant.sun));
}
if (plant?.date) {
setValue3(OPTION3.find((t) => t.label === plant.date));
}
}, [plant]);
If you wanted to be more DRY, consider using an array of values instead of multiple separate states, something along the lines of
// feel free to add properties to the below
// which can also be declared outside the component
const plantProperties = ['water', 'sun', 'date'];
const [values, setValues] = useState(() => plantProperties.map(() => null));
useEffect(() => {
plantProperties.forEach((prop, i) => {
if (plant?.[prop]) {
setValues(
values.map((val, j) => i === j ? options[i].find((t) => t.label === plant.water) : val);
);
}
});
}, [plant]);
The plant state also looks pretty suspicious to me; the initial state is an array, since you're doing
const [plant, setPlant] = useState([])
but then you have to check if it's nullish. If you never do something like setPlant(null) or setPlant(undefined), there's no need for the null checks. If you do do that, consider whether you could instead reset the plant to the initial value with setPlant([]) or setPlant({}).
Another issue is that you check if it has certain properties directly on it, as if it was a plan object, which is really weird. Arrays generally shouldn't have arbitrary key-value pairs on them; arrays are for numeric-indexed collections of data only. If you want a collection of arbitrary key-values, consider using a plain object instead.
Another alternative you can consider is to not duplicate the same sort of data in different states. You could have only the plant state, and then calculate the dependent values synchronously afterwards, rather than in an effect hook.
const [plant, setPlant] = useState({})
const values = useMemo(() => calculateValues(plant), [plant]);
I have a custom hook that updates a state. The state is made with immer thanks to useImmer().
I have written the tests with Jest & "testing-library" - which allows to test hooks -.
All the functions work when launched alone. But when I launch them all in the same time, only the first one succeed. How so?
Here is the hook: (simplified for the sake of clarity):
export default function useSettingsModaleEditor(draftPage) {
const [settings, setSettings] = useImmer(draftPage);
const enablePeriodSelector = (enable: boolean) => {
return setSettings((draftSettings) => {
draftSettings.periodSelector = enable;
});
};
const enableDynamicFilter = (enable: boolean) => {
return setSettings((draftSettings) => {
draftSettings.filters.dynamic = enable;
});
};
const resetState = () => {
return setSettings((draftSettings) => {
draftSettings.filters.dynamic = draftPage.filters.dynamic;
draftSettings.periodSelector = draftPage.periodSelector;
draftSettings.filters.static = draftPage.filters.static;
});
};
return {
settings,
enablePeriodSelector,
enableDynamicFilter,
resetState,
};
}
And the test:
describe("enablePeriodSelector", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches period selector", () => {
act(() => result.current.enablePeriodSelector(true));
expect(result.current.settings.periodSelector).toBeTruthy();
act(() => result.current.enablePeriodSelector(false));
expect(result.current.settings.periodSelector).toBeFalsy();
});
});
describe("enableDynamicFilter", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches dynamic filter selector", () => {
act(() => result.current.enableDynamicFilter(true));
expect(result.current.settings.filters.dynamic).toBeTruthy();
act(() => result.current.enableDynamicFilter(false));
expect(result.current.settings.filters.dynamic).toBeFalsy();
});
});
describe("resetState", () => {
const { result } = useHook(() => useSettingsModaleEditor(page));
it("switches dynamic filter selector", () => {
act(() => result.current.enableDynamicFilter(true));
act(() => result.current.enablePeriodSelector(true));
act(() => result.current.addShortcut(Facet.Focuses));
act(() => result.current.resetState());
expect(result.current.settings.periodSelector).toBeFalsy();
expect(result.current.settings.filters.dynamic).toBeFalsy();
expect(result.current.settings.filters.static).toEqual([]);
});
});
All functions works in real life. How to fix this? Thanks!
use beforeEach and reset all mocks(functions has stale closure data) or make common logic to test differently and use that logic to test specific cases.
The answer was: useHook is called before "it". It must be called below.
I'm trying to do this in ReasonML without success.
The problem is that I don't know the object keys.
const items = {
foo: () => 'ok',
bar: () => 'ok2'
};
const result = Object.keys(items).reduce((acc, key) => ({
...acc, [key]: items[key]()
}), {});
console.log(result);
It is possible, but I don't see why List.fold_left should be a requirement. Js.Dict.map is much more appropriate:
let items = Js.Dict.fromList([
("foo", () => "ok"),
("bar", () => "ok2")
]);
let result = items |> Js.Dict.map([#bs] f => f());
I am currently using a datareader as the source but I want to instead use a dataset.
//datareader
AutoMapper.Mapper.CreateMap<IDataReader, AccountDTO>()
.ForMember(m => m.AccountId, opt => opt.MapFrom (r => r.GetInt32(r.GetOrdinal("AccountId"))))
.ForMember(m => m.ParentAccountId, opt => opt.MapFrom(r => r.GetInt32(r.GetOrdinal("ParentAccountId"))))
.ForMember(m => m.IsInactive, opt => opt.MapFrom(r => r.GetString(r.GetOrdinal("IsInactive"))))
.ForMember(m => m.AccountName, opt => opt.MapFrom(r => r.GetString(r.GetOrdinal("AccountName"))))
//dataset
AutoMapper.Mapper.CreateMap<DataSet, AccountDTO>()
.ForMember(m => m.AccountId, opt => opt.MapFrom(r => r.Tables[0].Columns[Constants.MappingFields.Accounts.AccountId]))
.ForMember(m => m.ParentAccountId, opt => opt.MapFrom(r => r.Tables[0].Columns[Constants.MappingFields.Accounts.ParentAccountId]))
.ForMember(m => m.IsInactive, opt => opt.MapFrom(r => r.Tables[0].Columns[Constants.MappingFields.Accounts.IsInactive]))
.ForMember(m => m.AccountName, opt => opt.MapFrom(r => r.Tables[0].Columns[Constants.MappingFields.Accounts.AccountName]))
.ForMember(m => m.AccountNumber, opt => opt.MapFrom(r => r.Tables[0].Columns[Constants.MappingFields.Accounts.AccountNumber]))
any ideas?
I want to use the dataset instead of the datareader so I dont keep the connection to the database open.
I think I have found the solution;
Create the dataset and close/dispose the connection
create a datatablereader from the datatable and pass the in
This seems to be working.
DataTableReader dataTableReader = ds.Tables[0].CreateDataReader();
conn101.Close();
conn101.Dispose();
List<AccountDTO> accountDto1s = Mapper.Map<IDataReader, List<AccountDTO>>(dataTableReader);